在这样一个信息时代里,有无数人不择手段利用信息差为自身获取利益。所幸,在技术领域里总有那么一帮人愿意无私奉献出自己的时间,免费向从业人员或者普罗大众分享专业技能与工作成果,成就了蓬勃发展的开源社区。🙇🏼
这个系列会通过🔗的形式(省去可能的版权纠纷)转载个人觉得非常具有价值的技术文章,也为后续自我地不断提高积累食粮~
在这样一个信息时代里,有无数人不择手段利用信息差为自身获取利益。所幸,在技术领域里总有那么一帮人愿意无私奉献出自己的时间,免费向从业人员或者普罗大众分享专业技能与工作成果,成就了蓬勃发展的开源社区。🙇🏼
这个系列会通过🔗的形式(省去可能的版权纠纷)转载个人觉得非常具有价值的技术文章,也为后续自我地不断提高积累食粮~
自 Swift 问世以来,于 iOS 中的运用在很长的一段时间内就注定离不开与 Objective-C 的混编问题。而如何让广大开发者更快地接受并逐步使用这个“亲儿子”,一定程度上会受到它的操作难度的影响。官方也给出了他们的解决方案:编译器标识符 + 混编头文件。即:
XXX-Swift.h
(可在编译设置「Generated Header Name」中修改名称) 由编译器自动生成可供 Objective-C 使用的 Swift 接口(前提是为能够桥接到 Objective-C 的接口加上 public @objc
)XXX-Bridging-Header.h
(可在编译设置「Objective-C Bridging Header」中修改路径)管理能被 Swift 访问的 Objective-C 接口如果你经历过初期的阵痛,就一定会疯狂吐槽因 Xcode 的缓存导致 XXX-Swift.h
无法即使更新而出现的各种莫名其妙的编译错误,对于同一个模块里必须加 public 才能识别的限制使组件封装性被破坏的神奇操作亦让人叫苦不迭,🤣
那么在 @objc
背后发生了什么?dynamic
又有什么作用?调用环境的不同行为是否一致呢?让我们一探究竟!
优先级反转是一个出乎意料的多线程任务调度状态,往往出现于高优先级任务等待低优先级释放临界资源。同时,该次优先级任务被次高优先级任务抢占打断执行,导致次高优先级任务先于高优先级任务执行。优先级反转带来的程序问题可大可小,因为任务的延迟执行很难被察觉(火星探测器上的那次绝对是最值钱的一次,🤣),如下文中提到的这个🌰。
在 Apple 提供的 UI 开发套件中,UIButton 是一个比较特殊的存在。在系统默认样式下,当用户点击时会有一个文本或者图片透明度变化的高亮效果。这个简单的交互行为能够一定程度上提升用户的使用体验,因为它含蓄地提醒了某个功能区的响应范围。不过遗憾的是这个特性并没有推广开来,UIButton 自身也是缺胳膊少腿的。而隔壁 Google 在自家 App 上已经把它玩出花儿来了,君不见那 Gmail 按钮按下时水波已经荡漾在了用户心头,而自家“孩子”却依旧像是事不关己油盐不进?
好好好!既如此,那就不妨让我们花点时间完成设计师想要的点点交互效果。
或许你有过这样的使用体验:当你看完某 App 的年度报告之后,会有一个交互按钮支持你生成视频并保存到相册。当你点击并经过一定的等待时间之后,你会发现相册里多了一段内容为自动翻页的年度报告视频。
谓之何解?照 WKWebView 且使动之为之一途。
对于商品落地页而言,价格无疑是最重要的信息。而 IAP 商品却是在 App Store Connect 后台进行管理的,Server 是无法拿到相关信息的。一种最简单的方式是:在商品创建的同时,将商品的价格信息注册到 Server,然后 Frontend 与 Server 通信获取落地页信息。但这显然存在问题,因为它无法处理实时价格、价格国际化和折扣优惠。目前唯一的解决方式是 Native 通过 StoreKit 获取商品信息,并将其告知 Frontend。
iOS 中的列表视图:UICollectionView 和 UITableView 可以在兼顾性能的同时让开发者比较容易地搭建复杂的 App UI 界面,并且用户的交互行为也变得顺畅。如果要百尺竿头更进一步的话,我们需要按需刷新列表而不是一股脑儿地调用 reloadData。在 iOS 13 之前,我们可以通过 IndexPath 或者 IndexSection 来进行局部刷新,但却几乎总是会遇到这个崩溃:
Terminating app due to uncaught exception’NSInternalInconsistencyException’,reason: ‘Invalid update: invalid number of sections. The number of sections contained in the tableView view after the update (1) must be equal to the number of sections contained in the tableView view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 1 deleted).
而造成崩溃本质上的原因是模型层和渲染层的数据不一致。考虑到了开发者的“民间疾苦“,在 iOS 13 之后,Apple 提供了一套新的 API 用于 Diff 数据源,还附送优雅的刷新动画,You deserve it!
在组件的编码工作完成之后,就需要将代码进行公开托管以供其他开发者使用。作为全球最大的“码农交流”平台,Github 无疑是最好的选择。借助于它的 action,可以自动化地帮助我们完成很多重复性工作,在保证代码质量的同时减轻维护工作的压力。
对于音视频资源来说,缓存行为分为两种:先下后播和边下边播。前者对于资源本身没有特殊要求,能够通过一个下载任务将数据转换为可播放的媒体文件即可。后者则需要其支持指定范围获取数据,这反应在 URL 响应表现为响应头中键为 accept-ranges 的值是 bytes。为了能描述这两种缓存行为,我们在 Part 1 中定义了 ZPCacheable
:它要求遵循者需要实现将 URL 异步转换为 AVURLAsset 的方法。如果过程中发生了错误,则通过 ZonPlayer.Error
这个类型去描述。这得益于 AVURLAsset 天然支持本地文件路径,并且对于流媒体来说如果有缓存需要,只需设置它的 AVAssetResourceLoaderDelegate 就可以接管数据的请求与响应。
在上文中,我们完成了 ZonPlayer
的需求分析和接口设计,后者的核心思想是面向协议编程。使用者不用关心播放器具体是什么类型,而是通过 ZPSettable
完成播放器的初始化设置,并且通过遵循 ZonPlayable
的返回值进行播放器行为的控制和状态的读取。如果从权责分明的设计原则来看,ZPSettable
中包含的音频会话和远程控制设置(可选)似乎有违规范。事实上,为了简化播放器上下文的环境的构建,特别是解决官方 SDK 挖下的坑,如此设计是目前能想到的无奈之举。欲知细节如何,且看下文分解!