iOS摸鱼周报 第三十三期

iOS摸鱼周报 第三十三期

本期概要

  • 话题:感恩节和圣诞节期间 AppStore 将正常接受审核。
  • Tips:使用 os_signpost 标记函数执行和测量函数耗时;混编|将 Objective-C typedef NSString 作为 String 桥接到 Swift 中。
  • 面试模块:LeetCode - #1 Two Sum。
  • 优秀博客:本期为大家整理了一些关于图像识别框架 Vision 的文章。
  • 学习资料:Vue Color Avatar,一个纯前端实现的头像生成网站;一篇全面介绍 WebKit 和 Gecko 内部操作的入门文章。
  • 开发工具:swiftenv。

本期话题

@zhangferry:苹果发布了年末两个重要假期关于 AppStore 审核的声明。往年圣诞节期间一般都是停止审核的,今年则会正常接受提交。但是在 11 月 24 号到 28 号(感恩节),和 12 月 23 号到 27 号(圣诞节)之间的提交审核流程会比较慢。如果可以错开排期的话尽量不要在这个时间段提审。

信息来源:Submissions now accepted through the holidays

开发Tips

使用 os_signpost 标记函数执行和测量函数耗时

整理编辑:zhangferry

os_signpost 是 iOS12 开始支持的一个用于辅助开发调试的轻量工具,它跟 Instruments 的结合使用可以发挥很大作用。os_signpost API 较简单,其主要有两大功能:做标记、测量函数耗时。

首先我们需要引入 os_signpost 并做一些初始化工作:

1
2
3
4
5
6
7
8
import os.signpost

// test function
func bindModel {
let log = OSLog(subsystem: "com.ferry.app", category: "SignLogTest")
let signpostID = OSSignpostID(log: log)
// ...
}

其中 subsystem 用于标记应用,category 用于标记调试分类。

后面试下它标记和测量函数的功能。

做标记

1
2
let functionName: String = #function
os_signpost(.event, log: log, name: "Complex Event", "%{public}s", functionName)

注意这个 API 中的 name 和后面的 format 都是 StaticString 类型(format 是可选参数)。StaticString 与 String 的区别是前者的值是由编译时确认的,其初始化之后无法修改,即使是使用 var 创建。系统的日志库 OSLog 也是选择 StaticString 作为参数类型,这么做的目的一部分在于编译器可采取一定的优化,另一部分则是出于对隐私的考量。

The unified logging system considers dynamic strings and complex dynamic objects to be private, and does not collect them automatically. To ensure the privacy of users, it is recommended that log messages consist strictly of static strings and numbers. In situations where it is necessary to capture a dynamic string, you may explicitly declare the string public using the keyword public. For example, %{public}s.

对于调试期间我们需要使用 String 附加参数的话,可以用 %{public}s 的形式格式化参数,以达到捕获动态字符串的目的。

测量函数耗时

1
2
3
os_signpost(.begin, log: log, name: "Complex calculations", signpostID: signpostID)
/// Complex Event
os_signpost(.end, log: log, name: "Complex calculations", signpostID: signpostID)

将需要测量的函数包裹在 begin 和 end 两个 os_signpost 函数之间即可。

使用

打开 Instruments,选择创建 Blank 模板,点击右上角,添加 “+” 号,双击选择添加 os_signpost 和 Time Profiler 两个模板。运行应用直到触发标记函数时停止,我们展开 os_signpost,找到我们创建的 SignLogTest,将其加到下方。调整 Time Profiler 的 Call Tree 之后就可以看到下图样式。

event 事件被一个减号所标记,鼠标悬停可以看到标记的函数名,begin 和 end 表示那个耗时函数执行的开始和结束用一个区间块表示。

其中 event 事件可以跟项目中的打点结合起来,例如应用内比较重要的几个事件之间发生了什么,他们之间的耗时是多少。

混编|将 Objective-C typedef NSString 作为 String 桥接到 Swift 中

整理编辑:师大小海腾

在 Objective-C 与 Swift 混编的过程中,我遇到了如下问题:

我在 Objective-C Interface 中使用 typedef 为 NSString * 取了一个有意义的类型别名 TimerID,但 Generated Swift Interface 却不尽如人意。在方法参数中 TimerID 类型被转为了 String,而 TimerID 却还是 NSString 的类型别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C Interface
typedef NSString * TimerID;

@interface Timer : NSObject
+ (void)cancelTimer:(TimerID)timerID NS_SWIFT_NAME(cancel(timerID:));

@end

// Generated Swift Interface
public typealias TimerID = NSString

open class Timer : NSObject {
open class func cancel(timerID: String)
}

这在 Swift 中使用的时候就遇到了类型冲突问题。由于 TimerID 是 NSString 的类型别名,而 NSString 又不能隐式转换为 String。

1
2
3
// Use it in Swift
let timerID: TimerID = ""
Timer.cancel(timerID: timerID) // Error: 'TimerID' (aka 'NSString') is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert? Insert ' as String'

可以通过以下方式解决该问题:

  1. 在 Swift 中放弃使用 TimerID 类型,全部用 String 类型
  2. 在 Swift 中使用到 TimerID 的地方显示转化为 String 类型
1
Timer.cancel(timerID: timerID as String)

但这些处理方式并不好。如果从根源上解决该问题,也就是在 Generate Swift Interface 阶段将 typedef NSString *TimerID 转换为 typealias TimerID = String,那就很棒。宏 NS_SWIFT_BRIDGED_TYPEDEF 就派上用场了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C Interface
typedef NSString * TimerID NS_SWIFT_BRIDGED_TYPEDEF;

@interface Timer : NSObject
+ (void)cancelTimer:(TimerID)timerID NS_SWIFT_NAME(cancel(timerID:));

@end

// Generated Swift Interface
public typealias TimerID = String // change: NSString -> String

open class Timer : NSObject {
open class func cancel(timerID: TimerID) // change: String -> TimerID
}

现在,我可以在 Swift 中愉快地使用 TimerID 类型啦!

1
2
let timerID: TimerID = ""
Timer.cancel(timerID: timerID)

除了 NSString,NS_SWIFT_BRIDGED_TYPEDEF 还可以用在 NSDate、NSArray 等其它 Objective-C 类型别名中。

面试解析

整理编辑:夏天

现代开发⼯程师在⾯试过程中,算法⾯试往往有⼀定程度的重要性。

算法⾯试作为基本功之⼀,它包含了太多的逻辑思维,可以考察你思考问题的逻辑和解决问题的能⼒。完全类似的业务选手只能靠挖掘,但当⼀个⼈逻辑思维和能⼒不错的情况下,其业务匹配及后期上⼿概率也会很⾼。

⾯试算法题⽬在难度上(尤其是代码难度上)会略低⼀些,倾向于考察⼀些基础数据结构与算法,通过交流暴露更多的⾯试题细节。

这也就是为什么现代算法⾯试中推崇⼀题多解,在实际算法⾯试中出现原题的概率往往不⾼,随着与面试官交流且探讨让已知的面试题出现变化。

下⾯我们以 LeetCode 开篇 TwoSum 来简要说明。

默认读者有关于时间复杂度和空间复杂度的概念。

TwoSum

给定⼀个整数数组 nums 和⼀个整数⽬标值 target ,请你在该数组中找出为⽬标值 target 的那两个整数,并返回它们的数组下标。

你可以假设每种输⼊只会对应⼀个答案。但是,数组中同⼀个元素在答案⾥不能重复出现。

你可以按任意顺序返回答案。

示例 1:

1
2
3
输⼊:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

1
2
输⼊:nums = [3,2,4], target = 6 
输出:[1,2]

示例 3:

1
2
输⼊:nums = [3,3],target = 6 
输出: [0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在⼀个有效答案

解析

作为⼏乎⼈⼈ LeetCode ,⼈⼈ Code 过的经典题⽬,本题的最优解就是时间复杂度及空间复杂度皆为 O(n) 的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var dict: [Int: Int] = [:]

for (i, n) in nums.enumerated() {
if let index = dict[target - n] {
return [i, index]
}
dict[n] = i
}

return []
}

}

但是这种面试原题,往往不是我们能正好遇到的。

删减版两数之和

给定⼀个整数数组 nums 和⼀个整数⽬标值 target ,请你在该数组中找出和为⽬标值 target 的那两个整数,并返回它们的数组下标。

示例 1:

1
2
3
输⼊:nums = [2,7,11,15], target = 9 
输出:[0,1] 解释:
因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

这就很像我们会遇到的初始问题,现在我们一起跟面试官 来确认一下面试题吧!

确认异常状态即确认出参与⼊参的限制

我们需要确保两数之和不会超过值的最⼤限制 Int.Max 以及会不会超过 Int.min

需要跟⾯试官确认参数的限制,以及超出限制以后返回的结果

确认是否有多个答案及结果顺序,及同⼀位置能否⽤多次

确认是否存在多个答案,确认数组中是否存在相同数据,以及确认是否需要展示所有正确的值即答案是否唯一且两数下标是否是顺序的

例如:

1
输⼊:nums = [2,2,7,11,15], target = 9

这个答案可能是 [0, 2][1, 2] 这也会导致最终代码的编写

确认是否整数数组是否有序

对于已经排序的数组,我们可以利⽤双指针的思想来优化我们的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
guard nums.count > 0 else {
return []
}

var start = 0
var end = nums.count-1
while start < end {
let sum = nums[start] + nums[end]
if sum == target {
return [start, end]
} else if sum < target {
start += 1
} else {
end -= 1
}
}

return []
}
}

当然可能还有其他变种,如果你有什么想法也可以来丰富所有的示例。

总结

算法⾯试题是⼀个与⾯试官交流的好途径,而在⾯试过程中⼀步步与⾯试官交流,可以展现⾯试者逻辑思维能⼒以及沟通交流能⼒。

在实际遇到面试题的时候,我们不着急写出具体的代码,展现你的思维过程,利用交流丰富你的表现,思维能力和沟通交流能力。

优秀博客

本期主题:Vision

Vision 是苹果在 WWDC 2017 推出的图像识别框架。与 Core Image、AV Capture 相比,Vision 在耗电量、耗时、精确度上表现优异。

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、使用 Vision 框架对图像进行分类 – 来自:Apple

@我是熊大:本文演示了如何使用 Vision 和 Core ML 对图像进行识别并分类,附 Apple 官方 Demo。

2、识别视频流中的对象 – 来自:Apple

@我是熊大:直接识别来自相机中的视频流,实时识别物体,本文附 Apple 官方 Demo。

3、Swift之Vision 图像识别框架 – 来自掘金:RunTitan

@皮拉夫大王:Vision 有很多应用场景,比如人脸检测、图像对比、二维码条形码检测、文字检测、目标跟踪等。每种使用场景文章都列举了代码样例。

5、用苹果官方 API 实现 iOS 备忘录的扫描文稿功能 – 来自:东坡肘子

@东坡肘子:本文将介绍如何通过 VisionKit、Vision、NaturalLanguage、CoreSpotlight 等系统框架实现与备忘录扫描文稿类似的功能。

6、理解 Vision 框架中的图片技术 – 来自掘金:RickeyBoy

@东坡肘子:本文主要介绍了 Vision 框架在图像技术方面的一些酷炫功能,并一定程度上阐述了其原理。

学习资料

整理编辑:Mimosa

Vue Color Avatar

地址:https://github.com/Codennnn/vue-color-avatar

一个纯前端实现的头像生成网站,使用 Vite + Vue3 开发,是一款矢量风格头像的生成器,你可以搭配不同的素材组件,或者通过配置代码,来生成自己的个性化头像。

浏览器的工作原理:新式网络浏览器幕后揭秘

地址:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

这是一篇全面介绍 WebKit 和 Gecko 内部操作的入门文章,是以色列开发人员塔利·加希尔大量研究的成果。在过去的几年中,她查阅了所有公开发布的关于浏览器内部机制的数据,并花了很多时间来研读网络浏览器的源代码。学习浏览器的内部工作原理将有助于你作出更明智的决策,并理解那些最佳开发实践的个中缘由。

工具推荐

整理编辑:CoderStar

swiftenv

地址https://github.com/kylef/swiftenv

软件状态:免费,开源

软件介绍

swiftenv 允许您:

  • 更改每个用户的全局 Swift 版本。
  • 设置每个项目的 Swift 版本。
  • 允许您使用环境变量覆盖 Swift 版本。

swiftenv

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十二期

iOS摸鱼周报 第三十一期

iOS摸鱼周报 第三十期

iOS摸鱼周报 第二十九期

iOS 摸鱼周报 第三十二期

iOS 摸鱼周报 第三十二期

本期概要

  • 话题:Mac 版 TestFlight 已经上线。
  • Tips:介绍了关于中间层的一些概念,前后端之间的现状以及需要解决的问题等。
  • 面试模块:能否向编译后的类增加实例变量?能否向运行时动态创建的类增加实例变量?为什么?
  • 博客主题:Swift 关键字。
  • 学习资料:来自字节跳动的 OKR 学习网站;一个关于 SwiftUI 的 Cheat Sheet —— Fucking SwiftUI。
  • 开发工具:一个允许你在 App Store 上搜索 iOS 应用程序并下载应用程序包的命令行工具 —— ipatool。

本期话题

使用 TestFlight 测试 Mac App

现在,你可以邀请人们试用你的 Mac app 的 Beta 版,在发布前为您提供宝贵的反馈。通过 TestFlight ,你可以对多达 10000 名测试人员发出邀请。TestFlight 在 Mac 端的使用与移动端无异。

安装地址:https://apps.apple.com/cn/app/testflight/id899247664。

版本要求:最低 macOS 12.0 (Monterey)

开发 Tips

整理编辑:夏天

关于中间层的一些概念

本文基于经验、个人理解及参考资料整理而出。本文以前端作为接口调用方,服务端称为接口提供者,本文指出的中间层概念是作者理解的,如果有误欢迎指正。

在传统的开发分层中,前端工程师负责前端界面层的研发,数据接口由服务端工程师提供。随着微服务架构的发展,服务端的功能更加细致,而前端与用户贴近,其数据展示是多变繁杂的。

中间层,用户接口或 Web 客户端与底层或数据库之间的逻辑层,早期也被称为粘合层,其作用是粘合前端及后端之间的形象比喻。

前后端交流的困境

由于前端直接与服务端进行对接,会导致以下问题:

前端和服务端考虑的问题不一致

前端在开发过程中需要考虑用户体验等信息,而服务端因为服务下沉及解耦等原因一般其提供独立的方法实现独立的功能。

前端入口多

前端目前有客户端(iOS, Android),PC 端及 H5 端,对待不同的前端类型可能需要不同的接口,如果用同一接口的话,需要服务端进行适配,增大了服务端的工作量。

前端需求变化大

作为面向用户侧的前端开发来说,其需求变化是频繁的,对数据要求也是不同的。服务端接口的返回值一般不是前端想要的,并且会返回远超前端想要的内容。服务端会提供不同样式的返回值,导致前端需要兼容,入口越多,兼容成本越大

安全问题

越多的底层接口的暴露意味着需要保护的内容也越多,越多的底层服务被暴露出来,其底层实现逻辑也越容易被分析然后加以攻击。

客户端更新问题

大部分情况下并不会要求客户去强制更新客户端,如果出现旧接口迁移或改造,且保留原接口的话,这种迁移和改造的必要性将会大打折扣。

这些问题导致前端和服务端之间关于 API 接口颗粒度的争吵,越来越常见。

  • 服务端提供的接口是面向前端业务还是通用服务

  • 前端希望接口更能贴近业务及其他性能问题

中间层能够解决的问题

BFF 理念中最重要的一点是 服务自治,谁用谁处理。通过中间层我们可以处理一些问题。

前后端分离

比起业务,服务端应该更多考虑接口的性能,服务能力。由中间层来实现前后端的交流,前端需求和后端需求分开了,便于后期维护

易于维护和修改的API
  1. 不同的服务端可能提供不同的接口,对于前端来说对接不同接口会导致成本的增加,并且不利于前端对接口进行统一的处理。

  2. 使用中间层能够统一处理接口返回的数据, 前端获取的数据可以经过中间层来进行处理,可以大大减少各端开发者的调用时间。

  3. 便于 Mock 数据, 由中间层定义前端的数据格式,可以在服务端 Mock 数据,后续对接接口时,只需要对中间层进行修改,便于调试。

更安全

接口之间可能存在敏感信息需要传递到下一个接口,使用中间层完成多接口串联可以减少这种敏感数据的暴露。

服务端可以限制只有中间层才能对后台进行访问,降低攻击的可能性。

中间层的限制及滥用

在明确是否使用中间层之前,需要明确这几点:

  1. 中间层不能防止错误的出现。中间层是一个中转服务,服务端还需要与中间层进行沟通,这个过程被简化了,但是并没有消除。中间层更多的是统一和聚合,而不是真正底层逻辑的实现,这些实现还需要服务端实现。
  2. 避免重复逻辑的中间层分割。我们可以有多个中间层,但是这种分割应该基于具体的用户体验而不是特定的设备。例如基于用户的需求来分割中间层是可以接受的,例如订单、产品分类等,但是根据 iOS, Android,PC, H5 来区分就是不明智的。
  3. 不要过分依赖中间层。中间层不是万能的,它可以优化用户体验,提升开发效率,但是它仅仅是一个中转层,过多的中间层逻辑处理仅仅是原来服务端的套壳,对于接口等还需要合理的设计。

问题

当然还有其他问题,例如中间层谁来开发、谁来部署、谁来运维这些问题,留待后续讨论。有两个阿里的关于中间层的引申阅读,可以来看看。

参考资料

Pattern: Backends For Frontends

The BFF Pattern (Backend for Frontend): An Introduction

Developer Experience First —— TWA 的理念与实践(附演讲视频)

Serverless For Frontend 前世今生

面试解析

整理编辑:师大小海腾

Q:能否向编译后的类增加实例变量?能否向运行时动态创建的类增加实例变量?为什么?

A:

  • 不能向编译后的类增加实例变量。类的内存布局在编译时就已经确定,类的实例变量列表存储在 class_ro_t Struct 里,编译时就确定了内存大小无法修改,所以不能向编译后的类增加实例变量。
  • 能向运行时动态创建的类增加实例变量。运行时动态创建的类只是通过 alloc 分配了类的内存空间,没有对类进行内存布局,内存布局是在类初始化过程中完成的,所以能向运行时动态创建的类增加实例变量。
1
2
3
4
Class newClass = objc_allocateClassPair([NSObject class], "Person", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
objc_registerClassPair(newClass); // 要在类注册之前添加实例变量

优秀博客

整理编辑:皮拉夫大王在此我是熊大东坡肘子

本周博客主题:Swift 关键字

在还没有学习 Swift 时,听过好几次部门内关于 Swift 的分享。依稀记得在分享会上听到了各种各样的新颖的概念,见到一些在 OC 中没有见过的关键字。希望本次主题能为 OC 的同学扫清一些学习 Swift 的障碍。

1、Swift-29个关键字,助力开发(万字长文) – 来自掘金:SunshineBrother

@皮拉夫大王:推荐新手阅读,对 Swift 比较熟悉的同学可以简单浏览校验下是否有不清楚的概念。文中有对应的示例代码,帮助大家理解。

2、Swift 的闭包为什么选用 in 关键字? – 来自知乎

@皮拉夫大王:Swift 闭包为什么用 in 可能很多同学都没有思考过。这个问题比较有意思,信息量也不是很密集,也比较轻松。该话题有人列出了苹果工程师对此的解释。

3、细说 Swift 4.2 新特性:Dynamic Member Lookup – 来自掘金:没故事的卓同学

@东坡肘子@dynamicMemberLookup 中文可以叫动态查找成员。在使用 @dynamicMemberLookup 标记了对象后(对象、结构体、枚举、protocol),实现了 subscript(dynamicMember member: String) 方法后我们就可以访问到对象不存在的属性。如果访问到的属性不存在,就会调用到实现的 subscript(dynamicMember member: String) 方法,key 作为 member 传入这个方法。

4、解析 Swift 中的 @discardableResult – 来自:SwiftMic

@东坡肘子@discardableResult 属性可能很少被人熟知,但是对于想消除方法返回值未被使用的警告来说的话,该属性还是很有用的,只需要在对应方法前添加 @discardableResult 属性即可。但是,还是要考虑是否真的需要忽略该类警告,因为有些情况下及时处理返回结果可能是一种更好的解决方案。

5、“懒”点儿好 – 来自:SwiftGG

@我是熊大:这是一个优化的小技巧–使用 lazy 关键字,可以用于属性、闭包初始化等场景;不仅如此,就连 let 修饰的常量,默认也是 lazy 的,还有其他相关的 lazy 小技巧,推荐阅读。

6、访问控制 – 来自:SwiftGG

@我是熊大:在 Swift 中,类、结构体、协议、属性、方法默认访问级别都是 internal,此外还有更多的访问级别需要我们了解,尤其是在做组件、模块时;学好关键字助你设计更好的代码。

学习资料

整理编辑:Mimosa

OKR.com

地址:https://www.okr.com/

来自字节跳动的 OKR 学习网站。你可能或多或少听说过 OKR 这个概念,但没有深入了解过。OKR 是一套协助组织进行目标管理的工具和方法,它能帮助团队明确目标、聚焦重点。该网站会从 OKR 是什么开始,带你了解 OKR 理论的优缺点,价值在哪,或者适不适合你的个人或你的公司规划,你还能在网站上面找到许多关于 OKR 的资源。

Fucking SwiftUI

地址:https://fuckingswiftui.com/

Fucking SwiftUI 是一个关于 SwiftUI 的 Cheat Sheet。这上面有许多许多(关于技术的和无关技术的都有)关于 SwiftUI 的问答,类似 “我该学 SwiftUI 么”、”SwiftUI 中 List 如何使用?”、”SwiftUI 会取代 UIKit 么?” 等等问题,也有几乎所有 SwiftUI 控件的使用方式,希望能帮到大家。(如果你的浏览器不能打脏话可以使用这个链接:https://goshdarnswiftui.com/)

工具推荐

整理编辑:CoderStar

ipatool

地址https://github.com/majd/ipatool

软件状态:免费,开源

软件介绍

ipatool 是一个允许你在 App Store 上搜索 iOS 应用程序并下载应用程序包的命令行工具。当然,这过程中需要你的账户以及密码,并且也只能下载账户过去已经下载过的应用程序。相对于使用 Apple Configurator 2 操作更加便捷一些。

ipatool

注意 gif 中的 ipa 命令实际使用中可能为 ipatool

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS 成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十一期

iOS摸鱼周报 第三十期

iOS摸鱼周报 第二十九期

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第三十一期

iOS摸鱼周报 第三十一期

本期概要

  • 本期话题:关于 In-App Events 的一些介绍。
  • Tips:优化 Xcode 增量编译的几个小技巧。
  • 面试模块:一道 RunLoop 相关题目。
  • 优秀博客:本期博客主题是 Swift 的高级中间语言:SIL。
  • 学习资料:raywenderlich 新出的 Flutter 教程;一份认知者偏差手册。
  • 开发工具:一个终端命令补全工具:fig。

本期话题

本期访谈内容准备有些晚了,暂停一期,下次一定😅。

苹果官网经常会更新一些活动或者开发相关的一些资讯,我们打算定期做一些整理和筛选,以帮助大家了解相关信息。

In-App Events(App 内活动)

整理编辑:iHTCboy

App 内活动是指 App 和游戏内的时效性活动,例如游戏竞赛、电影首映和直播体验等。用户能够直接在 iOS 和 iPadOS 上的 App Store 中探索您的 App 内活动。无论您是想吸引新用户,还是向当前用户提供最新信息,或是与以前的用户重建联系,这项功能都能够助您以全新的方式展示您的活动并扩大其触及的用户群。

苹果在 10 月 22 日开放后台,可以上传活动素材提交审核。需要注意的是,此功能目前是测试阶段,需要开发者接受此协议才生效。

2021 年 10 月 27 日起,在 iOS 15 以上设备的 App Store 上,用户可以看到 App 内活动。

详细,参见:https://developer.apple.com/cn/app-store/in-app-events/

开发Tips

Xcode 增量编译优化

整理编辑:zhangferry

相对于全量编译,增量编译才是平常开发使用最多的场景,所以这方面提升所带来的好处往往更可观。参考苹果文档 Improving the Speed of Incremental Builds ,我们可以从多个方面入手优化增量编译。

在开始优化之前更重要的是对编译时间的测量,有衡量指标才能准确分析出我们的优化效果。时间测量可以通过 Xcode 的 Product > Perform Action > Build With Timing Summary,然后在编译日志的底部查看各阶段耗时统计。

以下为优化建议:

声明脚本构建阶段脚本和构建规则的 Inputs 和 Outputs

New Build System 每次编译准备执行 Build Phase 中的脚本时,会根据 inputs 和 outputs 的状态来确定是否执行该脚本。以下情况会执行脚本:

  • 没有 input 文件
  • 没有 output 文件
  • input 文件发生变化
  • output 丢失

最近遇到一个问题刚好跟这有关,该问题导致增量编译时间很长,耗时主要集中在 CompileAsseetCatalog 阶段。

正常 CocoaPods 在处理资源 Copy 的时候是带有 input 和 output 的,用于减少资源的导入和编译行为,如下图:

我们项目中有很多私有库,里面引用图片使用了 Assets.xcassets 的形式(未封装 Bundle,静态库),这导致一个编译错误:

1
Targets which have multiple asset catalogs that aren't in the same build phase may produce an error regarding a "duplicate output file"

这个错误正是 New Build System 带来的,Build System Release Notes for Xcode 10 里有说明:

Targets which have multiple asset catalogs that aren’t in the same build phase may produce an error regarding a “duplicate output file”. (39810274)

Workaround: Ensure that all asset catalogs are processed by the same build phase in the target.

上面给出了临时的解决方案,就是将所有 asset catalogs 在同一个构建过程处理。对应到 CocoaPods 就是在 Podfile 里添加下面这句:

1
install! 'cocoapods', :disable_input_output_paths => true

该设置会关闭资源 Copy 里的 input 和 output,如上面所说,没有 input 和 output,每次都会执行资源的 Copy。因为 Pod 里的Assets.scassets 最终会和主项目的 Assets.scassets 合到一起编译成 car 文件,所以每次主项目都要等 Pods 的 Copy 完再编译,即使资源文件没有任何变更,这就导致了增量时长的增加。

CocoaPods 仓库里有一个 Issue 在讨论这个问题:Issue #8122 。但该回答下的方案均不适用,后来将私有库中资源引用的方式改为 Bundle,去掉 disable_input_output_paths 的设置,增量编译效果得到大幅提升:

其中主要占用编译耗时的 CompileAssetCatalog 阶段直接没有了。

将自己的模块应用 Module Maps

Module Maps 主要缩短的是头文件的引用问题,未 Module 化的时候,编译器会为每一个源文件预处理 .h 头文件,Module 之后,不会再预处理,而是为对应的库单独建一个缓存,之后编译重用缓存内容。在制作仓库时只要需要确保 DEFINES_MODULE 为 Yes 就可以了,剩余的工作全都可以 Xcode 代劳。

需要注意,要发挥 Module Maps 的功能,还需要确保在头文件引用时增加库的名字,这样编译器才会知道你有 Module Map。

推荐: #import <FrameworkName/Header.h>

不推荐:#import <Header.h> 或者 #import "Header.h"

明确的项目依赖

对于非必要的依赖进行移除,因为过时或多余的依赖关系可能会迫使 Xcode 在并行构建时变成顺序构建。

通常当项目引入新的 Framework 时,Xcode 会自动添加对应依赖,这种是隐式的。比较推荐显示的依赖:在 Build Phases -> Dependencies -> 点加号。

重构 Target 以提高并发能力

分析原有的构建流程,将一些额外的依赖去掉。这个改造成本稍微有点高,但某些情况下应该也能带来较大的提升。

举个例子:当一个 target 依赖多个子 targets 时,Xcode 必须等待所有子 targets 完成才能继续编译当前 target。我们可以考虑分拆依赖关系,最大化利用 Xcode 的并发能力。

面试解析

整理编辑:师大小海腾

Q:执行以下代码,打印结果是什么?

1
2
3
4
5
6
7
8
9
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});

- (void)test {
NSLog(@"2");
}

打印结果为 1、3。原因是:

  1. performSelector:withObject:afterDelay: 的本质是拿到当前线程的 RunLoop 往它里面添加 timer
  2. RunLoop 和线程是一一对应关系,子线程默认没有开启 RunLoop
  3. 当前 performSelector:withObject:afterDelay: 在子线程执行

所以 2 不会打印。

优秀博客

SIL:Swift Intermediate Language,SIL 是高级别的中间语言,SIL 由 SILGen 生成并由 IRGen 转为 LLVM IR ,SIL 会对 Swift 进行较高级别的语义分析和优化。我们看到的 @ 开头修饰的代码基本都属于 SIL 范畴。

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、Swift的高级中间语言:SIL – 来自简书:sea_biscute

@东坡肘子:在 LLVM 的官方文档中对 Swift 的编译器设计描述如下: Swift 编程语言是在 LLVM 上构建,并且使用 LLVM IR 和 LLVM 的后端去生成代码。但是 Swift 编译器还包含新的高级别的中间语言,称为 SIL。SIL 会对 Swift 进行较高级别的语义分析和优化。 本文将分析一下 SIL 设计的动机和 SIL 的应用,包括高级别的语义分析,诊断转换,去虚拟化,特化,引用计数优化,TBAA(Type Based Alias Analysis)等。并且会在某些流程中加入对 SIL 和 LLVM IR 对比。

2、一文看破Swift枚举本质 – 来自:狐友技术团队

@东坡肘子:SIL 在实际工作中的应用举例。通过分析内存布局、查看 SIL 源码等方式来探索一下枚举的底层到底是什么样子的。在 Swift 中枚举不仅仅只是一个用来区分类型的常量了,枚举的功能被大大的加强。枚举可以设置原始值,添加关联值,甚至可以添加计算属性(不能添加存储属性),定义方法,实现协议,其功能仅次于一个 Class 对象了,那么 Swift 的枚举到底是怎样实现这些功能的呢?

3、Swift Intermediate Language 初探 – 来自简书:sea_biscute

@皮拉夫大王:文章简单介绍了 SIL 以及 SIL 在 LLVM 架构中的位置。正文部分作者通过 SIL 分析来解释 extension 中 protocol 函数和对象中的 protocol 函数调用选择的问题。

4、Swift编译器中间码SIL – 来自博客:roy’s blog

@皮拉夫大王:作者首先介绍了 SIL 的设计初衷以及与 LLVM IR 的区别。文中还介绍了 SSA( static single-assignment)中“代”的概念以及 SSA 的益处。SIL 是命名函数的集合,SIL 源文件为 Module,通过 Module 可以遍历 Module 中的函数。

5、Swift Intermediate Language —— A high level IR to complement LLVM – 来自:Joe Groff 和 Chris Lattner

@我是熊大:在 LLVM 开发人员会议上 Groff 和 Chris Lattner 通过简报的方式对 SIL 进行了详细的介绍。内容包括:为什么要使用 SIL、SIL 的设计逻辑、Swift 对 SIL 的使用等内容。尽管简报为英文,但主要以代码和图表为主,对了解 SIL 的设计动机和设计原理有很大的帮助。

学习资料

整理编辑:Mimosa

Flutter Apprentice from raywenderlich

地址:https://www.raywenderlich.com/books/flutter-apprentice

raywenderlich 新出的 Flutter 教程,该网站的教程一直以简单易懂、清晰明了为特点,如果是想入门 Flutter 的话,这本书将会是不错的选择。另外该网站的 Swift Apprentice 也出了新的版本,有兴趣重温一下的小伙伴也可以阅读一下。

认知偏差知识手册

地址:https://s75w5y7vut.feishu.cn/docs/doccn3BatnScBJe7wD7K3S5poFf

一份心理学知识词条的普及手册,无关乎代码,但是对互联网工作者来说,阅读之后也许可以让你更加了解产品与用户之间的关系,能够提升产品的细节,用户的体验等等。

工具推荐

整理编辑:CoderStar

fig

地址https://fig.io/

软件状态:免费,开源

软件介绍

fig 是一个开源的终端自动补全工具,支持数百个 CLI 工具,如 gitdockernpm等等,并且可以无缝添加到你现有的终端,如 iTermHyperVSCodemacOS 终端,支持我们自己自定义一些补全规则。

fig

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十期

iOS摸鱼周报 第二十九期

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第二十七期

iOS摸鱼周报 第三十期

iOS摸鱼周报 第三十期

本期概要

  • Tips:分享 WKWebView 几个不常用的特性。
  • 面试模块:一道 Tagged Pointer 相关题目。
  • 优秀博客:本期博客整理了 Codable 在一些特殊场景的处理方式,Swift 处理 JSON 解析时的一些技术细节。
  • 学习资料:Xcode Build Settings 的参数说明网站;来自 Microsoft 的 Data Science 基础课程。
  • 开发工具:免费且开源的 Coding 时间追踪工具:wakapi。

开发Tips

WKWebView 几个不常用的特性

整理编辑:FBY展菲

1. 截获 Web URL

通过实现 WKNavigationDelegate 协议的 definePolicyFor 函数,我们可以在导航期间截获 URL。以下代码段显示了如何完成此操作:

1
2
3
4
5
6
7
8
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

let urlString = navigationAction.request.url?.absoluteString ?? ""
let pattern = "interceptSomeUrlPattern"
if urlString.contains(pattern){
var splitPath = urlString.components(separatedBy: pattern)
}
}

2. 使用 WKWebView 进行身份验证

当 WKWebView 中的 URL 需要用户授权时,我们需要实现以下方法:

1
2
3
4
5
6
7
8
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

let authenticationMethod = challenge.protectionSpace.authenticationMethod
if authenticationMethod == NSURLAuthenticationMethodDefault || authenticationMethod == NSURLAuthenticationMethodHTTPBasic || authenticationMethod == NSURLAuthenticationMethodHTTPDigest {
//Do you stuff
}
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
}

收到身份验证质询后,我们可以确定所需的身份验证类型(用户凭据或证书),并相应地使用提示或预定义凭据来处理条件。

3. 多个 WKWebView 共享 Cookie

WKWebView 的每个实例都有其自己的 cookie 存储。为了在 WKWebView 的多个实例之间共享 cookie,我们需要使用 WKHTTPCookieStore,如下所示:

1
2
3
4
let cookies = HTTPCookieStorage.shared.cookies ?? []
for (cookie) in cookies {
webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
}

4. 获取加载进度

WKWebView 的其他功能非常普遍,例如显示正在加载的 URL 的进度更新。

可以通过监听以下方法的 estimatedProgress 的 keyPath 值来更新 ProgressViews:

1
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)

5. 配置 URL 操作

使用 decisionPolicyFor 函数,我们不仅可以通过电话,Facetime 和邮件等操作来控制外部导航,还可以选择限制某些 URL 的打开。以下代码展示了每种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}

if ["tel", "sms", "mailto"].contains(url.scheme) && UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
} else {
if let host = navigationAction.request.url?.host {
if host == "www.notsafeforwork.com" {
decisionHandler(.cancel)
} else{
decisionHandler(.allow)
}
}
}
}

参考:WKWebView 几个不常用的特性

面试解析

整理编辑:师大小海腾

Q:以下两段代码的执行情况分别如何?

1
2
3
4
5
6
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghij"];
});
}
1
2
3
4
5
6
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghi"];
});
}
  • 第一段代码,self.name 是 __NSCFString 类型,存储在堆,需要维护引用计数,其 setter 方法实现为先 release 旧值,再 retain/copy 新值。这里异步并发执行 setter 就可能会有多条线程同时 release 旧值,过度释放对象,导致 Crash。
  • 第二段代码,由于指针足够存储数据,字符串的值就直接通过 Tagged Pointer 存储在了指针上,self.name 是 NSTaggedPointerString 类型。在 objc_release 函数中会判断指针是不是 Tagged Pointer,是的话就不对对象进行 release 操作,更不会过度释放而导致 Crash 了。

这里是 release 的实现:

1
2
3
4
5
6
7
8
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}

优秀博客

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、或许你并不需要重写 init(from:) 方法 – 来自:kemchenj

@东坡肘子:Codable 作为 Swift 的特性之一是很注重安全,也很严谨的,这就导致了它在实际使用时总会有这样那样的磕磕绊绊,我们不得不重写 init 方法去让它跟外部环境融洽地共存。本文介绍了一种通过重载 decodeIfPresent 方法以实现应对特殊类型的思路。从某种程度上来说,作者认为这甚至是比 Objective-C 的消息机制更加灵活的一种函数声明机制,而且它的影响范围是有限的,不容易对外部模块造成破坏(别声明为 open 或者 public 就没问题)。

2、使用 Property Wrapper 为 Codable 解码设定默认值 – 来自:onevcat

@东坡肘子:本文介绍了一个使用 Swift Codable 解码时难以设置默认值问题,并利用 Property Wrapper 给出了一种相对优雅的解决方式,来在 key 不存在时或者解码失败时,为某个属性设置默认值。这为编解码系统提供了更好的稳定性和可扩展性。最后,对 enum 类型在某些情况下是否胜任进行了简单讨论。

3、2021 年了,Swift 的 JSON-Model 转换还能有什么新花样 – 来自知乎:非著名程序员,作者 明林清

@皮拉夫大王:本文主要介绍 ExCodable 的特性和使用方法。在文章开头先介绍了常见的 JSON 转模型的几种方式,并对这些方式各自的优缺点进行了总结,随后引出 ExCodable 的特性及使用方法。

4、json 解析有什么可说道的 – 来自公众号:码农哈皮

@皮拉夫大王:文章开头先介绍了什么是 JSON。正文主要篇幅在介绍 SwiftyJSON 和 YYModel 的实现方案。文章最后引出了 HandyJSON,HandyJSON 是基于借助 metadata 结构来实现 JSON 转 Model 的。在这里额外提一句,如何推断 metadata 的结构,可以参考 GenMeta.cpp 中每个结构的 layout 函数。

5、Swift中Json转Model的便捷方式 – 来自掘金:我是熊大

@我是熊大:本文介绍 JSON、Model、Data、Dict 相互转换的小技巧和代码段,适合在实际工作中使用。

6、Swift 码了个 JSON 解析器(一) – 来自知乎:OldBirds

@我是熊大:正如作者所言,码了个 JSON 解析器,感兴趣的可以看一下。

学习资料

整理编辑:Mimosa

Xcode Build Settings

地址:https://xcodebuildsettings.com/

顾名思义,这个网站的作用是展示 Xcode 所有的 Build Settings。你可以在这里按分类查看所有的设置项,搜索你想要的设置项,或查询某个设置项的值类型及其默认值。对于常常要和 Build Settings 打交道的开发者来说,这个网站很实用。

Data-Science-For-Beginners

地址:https://github.com/microsoft/Data-Science-For-Beginners

来自 Microsoft 的 Data Science 基础课程,为期 10 周,有 20 节课。这是一个基于项目的课程,配套 40 多个小测试,通过该课程你可以学习到关于数据科学的基础知识。每节课程还有精美的插画配图,有兴趣学习 Data Science 的朋友可以尝试一下。

工具推荐

整理编辑:CoderStar

wakapi

地址https://wakapi.dev/

软件状态:免费,开源

软件介绍

Wakapi 是一个开源工具,可帮助我们跟踪使用不同编程语言等在不同项目上编码所花费的时间,并使用图表等形式展现出来,支付 Xcode,值得一玩。

wakapi

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第二十九期

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第二十七期

iOS摸鱼周报 第二十六期

iOS摸鱼周报 第二十九期

iOS摸鱼周报 第二十九期

本期概要

  • Tips:关于低电量模式的一些介绍。
  • 面试模块:Objective-C 的消息机制(下)。
  • 优秀博客:整理了几篇 Swift Tips 的文章。
  • 学习资料:Gitmoji:一个 GitHub 提交信息的 emoji 指南😎。
  • 开发工具:能够使用 Swift 开发安卓应用的工具:SCADE;可视化解析 .ndjson 文件的工具:Privacy-Insight。

本期话题

@zhangferry:本期访谈内容独立成篇了,大家可以查看本期公众号推送的次条。或者访问这个链接:

本期摸鱼周报迎来一位新伙伴:东坡肘子。肘子之前因为身体原因修养过一段时间,也因为身体的原因需要做健康记录,但并没有找到满意的记录方式,于是决定自己开发,由此结缘 iOS 做起了独立开发。之后我们还会对他进行一次访谈,带大家了解他的更多故事,你也可以关注他的博客:肘子的 Swift 记事本 https://www.fatbobman.com/

开发Tips

整理编辑:夏天

低电量模式

从 iOS 9 开始,Apple 为 iPhone 添加了低电量模式(Low Power Mode)。用户可以在 设置 -> 电池 启用低电量模式。在低电量模式下,iOS 通过制定某些节能措施来延长电池寿命,包括但不限于以下措施:

  • 降低 CPU 和 GPU 性能,降低屏幕刷新率
  • 包括联网在内的主动或后台活动都将被暂停
  • 降低屏幕亮度
  • 减少设备的自动锁定时间
  • 邮件无法自动获取,陀螺仪及指南针等动态效果将被减弱,动态屏保将会失效
  • 对于支持 5G 的 iPhone 设备来说,其 5G 能力将被限制,除非你在观看流媒体

上述节能措施是否会影响到你的应用程序,如果有的话,你可能需要针对低电量模式来适当采取某些措施。

lowPowerModeEnabled

我们可以通过 NSProcessInfo 来获取我们想要的进程信息。这个线程安全的单例类可以为开发人员提供与当前进程相关的各种信息。

一个值得注意的点是,NSProcessInfo 将尝试将环境变量和命令行参数解释为 Unicode,以 UTF-8 字符串返回。如果该进程无法成功转换为 Unicode 或随后的 C 字符串转换失败的话 —— 该进程将被忽略

当然,我们还是需要关注于低电量模式的标志,一个表示设备是否启用了低电量模式的布尔值 —— lowPowerModeEnabled

1
2
3
4
5
if NSProcessInfo.processInfo().lowPowerModeEnabled {
// 当前用户启用低电量模式
} else {
// 当前用户未启用低电量模式
}

NSProcessInfoPowerStateDidChangeNotification

为了更好的响应电量模式的切换——当电池充电到 80% 时将退出低电量模式,Apple 为我们提供了一个全局的通知NSProcessInfoPowerStateDidChangeNotification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "yourMethodName:",
name: NSProcessInfoPowerStateDidChangeNotification,
object: nil
)

func yourMethodName:(note:NSNotification) {
if (NSProcessInfo.processInfo().isLowPowerModeEnabled) {
// 当前用户启用低电量模式
// 在这里减少动画、降低帧频、停止位置更新、禁用同步和备份等
} else {
// 当前用户未启用低电量模式
// 在这里恢复被禁止的操作
}
}

总结

通过遵守 iOS 应用程序能效指南 推荐的方式,为平台的整体能效和用户体验做出改变。

参考

面试解析

整理编辑:师大小海腾

本期面试解析讲解的知识点是 Objective-C 的消息机制(下)。在上一期摸鱼周报中我们讲解了 objc_msgSend 执行流程的第一大阶段 消息发送,那么这一期我们就来聊聊后两大阶段 动态方法解析消息转发

动态方法解析

如果 消息发送 阶段未能处理未知消息,那么就会进行一次 动态方法解析。我们可以在该阶段通过动态添加方法实现,来处理未知消息。动态方法解析 后,会再次进入 消息发送 阶段,从 “去 receiverClass 的 method cache 中查找 IMP” 这一步开始执行。

具体来说,在该阶段,Runtime 会根据 receiverClass 的类型是 class/meta-class 来调用以下方法:

1
2
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;

我们可以重写以上方法,并通过 class_addMethod 函数来动态添加方法实现。需要注意的一点是,实例方法存储在类对象中,类方法存储在元类对象中,因此这里要注意传参。

1
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

如果我们在该阶段正确地处理了未知消息,那么再次进入到 消息发送 阶段肯定能找到 IMP 并调用,否则将进入 消息转发 阶段。

消息转发

消息转发 又分为 Fast 和 Normal 两个阶段,顾名思义 Fast 更快。

  1. Fast:找一个备用接收者,尝试将未知消息转发给备用接收者去处理。

具体来说,就是给 receiver 发送一条如下消息,注意有类方法和实例方法之分。

1
+/- (id)forwordingTargetForSelector:(SEL)selector;

如果我们重写了以上方法,并正确返回了一个 != receiver 的对象(备用接收者),那么 Runtime 就会通过 objc_msgSend 给备用接收者发送当前的未知消息,开启新的消息执行流程。

如果该阶段还是没能处理未知消息,就进入 Normal。需要注意,在 Fast 阶段无法修改未知消息的内容,如果需要,请在 Normal 阶段去处理。

  1. Normal:启动完整的消息转发,将消息有关的全部细节都封装到一个 NSInvocation 实例中,再给接收者最后一次机会去处理未知消息。

具体来说,Runtime 会先通过调用以下方法来获取适合未知消息的方法签名。

1
+/- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

然后根据这个方法签名,创建一个封装了未知消息的全部内容(target、selector、arguments)的 NSInvocation 实例,然后调用以下方法并将该 NSInvocation 实例作为参数传入。

1
+/- (void)forwardInvocation:(NSInvocation *)invocation;

我们可以重写以上方法来处理未知消息。在 forwardInvocation: 方法中,我们可以直接将未知消息转发给其它对象(代价太大,不如在 Fast 处理),或者改变未知消息的内容再转发给其它对象,甚至可以定义任何逻辑。

如果到了 Normal 还是没能处理未知消息,如果是没有返回方法签名,那么将调用 doesNotRecognizeSelector:;如果是没有重写 forwardInvocation:,将调用 NSObject 的 forwardInvocation: 的默认实现,而该方法的默认实现也是调用 doesNotRecognizeSelector:,表明未知消息最终未能得到处理,以 Crash 程序结束 objc_msgSend 的全部流程。

一些注意点

  • 重写以上方法时,不应由本类处理的未知消息,应该调用父类的实现,这样继承体系中的每个类都有机会处理未知消息,直至 NSObject。
  • 以上几个阶段均有机会处理消息,但处理消息的时间越早,性能就越高。
    • 最好在 动态方法解析 阶段就处理完,这样 Runtime 就可以将此方法缓存,稍后这个对象再接收到同一消息时就无须再启动 动态方法解析消息转发 流程。
    • 如果在 消息转发 阶段只是单纯想将消息转发给备用接收者,那么最好在 Fast 阶段就完成。否则还得创建并处理 NSInvocation 实例。
  • respondsToSelector: 会触发 动态方法解析,但不会触发 消息转发

优秀博客

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、【iOS】Swift Tips - (一) – 来自掘金:Layer

@皮拉夫大王:文章是作者的学习笔记,作者将 objccn.io/ 的内容整理出来,一共 6 篇,适合在地铁上阅读。在这篇文章中主要介绍了柯里化、多元组、操作符等写法和用途。

2、十个技巧让你成为更加优秀的 Swift 工程师 – 来自知乎:Summer

@皮拉夫大王:学习 Swift 不光要能写 Swift 代码,更要优雅地使用 Swift,这也是本期博客主题的目的。这篇文章介绍了巧用扩展、泛型、计算属性等优化代码,在初学者看来是比较有意思的。

3、写更好的 Swift 代码:技巧拾遗 – 来自掘金:OldBirds

@东坡肘子:作者在文章中介绍了几个很实用的 Swift 使用技巧,包括:通过前缀避免命名冲突、快速交换值、@discardableresult、访问控制等,对日常的开发很有帮助。

4、Swift:where关键词使用 – 来自掘金:season_zhu

@东坡肘子:本文介绍了 where 在 Swift 中的几个使用场景,除了应用于 for 循环外,还包括泛型约束、指明类型等。有助于更好的理解在不同上下文中的 where 用法。

5、Swift - 使用Color Literal实现代码中颜色的智能提示(Xcode自带功能) – 来自航歌

@我是熊大:Color Literal 让颜色赋值可视化。

6、【译】使用Swift自定义运算符重载 – 来自掘金:shankss

@我是熊大:有没有想过 “+”,“-”,“??” 底层是怎么实现的?想不想自己也实现一个特有的运算符,如:“–>”,这篇文章带你一起探究。

学习资料

整理编辑:Mimosa

gitmoji

地址:https://gitmoji.js.org/

gitmoji 是一个 GitHub 提交信息的 emoji 指南😎,致力于成为一个标准化的 emoji 备忘单📋,当你在提交信息时,使用 emoji 来描述成了一种简单的方式来识别提交的目的和意图🍰,因为维护者只需要看一眼所使用的 emoji 就能明白🧐。由于有很多的 emoji,所以这里创建了一份指南来让使用 emoji 变得轻松、易懂、一致🥳。

工具推荐

整理编辑:CoderStarzhangferry

SCADE

地址https://www.scade.io/

软件状态

  • SCADE Community:免费
  • SCADE Professional:$29 per month / user

软件介绍

利用 SCADE 我们可以使用 Swift 语言进行跨端原生开发。其描述特点如下:

  • 跨平台:使用相同的源代码为 iOS 和 Android 开发
  • 原生功能:不受限制地使用所有 iOS 和 Android 功能
  • 无与伦比的速度:Swift 被编译为本机二进制代码以获得无与伦比的应用程序性能
  • Swift 框架:在 iOS 和 Android 上使用流行的 Swift 框架,如 Swift Foundation,无需更改代码

SCADE

Privacy-Insight

地址https://github.com/Co2333/Privacy-Insight/releases

软件状态 :免费,开源

软件介绍

解析 iOS 15 下格式为 .ndjson 的系统隐私报告,用 SwiftUI 写成。

隐私日志的生成为设置 -> 隐私 -> 打开记录 App 活动,等待一段时间之后点击下面的存储 App 活动按钮,即可收集这一段时间的隐私日志。存储会生成一个 .ndjson 格式的文件,导出使用 Privacy-Insight 打开即可查看。

以下为我使用 1 天的隐私请求记录:

微信和今日头条的隐私权限获取频率均非常高,我是肯定没有那么频繁通过微信访问相册的。对于微信频繁获取相册权限的问题最近也在热议,希望不仅是微信,各个主流 App 都应该对于用户隐私问题予以重视。

作为使用者相对有效的保护隐私的方案是,关闭对应 App 的「后台刷新」,非必要情况下关闭蓝牙、定位等权限,并将相册调用权限改为「选中的照片」。

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第二十七期

iOS摸鱼周报 第二十六期

iOS摸鱼周报 第二十五期

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第二十八期

本期概要

  • 话题:跟 yuriko 聊一下职业选择和如何保持学习热情。
  • Tips:介绍缓动函数相关的一些内容。
  • 面试模块:Objective-C 的消息机制(上)。
  • 优秀博客:整理了几篇 Swift 协议相关的优秀文章。
  • 学习资料:两个高质量的学习仓库,用涂鸦绘画的形式讲解编程知识和 raywenderlich 出品的 Swift 编码指南。
  • 开发工具:Xcode 下载管理工具 XcodesApp。

本期话题

@zhangferry:yuriko 是群里非常活跃也很有趣的一位小伙伴,工作时间还不是太长但学习热情很高,一起来了解下他是如何看待学习这件事的。

zhangferry:简单介绍下自己吧。

大家好,我就是我们成长之路群里自诩瓜皮的同学,为什么我自诩瓜皮呢,因为我 19 年毕业,就工作经验而言对于很多前辈而言还是稚嫩了些。刚毕业时去了一个 P2P 背景的公司,后来 P2P 在疫情前没几个月就倒了,急于找到一份工作,去了一家传统行业做开发,然后今年跳槽去了家互联网股票头部公司。

zhangferry:你之前在传统企业也工作过,传统企业跟互联网公司工作的感受有什么不同。

就拿我之前的公司而言,最大的不同就是对开发过程的重视,对技术的重视程度不同。

虽然我之前待的传统行业,就这家公司而言,员工也是不少,但是对于开发部门来说,完全就是个小作坊。没有合理的生产流程,只注重生产出产品(其实也不是很注重)。一个人负责的东西很杂,测试离职后就只能自测,甚至有一个星期被迫下店当了店员(一生黑)。其实我之前早就想要离开那里,可是碍于疫情只能多留了一年。

现在在这家公司,最大的感受就是流程规范了许多,公司重视技术,有定期的技术分享,生产流程也规范了许多,也有内部的自动化平台,目前也有机会参与公司的自动化流程构建优化(脚本自动化打包等),感觉在这里可以接触到学习到很多东西。

zhangferry:感觉你兴趣范围挺广的,逆向、算法,这些由兴趣推动的技术方向,你是如何保热情持续不断的学习的呢?

也可以说是一种有目标,也是为了成功的成就感。

在我看清了前司要离开却迫于疫情留下后,我就知道要为以后做打算了,于是我每天开始刷算法题,每个模块有目的性的做下来,然后参照题解,分析自己的时空复杂度是否有优化空间。在换工作前做了300 余道算法题,刷算法的同时感受到了算法思维的重要性。

逆向的学习也是机缘巧合,当时我的好基友有一款付费办公软件找我,希望我能破解。当时我就决定去学习这部分的知识。学了一段时间,也买了 Hopper 作为分析工具,帮基友破解掉了里面的内购付费功能。为了简化部分重复的工作,抽了一段时间学习写 Shell 脚本(也稍微了解了 Python,以后会详细学习),然后自写了一套重签名脚本。软件破解成功以后我真的是满满的成就感。

zhangferry:有了学习动力还需要一些学习方法,分享一些你的学习方法吧。

我认为最重要的是要有目的性,当初我决定要跳槽后,基本上能抽的空闲时间都抽出来了,地铁上刷 MJ 的视频,回去以后打开 Leetcode 刷题,每天制定学习的时长,时间不到不能进行娱乐活动。然后只玩休闲类益智类游戏,保证不会在游戏上花掉太多时间。像现在的话,我虽然已经没有跳槽的目的性,不过最近 *OS internals part3 译本已经出来了,我也购入了一本,当前目标就是先读完这本书。虽然里面有很多陌生的概念,也磕磕绊绊的看了一百余页。所以对我而言,目的性是我学习的最大动力。

zhangferry:说一下最近的思想感悟吧。

主要就是想以我自己的经历,向跟我差不多年龄的同学分享下,一定做好职业规划。之前我去那传统公司也是看人比较多,结果却大失所望,走了不少弯路。但是也是因为在那个公司,我才明白了只有提升自己才能进入好企业来摆脱它。

之前看群里的同学们,有分享过一些开发者视频(油管上的),都是英文的。感觉虽然工作了,英语也是相当重要,建议英文基础薄弱些的同学还是花些精力在这里,毕竟一手的资料比二手的译本更能代表原作者的意思是吧。

如果有什么想跟我聊的欢迎在群里骚扰我哈,基本秒回^_^

开发Tips

整理编辑:zhangferry

缓动函数

很多动画为了效果更加自然,通常都不是线性变化的,而是先慢后快,或者先慢后快再慢的速度进行的。在 iOS 开发里会用 UIView.AnimationOptions 这个枚举值进行描述,它有这几个值:

1
2
3
4
5
6
public struct AnimationOptions : OptionSet {
public static var curveEaseInOut: UIView.AnimationOptions { get } // default
public static var curveEaseIn: UIView.AnimationOptions { get }
public static var curveEaseOut: UIView.AnimationOptions { get }
public static var curveLinear: UIView.AnimationOptions { get }
}

ease 表示减缓,所以 easeInOut 表示,进入和完成都是减缓的,则中间就是快速的,就是表示先慢后快再慢。那这个先慢后快,或者先快后慢的过程具体是如何描述的呢?这里就引入了缓动函数,缓动函数就是描述这一快慢过程的函数,其对应三种状态:easeIn、easeOut、easeInOut。

缓动函数并非特定的某一个函数,它有不同的拟合方式,不同形式的拟合效果可以参看[下图](https://easings.net/ “easings.net”):

缓动函数名例如 easeInSine 后面的 Sine 就是拟合类型,其对应的就是三角函数拟合。常见的还有二次函数 Quad,三次函数 Cubic 等。以上函数有对应的 TypeScript 源码,有了具体的计算规则,我们就可以将缓动效果应用到颜色渐变等各个方面。以下是三角函数和二次函数拟合的 Swift 版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct EasingsFunctions {
/// sine
static func easeInSine(_ x: CGFloat) -> CGFloat {
return 1 - cos((x * CGFloat.pi) / 2)
}
static func easeOutSine(_ x: CGFloat) -> CGFloat {
return sin((x * CGFloat.pi) / 2)
}
static func easeInOutSine(_ x: CGFloat) -> CGFloat {
return -(cos(CGFloat.pi * x) - 1) / 2
}
/// quad
static func easeInQuad(_ x: CGFloat) -> CGFloat {
return x * x
}
static func easeOutQuad(_ x: CGFloat) -> CGFloat {
return 1 - (1 - x) * (1 - x)
}
static func easeInOutQuad(_ x: CGFloat) -> CGFloat {
if x < 0.5 {
return 2 * x * x
} else {
return 1 - pow(-2 * x + 2, 2) / 2
}
}
}

面试解析

整理编辑:师大小海腾

本期面试解析讲解的知识点是 Objective-C 的消息机制(上)。为了避免篇幅过长这里不会展开太细,而且太细的笔者我也不会😅,网上相关的优秀文章数不胜数,如果大家看完还有疑惑🤔一定要去探个究竟🐛。

消息机制派发

“消息机制派发” 是 Objective-C 的消息派发方式,其 “动态绑定” 机制让所要调用的方法在运行时才确定,支持开发者使用 “method-swizzling”、“isa-swizzling” 等黑魔法来在运行时改变调用方法的行为。除此之外,还有 “直接派发”、“函数表派发” 等消息派发方式,这些方式在 Swift 中均有应用。

“消息” 这个词好像不常说,更多的是称之为 “方法”。其实,给某个对象 “发送消息” 就相当于在该对象上“ 调用方法”。完整的消息派发由 接收者选择子参数 构成。在 Objective-C 中,给对象发送消息的语法为:

1
id returnValue = [someObject message:parameter];

在这里,someObject 叫做 接收者,message 叫做 选择子选择子参数 合起来称为 消息。编译器看到此消息后,会将其转换为一条标准的 C 语言函数调用,所调用的函数为消息机制的核心函数 objc_msgSend

1
void objc_msgSend(id self, SEL _cmd, ...)

该函数参数个数可变,能接受两个或两个以上参数。前面两个参数 self 消息接收者_cmd 选择子 即为 Objective-C 方法的两个隐式参数,后续参数就是消息中的那些参数(也就是方法显式参数)。

Objective-C 中的方法调用在编译后会转换成该函数调用,比如以上方法调用会转换为:

1
id returnValue = objc_msgSend(someObject, @selector(message:), parameter);

除了 objc_msgSend,还有其它函数负责处理边界情况:

  • objc_msgSend_stret:待发送的消息返回的是结构体
  • objc_msgSend_fpret:待发送的消息返回的是浮点数
  • objc_msgSendSuper:给父类发消息
  • ……

在讲了一大段废话之后(废话居然占了这么大篇幅 wtm),该步入重点了,objc_msgSend 函数的执行流程是什么样的?

objc_msgSend 执行流程通常分为三大阶段:消息发送动态方法解析消息转发。而有些地方又将 动态方法解析 阶段归并到 消息转发 阶段中,从而将其分为了 消息发送消息转发 两大阶段,比如《Effective Objective-C 2.0》。好吧,其实我也不知道哪种是通常😅。

消息发送

  • 判断 receiver 是否为 nil,是的话直接 return,这就是为什么给 nil 发送消息却不会 Crash 的原因。
  • 去 receiverClass 以及逐级遍历的 superclass 中的 cache_t 和 class_rw_t 中查找 IMP,找到就调用。如果遍历到 rootClass 还没有找到的话,则进入 动态方法解析 阶段。
  • 该阶段还涉及到 initialize 消息的发送cache_t 缓存添加、扩容 等流程。

动态方法解析

消息转发

由于篇幅原因,剩下的内容我们下期再见吧👋。

优秀博客

整理编辑:皮拉夫大王在此我是熊大

1、Swift 协议 – 来自:Swift 编程语言中文教程

@我是熊大:在学习面向协议编程前,先了解 Swift 中的协议该如何使用。

2、面向协议编程与 Cocoa 的邂逅 (上) – 来自:OneV’s Den

@皮拉夫大王:文章先通过引入例子介绍 OOP 的核心思想:封装、继承。随后介绍 OOP 中 “Cross-Cutting Concerns”、多继承的菱形缺陷问题、动态派发的安全问题这三大困境。面向协议编程可以解决除菱形问题外的其他问题。

3、面向协议编程与 Cocoa 的邂逅 (下) – 来自:OneV’s Den

@我是熊大:作者使用协议演示了基于 Protocol 的网络请求,然后又回答了工作中的使用场景,正如作者所言:”通过面向协议的编程,我们可以从传统的继承上解放出来,用一种更灵活的方式,搭积木一样对程序进行组装”。

4、Swift Protocol 详解 - 协议&面向协议编程 – 来自掘金:RickeyBoy

@皮拉夫大王:文章概念性的东西较多,本文先介绍了协议的基本使用方法,主要介绍耦合相关的概念,例如耦合的 5 个级别、耦合带来的问题、依赖翻转和协议解耦等。

5、如果你还在用子类(Subclassing),那就不对了 – 来自简书:97c49dfd1f9f

@皮拉夫大王:本文主要介绍了面向协议、面向对象、函数式编程的优缺点。OC->Swift 不仅仅是语法上的变化,想想大家项目中的 xxxBasexxx.m,如果用 Swift开发需要避免再出现此类情况。

6、Swift 中的面向协议编程:是否优于面向对象编程? – 来自:SwiftGG

@我是熊大:引用作者的一句话:”30 年的开发经验,让我能够平心静气地说,你应该了解协议和 POP。开始设计并书写你自己的 POP 代码吧“。

学习资料

整理编辑:Mimosa

a-picture-is-worth-a-1000-words

地址:https://github.com/girliemac/a-picture-is-worth-a-1000-words

把复杂知识放进简单涂鸦!该仓库中用可爱的涂鸦绘制了包涵数据结构、算法、机器学习入门、web 基础开发的一些知识,画风可爱,简单易懂。但要下载的时候要注意一下,涂鸦图片很大。

raywenderlich/swift-style-guide

地址:https://github.com/raywenderlich/swift-style-guide

来自 raywenderlich 的 Swift 代码风格指南,其风格的重点在于印刷和网页版的可读性,这个风格指南是为了保持他们的书籍、教程和入门套件中的代码的优雅和一致性。可以供大家有特别需要时参考和借鉴。

工具推荐

整理编辑:brave723

XcodesApp

地址https://github.com/RobotsAndPencils/XcodesApp

软件状态: 免费,开源

软件介绍

AppStore 自带的升级功能经常因为某些奇怪的原因卡住而被吐槽,如果你也经历过这些事情可以试下 Xcodes。Xcodes 是一个 Xcode 下载管理器,支持下载不同版本的 Xcode,还可以切换默认版本。如果你喜欢命令行,还可以使用其命令行版本进行安装。

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第二十七期

iOS摸鱼周报 第二十六期

iOS摸鱼周报 第二十五期

iOS摸鱼周报 第二十四期

iOS摸鱼周报 第二十七期

iOS摸鱼周报 第二十七期

本期概要

  • 话题:跟 RunsCode 聊聊编程和兴趣爱好,以及如何在 1min 之内复原魔方😏。

  • Tips:iOS 识别虚拟定位调研;使用 App Store Connect API Key 解决 Fastlane 双重验证问题。

  • 面试模块:KVC 取值和赋值过程的工作原理。

  • 优秀博客:关于 Combine 的相关文章推荐。

  • 学习资料:阮一峰最新发布的 C 语言入门教程,GitHub 漫游指南。

  • 开发工具:Xcode 工程管理工具 XcodeProjects。

本期话题

@zhangferry:这期参与访谈的对象是摸鱼周报的另一个编辑:RunsCode,RunsCode 在我看来是一个非常酷的人。这个酷来自于他很强的学习能力和个人要求,编程和兴趣爱好都能被他玩出花来。下面通过几个问题,一起了解下他。

zhangferry:简单介绍下自己吧。

Hello,大家好我是 RunsCode,目前就职于 Hello出行,坐标杭州。

小时候因为沉迷于电脑游戏,毕业后入坑做手游,后来机缘巧合之下结识 iOS,从此移情别恋了,跟 iOS 相爱相杀至今。

zhangferry:你的学习范围比较广,安卓、Ruby、Applescript 都有写过,你是在什么场景下接触一门新语言的?学习一门新的语言,首先应该关注哪部分内容?

因为之前做的 cpp 游戏要做移动端跨平台移植对接移动支付 SDK,被迫学了 Android 和 iOS,但是还是被 iOS 的纯粹给吸引了。后来在 15 年搞 iOS 的过程中来了一个 CSDN 当时前十的大神当我们领导,开拓视野学习 Swift 和 Ruby,因为他说 Swift 借鉴了很多 Ruby 的特性,再加上 CocoaPods 也是 Ruby 写的,也就稍稍学习了一下。AppleScript 也是这个 CSDN 大神提醒的搞 Mac 自动化,后来也是一发不可收拾。

这些语言对我而言他们共性是让我写起来很开心,就像那种小孩子看到好吃的那种感觉(主要说 Swift 和 Ruby)
其实学语言都是有针对的学习,不同的语言都是有自己擅长的领域,比如说我要用 Ruby 写一个 iOS App,这就有点过分了,相比 OC/Swift 这就不是很适合了,虽说有点过分,但是真的有人这么干了,可以了解下 RubyMotion,真是极客啊。

个人觉得学习一门新语言除了一开始掌握基本的词法语法之外,慢慢的你就会发现这个语言跟你有没有共鸣,你的思路跟它的处理逻辑是不是很契合,就像大家都喜欢脚本 Python 我却更喜欢 Ruby,这主要是Ruby 的处理逻辑跟我很契合,就是同样的展示你用德玛习惯,我却用吕布更加习惯。

zhangferry:听说你乒乓球和魔方都玩的很好,这些东西的学习跟编程有没有什么共性呢?我之前也着迷过一段时间魔方(三阶),但最快也要 2min 以上,听说你的记录是 15s。由不会到会再到熟练是相对简单的过程,一般人都能做到,但再往上突破就很难了。就魔方来说,1min 对我来说就是一个大的突破了,如果要达到这一步需要做哪些事情呢?

乒乓球会一点,请过教练专门训练过基础技能,不能说玩的很好,跟小区大爷打球有时候经常翻车的,尤其打长胶翻车不少,哎……

魔方这个吧,就是比一般复原的稍微快一点,三阶止步 CFOP。

跟编程的共性:兴趣第一, 然后就是坚持,用正确的方法刻意练习就是了(高手都是寂寞的)。

一分钟还原三阶魔方来说,学会七步还原法,然后在苦练一个礼拜,每天练习两个小时应该就差不多了。当然掌握正确手法的天赋型选手也许只要两三天。

然后最重要的事情说三遍,买个好魔方, 买个好魔方, 买个好魔方!!!
千万不要买路边摊十块钱那种啊,那种容易卡住或者 POP(就是爆炸开了),及其打击自信,过来人血的教训。

怎么的也得三十块起步,国甲,孤鸿,圣手这种吧。

zhangferry:学习很多时候并不是有趣的,该如何保持学习热情?

这个我不知道怎么回答,或许是好奇,或许是焦虑,或许是爱好兴趣。我只知道是这三点支配了我。还有就是我只做自己喜欢的事情,我从不跟风,跟风容易迷失自己,适合别人的不一定适合自己。

主要还是要坚持,坚持下去能让人更坚定。时间长了你就习惯学习了,如果突然一下子你不学习,你是不是都会感到空虚、焦虑和内疚吧,哈哈哈。

zhangferry:说一个最近的思想感悟吧。

提升生活幸福感还是要终身学习,不仅限于工作技能,其他一切都可以。

开发Tips

整理编辑:FBY展菲zhangferry

iOS 识别虚拟定位调研

前言

最近业务开发中,有遇到我们的项目 App 定位被篡改的情况,在 Android 端表现的尤为明显。为了防止这种黑产使用虚拟定位薅羊毛,iOS 也不得不进行虚拟定位的规避。

第一种:使用越狱手机

一般 App 用户存在使用越狱苹果手机的情况,一般可以推断用户的行为存在薅羊毛的嫌疑(也有 App 被竞品公司做逆向分析的可能),因为买一部越狱的手机比买一部正常的手机有难度,且在系统升级和 Appstore 的使用上,均不如正常手机,本人曾经浅浅的接触皮毛知识通过越狱 iPhone5s 进行的 App 逆向。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// 判断是否是越狱设备
/// - Returns: true 表示设备越狱
func isBrokenDevice() -> Bool {

var isBroken = false

let cydiaPath = "/Applications/Cydia.app"

let aptPath = "/private/var/lib/apt"

if FileManager.default.fileExists(atPath: cydiaPath) {
isBroken = true
}

if FileManager.default.fileExists(atPath: aptPath) {
isBroken = true
}

return isBroken
}

第二种:使用爱思助手

对于使用虚拟定位的场景,大多应该是司机或对接人员打卡了。而在这种场景下,就可能催生了一批专门以使用虚拟定位进行打卡薅羊毛的黑产。对于苹果手机,目前而言,能够很好的实现的,当数爱思助手的虚拟定位功能了。

使用步骤: 下载爱思助手 Mac 客户端,连接苹果手机,工具箱中点击虚拟定位,即可在地图上选定位,然后点击修改虚拟定位即可实现修改地图的定位信息。

原理: 在未越狱的设备上通过电脑和手机进行 USB 连接,电脑通过特殊协议向手机上的 DTSimulateLocation 服务发送模拟的坐标数据来实现虚假定位,目前 Xcode 上内置位置模拟就是借助这个技术来实现的。

识别方式

一、通过多次记录爱思助手的虚拟定位的数据发现,其虚拟的定位信息的经纬度的高度是为 0 且经纬度的数据位数也是值得考究的。

二、把定位后的数据的经纬度上传给后台,后台再根据收到的经纬度获取详细的经纬度信息,对司机的除经纬度以外的地理信息进行深度比较,优先比较 altitudehorizontalAccuracyverticalAccuracy 值,根据值是否相等进行权衡后确定。

三、具体识别流程

  • 通过获取公网 ip,大概再通过接口根据 ip 地址可获取大概的位置,但误差范围有点大。
  • 通过 Wi-Fi 热点来读取 App 位置
  • 利用 CLCircularRegion 设定区域中心的指定经纬度和可设定半径范围,进行监听。
  • 通过 IBeacon 技术,使用 CoreBluetooth 框架下的 CBPeripheralManager 建立一个蓝牙基站。这种定位直接是端对端的直接定位,省去了 GPS 的卫星和蜂窝数据的基站通信。

四、iOS 防黑产虚假定位检测技术

文章的末尾附的解法本人有尝试过,一层一层通过 KVC 读取 CLLocation 的 _internal 的 fLocation,只能读取到此。

参考:iOS 识别虚拟定位调研

Fastlane 使用 App Store Connect API Key 解决双重验证问题

现在申请的 AppleId 都是要求必须要有双重验证的,这在处理 CI 问题时通常会引来麻烦,之前的解决方案使用 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORDFASTLANE_SESSION,但 FASTLANE_SESSION 具有时效性,每过一个月就需要更新一次,也不是长期方案。Fastlane 在 2.160.0 版本开始支持 Apple 的 App Store Connect API 功能。App Store Connect API 由苹果提供,需登录 App Store Connect 完成授权问题。使用方法如下:

1、在 这里 创建共享秘钥。

请求权限:

创建秘钥:

这里的 .p8 秘钥文件只能下载一次,注意保存。

2、fastfile 的配置。

可以直接用 app_store_connect_api_key 对象配置,也可以写成 json 供多个 lane 共享,这里推荐使用 json 形式管理,新建一个json文件,配置如下内容:

1
2
3
4
5
6
7
{
"key_id": "D383SF739",
"issuer_id": "6053b7fe-68a8-4acb-89be-165aa6465141",
"key": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHknlhdlYdLu\n-----END PRIVATE KEY-----",
"duration": 1200,
"in_house": false
}

前面三项都是对秘钥文件的描述,可以根据自己的项目进行修改。这里需注意 key 的内容,原始 .p8 文件是带换行的,转成字符串时用 \n 表示换行。注意这里的值为 key,官网写法是 key_content,这是官网的错误,我开始也被坑了,已经有人提出了 issues 19341

基本所有需要登录 app conenct 的命令都包含 api_key_path 这个参数,传入 json 文件路径即可:

1
2
3
lane :release do
pilot(api_key_path: "fastlane/D383SF739.json" )
end

参考:fastlane app-store-connect-api documents

面试解析

整理编辑:师大小海腾

本期面试解析讲解的知识点是 KVC 取值和赋值过程的工作原理

Getter

以下是 valueForKey: 方法的默认实现,给定一个 key 作为输入参数,在消息接收者类中操作,执行以下过程。

  • ① 按照 get<Key><key>is<Key>_<key> 顺序查找方法。

    如果找到就调用取值并执行 ⑤,否则执行 ②;
  • ② 查找 countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes: 命名的方法。

    如果找到第一个和后面两个中的至少一个,则创建一个能够响应所有 NSArray 的方法的集合代理对象(类型为 NSKeyValueArray,继承自 NSArray),并返回该对象。否则执行 ③;
    • 代理对象随后将其接收到的任何 NSArray 消息转换为 countOf<Key>objectIn<Key>AtIndex:<Key>AtIndexes: 消息的组合,并将其发送给 KVC 调用方。如果原始对象还实现了一个名为 get<Key>:range: 的可选方法,则代理对象也会在适当时使用该方法。
  • ③ 查找 countOf<Key>enumeratorOf<Key>memberOf<Key>: 命名的方法。

    如果三个方法都找到,则创建一个能够响应所有 NSSet 的方法的集合代理对象(类型为 NSKeyValueSet,继承自 NSSet),并返回该对象。否则执行④;
    • 代理对象随后将其接收到的任何 NSSet 消息转换为 countOf<Key>enumeratorOf<Key>memberOf<Key>: 消息的组合,并将其发送给 KVC 调用方。
  • ④ 查看消息接收者类的 +accessInstanceVariablesDirectly 方法的返回值(默认返回 YES)。如果返回 YES,就按照 _<key>_is<Key><key>is<Key> 顺序查找成员变量。如果找到就直接取值并执行 ⑤,否则执行 ⑥。如果 +accessInstanceVariablesDirectly 方法返回 NO 也执行 ⑥。
  • ⑤ 如果取到的值是一个对象指针,即获取的是对象,则直接将对象返回。
    • 如果取到的值是一个 NSNumber 支持的数据类型,则将其存储在 NSNumber 实例并返回。
    • 如果取到的值不是一个 NSNumber 支持的数据类型,则转换为 NSValue 对象, 然后返回。
  • ⑥ 调用 valueForUndefinedKey: 方法,该方法抛出异常 NSUnknownKeyException,程序 Crash。这是默认实现,我们可以重写该方法对特定 key 做一些特殊处理。

Setter

以下是 setValue:forKey: 方法的默认实现,给定 keyvalue 作为输入参数,尝试将 KVC 调用方 key 的值设置为 value,执行以下过程。

  • ① 按照 set<Key>:_set<Key>: 顺序查找方法。

    如果找到就调用并将 value 传进去(根据需要进行数据类型转换),否则执行 ②。
  • ② 查看消息接收者类的 +accessInstanceVariablesDirectly 方法的返回值(默认返回 YES)。如果返回 YES,就按照 _<key>_is<Key><key>is<Key> 顺序查找成员变量(同 Getter)。如果找到就将 value 赋值给它(根据需要进行数据类型转换),否则执行 ③。如果 +accessInstanceVariablesDirectly 方法返回 NO 也执行 ③。
  • ③ 调用 setValue:forUndefinedKey: 方法,该方法抛出异常 NSUnknownKeyException,程序 Crash。这是默认实现,我们可以重写该方法对特定 key 做一些特殊处理。

优秀博客

整理编辑:皮拉夫大王在此我是熊大

1、深入浅出 Apple 响应式框架 Combine – 来自 InfoQ:青花瓷的平方

@我是熊大:本文是 Joseph Heck 编写的教程的中文版本,适合新手阅读,学习 Combine。

2、Combine debugging using operators in Swift – 来自博客:avanderlee

@我是熊大:使用 RxSwift 会产生大量的不可读堆栈信息,这也是开发人员放弃 RxSwift 的原因之一,在 Combine 中这一点依旧如此。但好在有一些提示和技巧可以改善,本文就介绍了几种方式。

3、Combine: Getting Started – 来自:raywenderlich

@我是熊大:Swift Combine 的硬核教程,作者利用 UnsplashAPI 带大家实现了一个简易的 App,让我们学习了解如何使用 Combine 的发布者和订阅者来处理事件流、合并多个发布者等。

4、Combine - 介绍、核心概念 – 来自知乎:Talaxy

@皮拉夫大王:提到响应式编程就不得不说 Combine。这篇文章介绍了Combine 的相关概念和用法。包括发布者-订阅者的生命周期、发布者订阅者操作者的概念等等。

5、Apple 官方异步编程框架:Swift Combine 应用 – 来自:Nemocdz’s Blog

@皮拉夫大王:本文通过例子和代码介绍了 Combine 的用法,适合了解 Combine 相关概念和基础的同学阅读。

6、RxSwift to Combine Cheatsheet – 来自 GitHub:CombineCommunity

@皮拉夫大王:RxSwift 与 Combine 的对照关系,如果你想从 RxSwift 过渡到 Combine,可以参考此文章。

学习资料

整理编辑:Mimosa

阮一峰的《C 语言入门教程》

地址:https://wangdoc.com/clang

阮一峰最近新写的《C 语言入门教程》,他对该教程做了一些介绍可以看 这里,这对想重拾 C 语言这一门手艺的读者来说一定是一个巨大的帮助。同时各位读者若发现错误和遗漏,欢迎大家到仓库提交补丁。

Github 漫游指南

地址:http://github.phodal.com/

如果你是一名 Github 新手的话,这本《Github 漫游指南》将会带你漫游 Github 的世界,带你了解 Github 到底是什么,他有什么用,该怎么去使用它。如果你是一名老手了,它也可以带你深入平时可能不会注意的细节,帮助你更加了解这个我们每天都在使用的工具。

工具推荐

整理编辑:brave723

XcodeProjects

地址https://github.com/DKalachniuk/XcodeProjects

软件状态: 免费,开源

软件介绍

日常开发过程中,经常在终端中执行 pod install、pod update、或者 clear derived data 等操作,XcodeProjects 工具执行这些操作,只需要点击两下就能完成,还能为自己的项目自定义 command,很大程度的简化我们的操作步骤,节省开发时间。

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第二十六期

iOS摸鱼周报 第二十五期

iOS摸鱼周报 第二十四期

iOS摸鱼周报 第二十三期

iOS摸鱼周报 第二十六期

iOS摸鱼周报 第二十六期

本期概要

  • 话题:跟熊大聊一下独立开发和音视频开发。
  • Tips:对节流和防抖的再讨论;关于 TestFlight 外部测试的一些介绍。
  • 面试模块:本期解析 KVO 的实现原理。
  • 优秀博客:收录了一些 RxSwift 相关的文章。
  • 学习资料:靛青早期写的 RxSwift 学习教程。
  • 开发工具:公众号文章同步工具 Wechatsync。

本期话题

@zhangferry:本期交流对象是摸鱼周报的另一位编辑:🐻我是熊大。他在音视频方向有很多经验,自己也独立维护了两款应用,我们围绕这两个层面来和他交流一下。

zhangferry:简单介绍下自己吧。

大家好,我是熊大,野生iOS开发者,目前正在做一款海外社交 App,在团队中主要负责 iOS 的架构设计以及性能优化。

zhangferry:你有多款独立开发的应用,能简单介绍下当时独立开发的初衷和现状吗?

独立开发的产品是《今日计划》《imi我的小家》

当时做独立开发的目的有两个:一个是自己有些想法想要实现出来,二是希望能有睡后收入。之前认为独立开发可能需要更多时间投入,后来发现独立开发最首要的问题不是时间,而是运营和验证;如何找到产品定位人群,如何优化 ASO,关键词如何填写,产品留存达到多少才是及格?这些都是初次尝试独立开发容易忽略却不得不面对的挑战。也正因此,我做了个公众号独立开发者基地,分享独立开发遇到的问题。

zhangferry:在你看来要做独立开发需要具备哪些重要技能呢?

以下 5 个方面可能是我们需要的:

1、具备产品思维,能编写需求文档,分析产品数据,摸索产品方向。

2、具备使用 Sketch、Figma、蓝湖的能力。

3、运营推广能力,如何让app能被更多的人知道,如何召回用户

4、具备iOS、Android、小程序等一个或多个的开发能力

5、后端开发能力,其实这个前期可以使用第三方代替

这些都是硬件实例,最关键的还是执行力和创造力,如果你有想法,那就不要等待。

4、你的工作中多涉及音视频技能,能说下音视频开发和普通应用开发有什么区别吗?如果想往这个方面学习,需要注意和关注哪些东西。

在我的工作中,音视频开发主要涉及到AVFoundation、FFmpeg、OpenGL ES、MetalKit等框架。

音视频开发入门会更难一些,需要有图形图像学的基础知识;有时需要编写 C\C++ 代码,很多第三方的音视频库都是 C\C++ 写的,比如常用的 libjpeg-turbo、lame;同时要熟悉CMake工具进行编译等。

推荐学习路线:

1、数字图像的基本知识

2、开源库 GPUImage,AVFoundation + OpenGL ES,2016年时,很多第三方SDK图像处理框架都是基于这个开发的。

3、开源库 GPUImage3,这是AVFoundation + Metal。

4、李明杰老师今年有一个FFmpeg的课程。

我在小专栏开了一个介绍音视频技术的专栏:GPUImage By Metal,大家如果对这类知识感兴趣的话欢迎订阅。另外再送大家一些免费领取资格,名额有限只有十个,点击这里领取。(公众号外链无法跳转,专栏领取链接在下方参考资料中)

5、如何保持学习的热情,能否分享一些你的学习方法。

保持热情的最好办法就是热爱或追求。

1、学习要循序渐进,不要一下学太多,陌生的东西太多会打消积极性。

2、如果遇到几天都无法理解的东西,放一放,发酵几个月后再看。

3、要有目标和实践方案

6、有什么需要借助于摸鱼周报宣传的。

1、希望大家多多关注 SpeedySwift 这个开源项目:https://github.com/Tliens/SpeedySwift ,这是一个用于提效 Swift 开发的仓库,觉得 OK 的话给我个 star 鼓励一下吧。

2、北京连趣科技,寻找一起并肩作战的小伙伴,各个岗位都有需求,简历可以投递到 tliens.jp@gmail.com,我的微信:bear0000001

开发Tips

整理编辑:夏天zhangferry

节流、防抖再讨论

在之前的分享中,我们介绍了函数节流(Throttle)和防抖(Debounce)解析及其OC实现。部分读者会去纠结节流和防抖的概念以至于执拗于其中的区别,想在节流和防抖之间找到一个交接点,通过这个交接点进行区分,其究竟是节流(Throttle)还是防抖(Debounce)。

容易让人理解混乱的还有节流和防抖中的 LeadingTrailing 模式,这里我们试图通过更直白的语言来解释这两个概念的区别。

概念解释

以下是正常模式,当我们移动发生位移修改时,执行函数回调。

这个执行明显太频繁了,所以需要有一种方法来减少执行次数,节流和防抖就是这样的方法。

节流:在一定周期内,比如 200ms , 200ms 只会执行一次函数回调。

防抖:在一定周期内,比如 200ms,任意两个相邻事件间隔超过 200ms,才会执行一次函数调用。

注意上面两个方法都是把原本密集的行为进行了分段处理,但分段就分头和尾。比如每 200ms 触发一次,是第 0ms 还是第 200ms?相邻间隔超过 200ms,第一个事件算不算有效事件呢?这就引来了 Leading 和 Trailing,节流和防抖都有 Leading 和 Trailing 两种模式。

Leading:在时间段的开始触发。

Trailing:在时间段的结尾触发。

备注:leading 和 trailing 是更精确的概念区分,有些框架里并没有显性声明,只是固定为一个较通用的模式。比如 RxSwift, throttle 只有 leading 模式,debounce 只有 trailing 模式。

典型应用场景

通过对比文本输入校验和提供一个模糊查询功能来加深节流和防抖的理解。

在校验输入文本是否符合某种校验规则,我们可以在用户停止输入的 200ms 后进行校验,期间无论用户如果输入 是增加还是删减都不影响,这就是防抖。

而模糊查询则,用户在输入过程中我们每隔 200ms 进行一次模糊匹配避免用户输入过程中查询列表为空,这就是 节流。

拓展

如果你项目中有存在这样的高频调用,可以尝试使用该理念进行优化。

这些文章:彻底弄懂函数防抖和函数节流函数防抖与函数节流Objective-C Message Throttle and Debounce 都会对你理解有所帮助。

关于 TestFlight 外部测试

TestFlight 分为内部和外部测试两种。内部测试需要通过邮件邀请制,对方同意邀请才可以参与到内部测试流程,最多可邀请 100 人。每次上传应用到 AppStore Connect,内部测试人员就会自动收到测试邮件的通知。

外部测试可以通过邮件邀请也可以通过公开链接的形式直接参与测试,链接生成之后就固定不变了,其总是指向当前最新版本。外部测试最多可邀请 10000 人。

与内测不同的是,外测每个版本的首次提交都需要经过苹果的审核。比如应用新版本为 1.0.0,首次提交对应的 build 号为 100,这个 100 的版本无法直接发布到外部测试,需要等待 TestFlight 团队的审核通过。注意这个审核不同于上线审核,AppStore 和 TestFlight 也是两个不同的团队。外测审核条件较宽泛,一般24小时之内会通过。通过之后点击公开连接或者邮件通知就可以下载 100 版本包。后面同属 1.0.0 的其他 build 号版本,无需审核,但需要每次手动发布。(Apple 帮助文档里有提,后续版本还会有基本审核,但遇到的场景都是可以直接发布的。)

采用公开链接的形式是无法看到测试者的信息的,只能查看对应版本的安装次数和崩溃测试。

面试解析

整理编辑:师大小海腾

本期解析 KVO 的实现原理。

Apple 使用了 isa-swizzling 方案来实现 KVO。

注册:

当我们调用 addObserver:forKeyPath:options:context: 方法,为 被观察对象 a 添加 KVO 监听时,系统会在运行时动态创建 a 对象所属类 A 的子类 NSKVONotifying_A,(如果是在Swift工程中,因为命名空间的存在,生成的类名会是NSKVONotifying_ModuleName.A) 并且让 a 对象的 isa 指向这个子类,同时重写父类 A 的 被观察属性 的 setter 方法来达到可以通知所有 观察者对象 的目的。

这个子类的 isa 指针指向它自己的 meta-class 对象,而不是原类的 meta-class 对象。

重写的 setter 方法的 SEL 对应的 IMP 为 Foundation 中的 _NSSetXXXValueAndNotify 函数(XXX 为 Key 的数据类型)。因此,当 被观察对象 的属性发生改变时,会调用 _NSSetXXXValueAndNotify 函数,这个函数中会调用:

  • willChangeValueForKey: 方法
  • 父类 A 的 setter 方法
  • didChangeValueForKey: 方法

监听:

而 willChangeValueForKey: 和 didChangeValueForKey: 方法内部会触发 观察者对象 的监听方法:observeValueForKeyPath:ofObject:change:context:,以此完成 KVO 的监听。

willChangeValueForKey: 和 didChangeValueForKey: 触发监听方法的时机:

  • didChangeValueForKey: 方法会直接触发监听方法
  • NSKeyValueObservingOptionPrior 是分别在值改变前后触发监听方法,即一次修改有两次触发。而这两次触发分别在 willChangeValueForKey: 和 didChangeValueForKey: 的时候进行的。如果注册方法中 options 传入 NSKeyValueObservingOptionPrior,那么可以通过只调用 willChangeValueForKey: 来触发改变前的那次 KVO,可以用于在属性值即将更改前做一些操作。

移除:

在移除 KVO 监听后,被观察对象的 isa 会指回原类 A,但是 NSKVONotifying_A 类并没有销毁,还保存在内存中,不销毁的原因想必大家也很容易理解,其实就是一层缓存,避免动态类的频繁创建/销毁。

重写方法:

NSKVONotifying_A 除了重写 setter 方法外,还重写了 class、dealloc、_isKVOA 这三个方法(可以通过 class_copyMethodList 获得),其中:

  • class:返回父类的 class 对象,目的是为了不让外界知道 KVO 动态生成类的存在,隐藏 KVO 实现(通过此处我们可以知道获取对象所属类的方式最好是使用class方法,而不是isa指针);
  • dealloc:释放 KVO 使用过程中产生的东西;
  • _isKVOA:用来标志它是一个 KVO 的类。

参考:iOS - 关于 KVO 的一些总结

优秀博客

RxSwift

1、RxSwift 中文文档 – 来自RxSwift 中文文档

@我是熊大:其实 RxSwift 的中文文档完善度很高,其目的就是帮助 iOS 开发人员快速上手 RxSwift,其中不仅讲了核心成员使用,还附带了精选的 demo 以及生态架构的相关文章。

2、RxSwift 核心实现原理 – 来自博客:楚权的世界

@我是熊大:泛型和闭包,让 RxSwift 诞生,这篇文章带你还原 RxSwift 的设计现场,深入浅出,帮助你更深入的了解RxSwift 的原理。

3、初识RxSwift及使用教程 – 来自:韩俊强的博客

@皮拉夫大王:RxSwift 是 Swift 函数响应式编程的一个开源库。初次接触的同学可能会提问为什么要用 RxSwift。因此可以看看这篇文章。作为初学者,通过阅读这篇文章感觉 RxSwift 使逻辑离散的代码变的聚合,逻辑更加清晰。当然,RxSwift 不止于此,纸上得来终觉浅,更多的优势可能只有深入使用才会有所体会。

4、RxSwift使用教程大全 – 来自:韩俊强的博客

@皮拉夫大王:RxSwift 的教程大全,罗列了比较多的 RxSwift 使用方法。

5、使用 RxSwift 进行响应式编程 – 来自:AltConf

@zhangferry:这是 AltConf 2016 中的一期讲座内容,整理翻译成了中文。虽然是2016年的内容,但RxSwift的基本概念是不会改变的,这篇内容 Scott Gardner 将带大家走入到响应式编程的世界当中,并告诉大家一个简单的方法来开始学习 RxSwift。

6、RxSwift vs PromiseKit – 来自:靛青DKQing

@zhangferry:如果仅是为了处理回调地狱就选择引入 RxSwift,就有些大材小用了,处理回调地狱用 PromiseKit 就可以。RxSwift 里的回调处理只是附加功能,其真正的思想是响应式,PromiseKit 非响应式框架。响应式是一种面向数据流和变化传播的编程范式,不只是异步的网络请求,像是点击行为,文本框不同的输入都是数据流的一种形式,概念的理解在学习响应式编程中尤为重要。文中通过一个简单的例子,来说明 PromiseKit 不具备流的特性。

学习资料

整理编辑:zhangferry

RxSwift 学习教程

地址:http://t.swift.gg/t/rxswift-course

结合本期优秀博客的内容再推荐一个 RxSwift 学习教程。大家如果仔细看 AltConf 那篇译文的话会注意到里面的译者注:

国内最好的 RxSwift 教程推荐靛青DKQing所撰写的 RxSwift 教程系列,有兴趣的同学可以前往阅读。

由此可见靛青是当时公认的 RxSwift 代表人物,他是国内较早一批接触并深入理解 RxSwift 的人之一,对 RxSwift 在国内的推广起到了很大的帮助。

课程系列的顺序大致是这样:基本的使用 -> 基本的概念 -> 进阶的使用 -> 源码解读。

该课程写于2016年,至今有一段时间了,部分语法可能有变,但不影响我们对概念的理解,仍有一定的参考学习价值。

工具推荐

整理编辑:zhangferry

Wechatsync

地址https://www.wechatsync.com/

软件状态:免费,开源

软件介绍

作为号主通常会将文章发布到多个平台,每个平台都重复地登录、复制、粘贴是一件很麻烦的事。Wechatsync就是这样一款解脱重复工作的神器。它是一款 Chrome 浏览器插件,支持多个平台的文章发布,这需要我们提前登录各个平台获得授权。它会自动识别公众号文章,弹出「同步该文章」按钮,然后点击就可以同步文章到我们授权的平台。

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第二十五期

iOS摸鱼周报 第二十四期

iOS摸鱼周报 第二十三期

iOS摸鱼周报 第二十二期