Flux架構思想在度咔App中的實踐

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"導讀","attrs":{}},{"type":"text","text":":爲了應對視頻編輯類工具應用複雜的交互,度咔iOS借鑑了Flux架構模式的設計思想,參考有向無環圖的拓撲概念,將事件進行集中化管理,從開發體驗上實現了舒適清爽、容易駕馭的“單向流”模式;在這種調度模式下,事件的變化和追蹤變得清晰可預測,並且顯著的增加了業務的可擴展性。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"全文6882字,預計閱讀時間18分鐘。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一、架構背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"視頻編輯工具類應用往往交互複雜,大部分操作是在同一個主界面上進行,而這個界面同時存在較多的視圖區域(預覽區、軸區、undo redo、操作面板等等),每個區域既要接收用戶手勢,又要跟隨用戶操作聯動更新狀態。同時除支持主場景編輯功能外,還要同時支持其他特色功能,比如度咔的通用編輯、快速剪輯、主題模板等,都需要使用預覽和編輯功能;於是對架構的可擴展和可複用能力自然有了很高的要求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過調研,度咔iOS最終借鑑了Flux架構模式的設計思想,參考有向無環圖的拓撲概念,將事件進行集中化管理,從開發體驗上實現了舒適清爽、容易駕馭的“單向流”模式;在這種調度模式下,事件的變化和追蹤變得清晰可預測,並且顯著的增加了業務的可擴展性。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"二、播放預覽複用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"度咔通用編輯以及很多衍生工具、功能都需要依賴於預覽、素材編輯這一類基礎能力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如下列這些功能都依賴於同一套預覽播放邏輯,需要將這些基礎能力抽象爲一個base控制器。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/da/da13d8b8c3653cca22013ed35555cade.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"baseVC結構爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/75/75413f07d458fb45fbb604afea828de2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"三、功能模塊複用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"預覽播放複用的問題解決了,如何在這套邏輯上添加各樣的素材編輯功能,比如貼紙、文字、濾鏡等功能,並且使這些功能與VC解耦,最終達到複用的目的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終我們使用插拔式設計理念,把每一個子功能抽象成一個plugin,採用直接調用依賴層的方式把controller、view、timeline、streamingContext、liveWindow 這寫90%場景下會用到的屬性通過weak直接賦值給plugin。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"protocol BDTZEditPlugin: NSObjectProtocol {\n // 組織控制器\n var editViewController: BDTZEditViewController? { get set }\n // 所有添加到控制器View上的控件 加到這個View上,解決層級問題\n var mainView: BDTZEditLevelView? { get set }\n // 編輯場景的時間軸實體,由軌道組成,可以有多個視頻軌道和音頻軌道,由視頻軌道決定長度\n var timeline: Timeline? { get set }\n // 流媒體上下文 包含時間線、預覽窗口、採集、資源包管理等相關信息集合的對象\n var streamingContext: StreamingContext? { get set }\n // 視頻預覽窗口控件\n var liveWindow: LiveWindow? { get set }\n\n /// 插件初始化\n func pluginDidLoad()\n\n /// 插件卸載\n func pluginDidUnload()\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只要實現這個協議,並且通過調用baseVC的add:方法添加plugin後,那麼相應的plugin就會拿到對應的屬性進行調用,避免使用單例或者通過層層回調到VC去處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" func addPlugin(_ plugin: BDTZEditPlugin) {\n\n plugin.pluginWillLoad()\n\n plugin.editViewController = self\n\n plugin.mainView = self.view\n\n plugin.liveWindow = liveWindow\n\n plugin.streamingContext = streamingContext\n\n plugin.timeline = timeline\n\n if plugin.conforms(to: BDTZEditViewControllerDelegate.self) {\n pluginDispatcher.add(subscriber: plugin as! BDTZEditViewControllerDelegate)\n }\n plugin.pluginDidLoad()\n }\n\n func removePugin(_ plugin: BDTZEditPlugin) {\n\n plugin.pluginWillUnload()\n\n plugin.editViewController = nil\n\n plugin.mainView = nil\n\n plugin.liveWindow = nil\n\n plugin.streamingContext = nil\n\n plugin.timeline = nil\n\n if plugin.conforms(to: BDTZEditViewControllerDelegate.self) {\n pluginDispatcher.remove(subscriber: plugin as! BDTZEditViewControllerDelegate)\n }\n plugin.pluginDidUnload()\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"plugin是具體功能和VC之間的一箇中間層,可以接受VC的生命週期事件、預覽播放事件、拿到VC中的關鍵對象、調用VC的內部所有public接口能力。作爲插在VC上的一個獨立子功能單元,具有編輯能力、素材能力、網絡UI交互等能力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"plugin分爲service層和UI層,同時在設計之初,基於該架構的plugin不僅僅能在度咔app內使用,廠內其他app僅需要極少工作量就能立即接入plugin。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/63/63fcb50b672e93b44b7b8ac4578c1d86.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有功能能分散到插件中,按需組裝和複用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/12/12e1b8841ed7f3ff3856c1a2838427bc.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時可以對外輸出的不僅僅單個plugin、還是可以是多個plugin的組合。以封面功能爲例,封面編輯是一個以coverVC爲組織的控制器,它包含多個plugin,比如已存在的文字plugin和貼紙plugin;coverVC除了作爲獨立功能應用之外,把它包裝成一個封面plugin只需少量數據對接代碼(上圖的通用剪輯數據對接plugin)就可以集成到通用剪輯VC,像堆樂高積木一樣進行拼裝組合。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"四、事件狀態管理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"編輯工具app因交互的複雜性非常依賴於狀態更新,通常來說在iOS開發中通知對象狀態變化一般採用以下幾種方式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Delegate","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"KVO","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"NotificationCenter","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Block","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這四種方式都可以管理狀態的變化,但是都存在一些問題。Delegate和Block,往往會在組件之間創建強依賴關係;KVO 和 Notifications,會創建不可見的依賴項,如果某些重要消息被移除或更改,也很難被發現,從而降低應用穩定性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"即使是蘋果的MVC模式,也只提倡數據層及其表示層的分離,沒有提供任何工具代碼、指導架構。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4.1 爲什麼選擇Flux架構模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"於是我們借鑑Flux架構模式的思想。Flux 是一種非常輕量級的架構模式,Facebook 將其用於客戶端 Web 應用程序,用於避開MVC,支持單向數據流(後面也是列舉的前端的mvc數據流向圖)。核心思想是中心化控制,它讓所有的請求與改變都只能通過 action 發出,統一 由 dispatcher 來分配。好處是 View 可以保持高度簡潔,它不需要關心太多的邏輯,只需要關心傳入的數據。中心化還控制了所有數據,發生問題時可以方便查詢定位。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dispatcher:處理事件分發,維持 Store 之間的依賴關係","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Store:負責存儲數據和處理數據相關邏輯","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Action:觸發 Dispatcher","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"View:視圖,負責顯示用戶界","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ec/ec6475ddeaf32f746bed7e558c4de973.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過上圖可以看出來,Flux 的特點就是單向數據流:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"用戶在 View 層發起一個 Action 對象給 D ispatcher","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Dispatcher 接收到 Action 並要求 Store 做相應的更改","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"Store 做出相對應更新,然後發出一個 changeEvent","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"View 接收到 changeEvent 事件後,更新頁面","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基本的MVC數據流","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e1/e19cb28e8eeeb96dd03ae4e715735af3.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"複雜的MVC數據","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/04/04a1119a8802ef2629517a4655c42c2d.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單的Flux數據流","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ec/ec6475ddeaf32f746bed7e558c4de973.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"複雜Flux數據流","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/72/721d6738408a4c7065f06a369f2a8eeb.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比MVC模式,Flux多出了更多的箭頭跟圖標,但是有個關鍵性的差別是:所有的箭頭都指向一個方向,在整個系統中形成一個事件傳遞鏈。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4.2","attrs":{}},{"type":"text","text":" ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"應用Flux思想來實現狀態管理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"狀態分爲兩種:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以組織控制器發出的事件產生狀態變化,比如:控制器的生命週期ViewDidLoad()等等、基礎編輯預覽能力的回調,例如seek、progress、playState變化等等","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"各個組件的之間事件傳遞產生的狀態變化,下圖中plugin協議抽象來描述上圖中的Store作用","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1d/1db1fe01aa9a8f00991bc6af77c738e1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"控制器持有EventDispatch能力的對象dispatcher,並通過這個dispatcher傳遞事件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Dispatcher","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"class WeakProxy: Equatable {\n\n weak var value: AnyObject?\n init(value: AnyObject) {\n self.value = value\n }\n\n static func == (lhs: WeakProxy, rhs: WeakProxy) -> Bool {\n return lhs.value === rhs.value\n }\n}\n\nopen class BDTZActionDispatcher: NSObject {\n\n fileprivate var subscribers = [WeakProxy]()\n\n public func add(subscriber: T) {\n guard !subscribers.contains(WeakProxy(value: subscriber as AnyObject)) else {\n return\n }\n subscribers.append(WeakProxy(value: subscriber as AnyObject))\n }\n\n public func remove(subscriber: T) {\n let weak = WeakProxy(value: subscriber as AnyObject)\n if let index = subscribers.firstIndex(of: weak) {\n subscribers.remove(at: index)\n }\n }\n\n public func contains(subscriber: T) -> Bool {\n var res: Bool = false\n res = subscribers.contains(WeakProxy(value: subscriber as AnyObject))\n return res\n }\n\n public func dispatch(_ invocation: @escaping(T) -> ()) {\n clearNil()\n subscribers.forEach {\n if let subscriber = $0.value as? T {\n invocation(subscriber)\n }\n }\n }\n\n\n private func clearNil() {\n subscribers = subscribers.filter({ $0.value != nil})\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過泛型的多重代理方式把事件分發給subscribers內部的對象(上面代碼塊中的 addPlugin:內部添加subscribers),當然也可以通過註冊Block的方法去實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Dispatcher實例","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聲明一個protocol 繼承要分發的能力","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@objc protocol BDTZEditViewControllerDelegate: BDTZEditViewLifeCycleDelegate, StreamingContextDelegate, BDTZEditActionSubscriber {\n// BDTZEditViewLifeCycleDelegate 控制器聲明週期\n// StreamingContextDelegate 預覽編輯能力回調\n// BDTZEditActionSubscriber plugin之間的通訊協議\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"控制器事件分發","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public class BDTZEditViewController: UIViewController {\n// 實例化的 BDTZEditViewControllerDelegate\nvar pluginDispatcher = BDTZEditViewControllerDelegateImp()\n public override func viewDidAppear(_ animated: Bool) {\n super.viewDidAppear(animated)\n pluginDispatcher.dispatch { subscriber in\n subscriber.editViewControllerViewDidAppear?()\n }\n }\n\n public override func viewDidLoad() {\n super.viewDidLoad()\n /***省略部分代碼**/\n setupPlugins()\n //放最後調用\n pluginDispatcher.dispatch { subscriber in\n subscriber.editViewControllerViewDidLoad?()\n }\n }\n /***...**/\n /// seek進度回調\n func didSeekingTimelinePosition(_ timeline: Timeline!, position: Int64) {\n pluginDispatcher.dispatch { subscriber in\n subscriber.didSeekingTimelinePosition?(timeline, position: position)\n }\n }\n /***...**/\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"plugin之間事件傳遞","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"plugin之間的事件傳遞就要用到上面的BDTZEditActionSubscriber協議了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@objc protocol BDTZEditAction {\n}\n@objc protocol BDTZEditActionSubscriber {\n @objc optional func update(action: BDTZEditAction)\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"BDTZEditAction 是一個空協議,可以是任何類繼承它來描述想要傳遞的任何信息。結合編輯工具的特點(雖然交互複雜但是素材類型和操作都是有限的)只需要少量的action就能描述所有狀態。目前我們使用選中action、各種素材action、面板起落action、前進回退action等等這些事件來描述素材的添加、刪除、移動、剪裁、保存草稿一些列的操作。我們以選中action(選中某個片段的事件)舉例:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當APlugin 發出了一個選中事件,BPlugin、CPlugin等等都會收到這個事件,從而做出相應的狀態改變。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//APlugin\nfunc sendAction(model: Any?) { \n let action = BDTZClipSeleteAction.init(event: .selected, type: .sticker, actionTarget: model)\n editViewController?.pluginDispatcher.dispatch({ subscriber in\n subscriber.update?(action: action)\n })\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//BPlugin\nextension BDTZTrackPlugin: BDTZEditActionSubscriber {\n func update(action: BDTZEditAction) {\n if let action = action as? BDTZClipSeleteAction {\n handleSelectActionDoSomething()\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當預覽區的貼紙被選中,那麼軸區也會隨之被選中,底部區域也要切換成三級菜單。**一個action被派發以後,所有plugin都會收到它,對此action感興趣的plugin會做出相應的狀態變化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"**","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e6/e6899df2424a0fbbe8079bf87e9deaba.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"五、總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"iOS也有參照flux思想設計的ReSwift框架,但是如果使用純Flux模式來開發,缺點也非常明顯:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"層級太多,極易產生大量的冗餘代碼。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"老代碼移植工作量巨大。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對我們來說採用Flux 模式設計理念比某個特定的實現框架更重要,我們根據度咔業務的特點只是取其思想使用單層級結構,用來管理ViewController與Plugin抽象之間的關係和事件傳遞,而沒有把View也加到層級中去,plugin內部可以使用MVC、MVVM等任何架構,只需要把通訊方式統一。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面只是使用簡單的例子介紹了編輯工具在Flux思想上的應用。但是在實際使用中還應該考慮:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"UI層級遮蓋問題:插件中的某個View需要加到控制器View上,會造成控件層級遮蓋問題。上面代碼中的BDTZEditLevelView就是爲了解決這個問題。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"多線程問題:在開發中我們難免大量的線程異步處理任務,我們必須規定插件通訊之間的線程,Dispatcher內部也應該有線程管理的代碼。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"plugin依賴關係問題:Dispatcher還要維持plugin之間的依賴關係,比如一個action要APlugin先處理修改某些數據或者狀態後,BPlugin再處理,可以採用加標等方式解決。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"action膨脹問題:相對於API直接調用的方式,監聽action雖然寫更少的代碼,但是容易造成action無限增多的情況,所以在定義action要考慮可擴展和結構化。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"參考鏈接:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[1]http://reswift.github.io/ReSwift/master/getting-started-guide.html","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[2]https://facebook.github.io/flux/","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[3]https://redux.js.org","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[4]http://blog.benjamin-encz.de/post/real-world-flux-ios/?utm_source=swifting.io&utm_medium=web&utm_campaign=blog%20post","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"推薦閱讀:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=Mzg5MjU0NTI5OQ==&mid=2247503500&idx=1&sn=f84fd6f9e62feeb7b91dfd6f1f0d145d&chksm=c03efef0f74977e66f26f287912dc37c1f3cb0124c57483205345ad7645ea59bc3518e1c82e2&scene=21#wechat_redirect","title":"","type":null},"content":[{"type":"text","text":"|","attrs":{}}]},{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=Mzg5MjU0NTI5OQ==&mid=2247504629&idx=1&sn=fd02d4e11bdaea0c800cd2d2f1ec9c1e&chksm=c03ee289f7496b9f2270a4bde6029d0ddcdfa170feecbe8b87a5acee2d1f82bda9ebca11ac10&scene=21#wechat_redirect","title":"","type":null},"content":[{"type":"text","text":"iOS 崩潰日誌在線符號化實踐","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=Mzg5MjU0NTI5OQ==&mid=2247503928&idx=1&sn=a595da431aad386e4210f886cbcddaca&chksm=c03ee044f7496952e70788ab0ba71d6acaaff6de4162d4fa3d3e872cfcbb4b34335b95f597cf&scene=21#wechat_redirect","title":"","type":null},"content":[{"type":"text","text":"|百度商業託管頁系統高可用建設方法和實踐","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=Mzg5MjU0NTI5OQ==&mid=2247503597&idx=1&sn=a9b60d17f310dc3b5ccf29d067d66111&chksm=c03efe91f74977872cc380f48edd6c97e7915bc2fd99c49ca9edfdbaf2f4d9484e40d05b5b86&scene=21#wechat_redirect","title":"","type":null},"content":[{"type":"text","text":"|AI 在視頻領域運用—彈幕穿人","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"---------- END ----------","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"百度 Geek 說","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"百度官方技術公衆號上線啦!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"技術乾貨 · 行業資訊 · 線上沙龍 · 行業大會","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"招聘信息 · 內推信息 · 技術書籍 · 百度周邊","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎各位同學關注","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章