《Widgets 边看边写》第三部分:Timelines的进阶使用
作者:雪人少校(William),iOS 开发者,现就职于字节跳动音乐团队。
Session: https://developer.apple.com/videos/play/wwdc2020/10036/
文中涉及的demo在这里[1]
整个《Widgets 边看边写》系列中的其他文章:《Widgets 边看边写》第一部分:冒险开始了《Widgets 边看边写》第二部分:Timelines 的基本使用
开始之前,我们先介绍下面三个对象(在本系列之前的 session 中已经提到过,但因为它们相对抽象,且能体现 Widget 的设计思想,为了方便理解,我们用简单的语言再描述一下)
-
Timeline:时间线,是一个关于更新策略的封装,里面包装了若干TimelineEntry
-
TimelineEntry:时间线策略的控制点,主要用来控制,更新的具体时间点(会结合Timeline的其他属性)
-
TimelineProvider:提供Timeline的那个对象,系统通过它拿到Widget的Timeline
TimelineEntry 是最本质的那个对象,用来控制Widget更新时机
按苹果一贯控制且克制的风格,关键事情要拿在自己手里,在整个 iOS 系统中进行平衡——对于 Widget 来说,“关键的事情”是电量 & 流量的开销
这种机制确保了一个 Widget 不能随意刷新自己的内容,而是要通过有计划,有规律的方式来更新
特别注意:
TimelineEntry里面的Date不是一个精确的时间,只是一种给系统的建议,系统根据自身情况来决定是否需要更新,极端情况下,比如低电,甚至不能保证更新
本篇讲动态配置,如果没有看之前部分,可以把demo下载下来直接从第三部分开始
本篇 Widget:Leadbard Widget —— 一个将角色按 health 排列的 Widget,同时点击每个角色头像能够唤起主app,并进入该角色的详情页面,先整体看一下:
主App | Widget |
---|---|
URL Session
Widget可以通过网络请求来获取数据 —— TimelineProvider 是通过回调工作的,这样会让网络处理比较简单
直接看代码(入口 CharacterDetail.loadLeaderboardData)
主旨是:
-
1 这里其实就是一个普通的 URLSession,利用 dataTask 的方式请求这个 URL,然后在回调里处理返回的数据 -
2 这里把 fauxResponse(其实就是个字符串,没啥神秘的)写入了一个在临时目录的文件 “userData.json”
Q:但这是什么操作?!写个文件,然后请求文件 URL,然后在回调里处理文件数据?!
A:这里没问题,且说明了一件事:Widget 里可以调用 URLSession 系列的 API,间接证明了 Widget 是有网络能力的
说到这里,我们仔细想一下,为啥要在 demo 里证明 Widget 有哪些能力?
了解过 Extension 开发的同学应该明白,苹果有好多 extension 类型:
ShareExtension | 点击“信息” |
---|---|
时间上先有虎嗅 App 进程,然后才有“信息”界面,我们合理推论一下
-
低成本:“信息”的界面运行在“虎嗅”进程里了
-
高成本:“信息”也是独立进程和“虎嗅”跨进程通讯
-
从安全角度考虑,利用进程间的页表隔离,彻底隔离两个进程的数据(符合苹果一贯的谨慎)
无论哪种,都说明 extension 的进程和他的属主 app 的进程并不一定是同一个
之所以不一定,看 Extension 的类型,反例是 NotificationExtension
回到主题,Widget 也是 Extension,它有自己的运行时环境,且在 iPhone/iPad 这样的用电池的设备上,在能用的 API 集合上必然是受限的!
Demo 刚刚说明了,URLSession 的 API 是可以放心使用的
理解了这些,后面很多内容就比较 make sense 了!
Background Session
用过后台下载特性的同学都知道,background session 在完成的时候,可选后台唤起主 app,从 AppDelegate 的这两个回调
不理解 Background Session 的同学去看看这里,这里我们不展开
对于 Widget 来说,Widget(进程)同样有能力处理 Background Session 回调
在没有 AppDelegate 的情况下,Widget 要怎么做呢?
答案是:在 StaticConfiguration 中,有一个配置 “onBackgroundURLSessionEvents”,可以注册一些回调,可以用来代替 AppDelegate 的角色
比如,注册一个处理回调
Widget唤起宿主App
想要做到这样的效果,应该怎么做?
该 SwiftUI Link API 出场了,从 AllCharactersView 讲起(我们 Leaderboard Widget 的主要视图)
这里嵌入了一个 Link(角色的url)
Q:就是 Deeplink 而已,有什么特殊的?A:是 Deeplink,但不是通过 URLScheme 做的
感兴趣的读者可以去工程里求证
看这里(注意这是主 App 的视图,不是 Widget)
链接匹配则上面的 NavationLink 会通过 SwiftUI 的数据绑定,显示对应的界面
关于数据绑定的细节,可以参考这篇[2]
Widget Bundle
目前,我们在Widget Gallery里还只能看到一个Widget
就是被 @main 标识的那个Widget
但一个 Widget Bundle 可以容纳多个 Widget,@main 可以移动到 bundle 这里
⚠️:从这里开始,我们看的是 Demo 里 Game Status Final 目录下的工程
现在能看到多个 Widget 了
动态配置 Widget
此 Target 主要是给 Widget 提供配置项目,下面详细说
Widget 可以有一些静态配置,在 part2 里介绍过
视频有点抽象,补一张图,“编辑小组件”就是这里所讨论的配置
一个配置可以通过 XCode 增加一个 IntentDefinition 来定义
不要被 SiriKit 带偏了,我们后面讨论的东西和 Siri 没有直接关系
这里之所以会出现 SiriKit 的身影,是因为现代版本的 iOS 里,Springboard 和 SiriKit 是分不开的,所以 Springboard 上的配置很多都和 SiriKit 相关
那这个配置到底干嘛的?
定义点了编辑之后,用户能看的项目
讲动态之前,先看静态配置:
注意几个细节
-
左边栏里上面那个才是动态配置,这个是静态配置,
-
配置项目是hero
-
静态配置一般是通过Enum来表达,那故名思议,这当然是静态配置
-
动态配置当然是程序控制有哪些项目的
再看Enum
一系列固定值,符合预期
再看看动态配置
这和刚刚的静态配置看起来一样的?
静态配置 | 动态配置 |
---|---|
发现了吗?配置项的类型不同
这里在视频中没有详细说,但有必要了解清楚,那按苹果工具的思路,我们尝试探索一下:点这里
然后对新加的参数调整一下类型
<
没有工程路径!说明这是一个生成的类
Show in Finder 看实际路径
一路追上去,是 DerivedData
对于动态配置,这里是关键
然后我们看一下 Hero 属性,看起来是一个形式化定义,是不是又生成代码了?
是的,读者有兴趣可以点Hero自行查看
让我们回到主线,打开 IntentHandler.swift 文件看一眼:
把两个集合合并好后,给系统回调
但系统怎么知道是 IntentHandler.swift 文件?
iOS 开发里代码逻辑的上找不出关联的东西,找 Info.plist
看到了吧?
长按编辑 | Hero 配置项出现 | 配置内容 |
---|---|---|
remoteCharacters 的内容在最后
到这里,视频就完了,但为了理解上的完整,我们再说一下如果用户真的选择了某个角色会发生什么:
我们开篇就说了这个类——提供 Timeline 的那个对象
Widget 配置中有它
Provider 有个回调
public func timeline(for configuration: DynamicCharacterSelectionIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
let selectedCharacter = character(for: configuration)
let endDate = selectedCharacter.fullHealthDate
let oneMinute: TimeInterval = 60
var currentDate = Date()
var entries: [SimpleEntry] = []
while currentDate < endDate {
let relevance = TimelineEntryRelevance(score: Float(selectedCharacter.healthLevel))
let entry = SimpleEntry(date: currentDate, relevance: relevance, character: selectedCharacter)
currentDate += oneMinute
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
注意第 2 行
func character(for configuration: DynamicCharacterSelectionIntent) -> CharacterDetail {
let name = configuration.hero?.identifier
return CharacterDetail.characterFromName(name: name)
}
会去拿 hero.identifier,然后和自己定义的角色匹配,匹配到了就会更新界面!
所以,当用户选择了 Widget 的具体配置项目之后,会触发这里的刷新
func timeline(for configuration: DynamicCharacterSelectionIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> Void)
总结
终于讲完了,为了理解上的完整性,本文做了很多扩展性的说明,感谢各位读者的耐心阅读;这是 Widget 系列的最后一篇,WidgetKit 的概念很棒,也非常好用!希望大家能真正读懂原理,创造更多优秀的 Widget!
推荐阅读
《Widgets 边看边写》第二部分:Timelines 的基本使用
关注我们
我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。
支持作者
这篇文章的内容来自于 《WWDC20 内参》。在这里给大家推荐一下这个专栏,专栏目前已经创作了 99 篇文章,只需要 29.9 元。点击【阅读原文】,就可以购买继续阅读 ~
WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。
参考资料
演示 Demo: https://developer.apple.com/documentation/widgetkit/building_widgets_using_widgetkit_and_swiftui
[2]数据绑定: https://xiaozhuanlan.com/topic/0528764139
本文分享自微信公众号 - 老司机技术周报(LSJCoding)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
蚂蚁智能运维:单指标异常检测算法初探
1 背景介绍 AntMonitor:蚂蚁集团研发的一款面向云原生时代的全功能智能运维产品,包含业务监控、应用监控、基础设施监控、云原生可观测、一站式多维分析等功能。其中,智能化的单指标异常检测是该产品最基础、最重要的组成部分。 针对时序异常检测,目前蚂蚁集团内部基本都在按照以下几个思路进行研发: 通过时序预测的方法,典型算法为 ARIMA、LSTM 等,将历史数据训练的模型预测当前时刻的幅值,通过与真实值的差异来判断此刻的异常程度。在多次尝试此类模型后发现,其不但算法复杂度较高,还存有隐藏风险,此类模型训练遵循的是全局最优化策略,因此在预测当前值时无法保证当前值是单点最优(运气不好的情况下,当前点预测值误差较大)。一种解决的思路是结合其他算法进行集成学习,将误差概率尽可能的降低; 采用深度学习的方法,通过大量采集正负样本,采用一维 CNN、甚至二维 CNN (将时序数据视为图像)的方法训练模型。在尝试该类方法后发现,虽然其能够解决一些无法用规则描述的异常场景,但要搭建一个合适的针对时序数据的网络模型难度较大,此外在当前异常标准没有完全统一的情况下,模型移植性存在着很大的问题,当不同的 ...
- 下一篇
GC调优到底是什么
点击上方的蓝字关注我吧 程序那些事 简介 我们经常会听到甚至需要自己动手去做GC调优。那么GC调优的目的到底是什么呢?让程序跑得更快?让GC消耗更少的资源?还是让程序更加稳定? 带着这些疑问来读一下这篇文章,将会得到一个系统的甚至是不一样的结果。 那些GC的默认值 其实GC或者说JVM的参数非常非常的多,有控制内存使用的: 有控制JIT的: 有控制分代比例的,也有控制GC并发的: 当然,大部分的参数其实并不需要我们自行去调整,JVM会很好的动态帮我们设置这些变量的值。 如果我们不去设置这些值,那么对GC性能比较有影响的参数和他们的默认值有哪些呢? 01 GC的选择 我们知道JVM中的GC有很多种,不同的GC选择对java程序的性能影响还是比较大的。 在JDK9之后,G1已经是默认的垃圾回收器了。 我们看一下G1的调优参数。 G1是基于分代技术的,其实JVM还在开发一些不再基于分代技术的GC算法,比如ZGC,我们可以根据需要来选择适合我们的GC算法。 02 GC的最大线程个数 GC是由专门的GC线程来执行的,并不是说GC线程越多越好,这个默认线程的最大值是由heap size和可用的CP...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Red5直播服务器,属于Java语言的直播服务器
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合Redis,开启缓存,提高访问速度