阿里UC 王文槿 - 《从Observer到Observable:使用Functional+Swift提升复杂iOS项目的可维

我目前是一名在UC工作的iOS开发者。曾经创业过一段时间,期间主要Swift来构建快速移动应用,以及使用Python后端全家桶(redis、mongodb、zmq等)来构建一系列app的后台服务。进入UC之后先后负责夸克浏览器的开发,Weex适配的工作,目前主要负责短视频业务,其中主要包括视频拍摄,OpenGL/Shader,视频编解码之类的工作。 喜欢Swift语言的各种先进特性,2年前加入SwiftGG后一直致力于Swift语言的布道和最佳实践的讨论。其中对利用Swift的函数式特性改进工程实践的方面研究较多,去年的第二届atSwift大会上也分享了如何通过设计一套简单的reactive api来让mvvm写起来更舒服,Swift社区大多数都叫我“莲叔”。主要当时在翻译组里,我的昵称叫小莲 :-D。
展开查看详情

1. 从 Observer 到 Observable 使⽤用 Functional Swift 提升复杂 iOS 项⽬目的可维护性 演讲者/王⽂文槿

2. ⾃自我介绍 • 王⽂文槿 / aaaron7 / 莲叔 • 2012 - 2016:创业,iOS + Py 后端 • 2016 - ⾄至今 :UC,iOS + Weex + ⾳音视频 • 函数式编程爱好者

3. 摘要 • 为什什么需要 Functional thinking? • Functional 经典应⽤用:实现简单的 Observable 系统 • 解决⼀一些实际问题 • 背后隐藏的设计模式 • Q&A

4.为什什么需要 Functional thinking?

5.异常处理理的完备性问题

6. OC时代的异常处理理 - (void)statVideoPlay:(NSString *)videoUrl{ VideoInfo *info = self.videos[videoUrl]; [StatUtility uploadStatisInfo:@{@"video_title":info.videoTitle}]; }

7. OC时代的异常处理理 - (void)statVideoPlay:(NSString *)videoUrl{ assert(videoUrl != nil); VideoInfo *info = self.videos[videoUrl]; [StatUtility uploadStatisInfo:@{@"video_title":info.videoTitle}]; }

8. OC时代的异常处理理 - (void)statVideoPlay:(NSString *)videoUrl{ assert(videoUrl != nil); VideoInfo *info = self.videos[videoUrl]; if (info) { [StatUtility uploadStatisInfo:@{@"video_title":info.videoTitle}]; } }

9. OC时代的异常处理理 - (void)statVideoPlay:(NSString *)videoUrl{ assert(videoUrl != nil); VideoInfo *info = self.videos[videoUrl]; if (info && info.videoTitle) { [StatUtility uploadStatisInfo:@{@"video_title":info.videoTitle}]; } }

10. OC时代的异常处理理 - (void)statVideoPlay:(NSString *)videoUrl{ assert(videoUrl != nil); VideoInfo *info = self.videos[videoUrl]; if (info && info.videoTitle) { [StatUtility uploadStatisInfo:@{@"video_title":info.videoTitle}]; //... //... //... [StatUtility uploadStatisInfo:@{@"video_category":info.videoCategory}]; //... //... } }

11.⼼心累,想哭

12. Swift 时代 func statVideoPlay(videoUrl : String?){ if let url = videoUrl{ let videoInfo = self.videos[url] if let info = videoInfo{ if let title = info.title{ StatUtility.uploadStatisInfo(statInfo: ["videoTitle":title]) } } } }

13.还是⼼心累,还是想哭

14.异常处理理的完备性问题 异常处理理 title : String? [“video_title” : title] 本质 a? f : a -> b

15.异常处理理的完备性问题 异常处理理 title : String? [“video_title” : title] 本质 a? f : a -> b f 也可能失败

16.异常处理理的完备性问题 异常处理理 title : String? [“video_title” : title] 本质 a? f : a -> b? 是否有标准化的可能?

17.Functional Thinking g a? f : a -> b? g: (input : a?, f : (a) -> b?) -> b? if input {return f(input)} else {return nil}

18. 如果有神奇的 g func statVideoPlay(videoUrl : String?){ let info:VideoInfo? = g(input: videoUrl) { self.videos[$0] } let title:String? = g(input: info) { $0.title } _ = g(input: title, f: { (x) -> String? in StatUtility.uploadStatisInfo(statInfo: ["videoTitle":x]) return nil }) } 通过 g,⾃自始⾄至终没有任何判空,但代码是安全的。

19. hmmm,改进⼀一下 把 g 扔到 optional 的 extension ⾥里里 func statVideoPlay(videoUrl : String?){ _ = videoUrl .g { self.videos[$0] } .g { $0.title } .g { (x) -> String? in StatUtility.uploadStatisInfo(statInfo: ["videoTitle" : x]) return nil } } 进⼀一步消除了了中间变量量

20. 这个 g,有点眼熟? extension Optional{ func g<b>(f : (Wrapped) -> b?) -> b?{ return self.flatMap {f($0)} } }

21. 重新认识 flatMap Optional 类型的 flatMap, 本质就是提供了了⼀一个标准化的⽅方式,来实现 Optional Value 到 只接收⾮非 Optional Value 逻辑的绑定

22. Functional Thinking • 通过抽象来更更好的描述问题 • 本质:⾮非确定性 State -> 确定性 State的转换 • 对于同构的场景提取模型 • 普适的模型: (input : a?, f : (a) -> b?) -> b? • 最终通过符合直觉的⽅方式解决问题 • 链式调⽤用,顺序执⾏行行,⽆无需维护⼀一堆中间变量量和分⽀支逻辑

23. Functional 经典应⽤用 ⼀一个简单的 Observable 系统

24. Observable An Observable is an entity that wraps a value and allows to observe the value for changes. PUSH-DRIVEN EVENT MODEL

25.Observable 的⽅方案选择

26. 复杂框架所带来的问题 陡峭的Learning Curve ⽆无法低成本在团队推⼴广 最终因编码⻛风格的⼀一致 性等原则导致难产

27. Observable 的核⼼心 • ⼀一个具备 observe closure 管理理功能的泛型容器器 • ⼏几个操作该容器器的核⼼心⽅方法(subscribleNext/bind/map/filter) • ⼀一般⽤用 Signal 表示,因为 value emit 的⾏行行为很像信号 • 具备⾃自研条件,易易于落地

28. 动动⼿手 public class Signal<a> : NSObject { typealias SignalToken = Int typealias Subscriber = (a) -> Void var subscribers = [SignalToken:Subscriber]() closure 容器器 public private(set) var value : a? let queue = DispatchQueue(label: "com.swift.let.token") init(value : a) { 订阅更更新的接⼝口 self.value = value } 语法糖,直接绑定两个 Signal 的值(type⼀一致) public func subscribeNext(hasInitialValue:Bool = false, subscriber : @escaping (a) -> Void) -> SignalToken public func bind(signal : Signal<a>) -> SignalToken public func update(_ value : a) } 更更新 value,并逐个调⽤用所有 closure

29. Playground let xSignal = Signal(value: 0) let ySignal = Signal(value: 1) _ = xSignal.bind(signal: ySignal) _ = xSignal.subscribeNext { (x) in print("got \(x) in xsignal") } _ = ySignal.subscribeNext(subscriber: { (x) in print("got \(x) in ysignal”) }) xSignal.update(33) got 33 in xsignal got 33 in ysignal