另闢蹊徑--極簡Swifty路由

另闢蹊徑--極簡Swifty路由

1. 前言

在組件化通訊方案的設計之初,儘管咱們是純Swift的組件化,我也一直難逃窠臼的想用註冊(不管是註冊協議仍是註冊URL)的方式來解決問題,或者採用CTMediatorTarget-Action方式,具體幾種組件化方案的實現與利弊見文章:iOS 組件化 —— 路由設計思路分析🔥🔥 git

2. 彎路(經驗)

最開始設計的組件化解決方案,由於做爲一個電商項目(纔不是這個緣由),因此我僅採用了URL註冊的方式,我一直力求的它應該具有的特性以下:github

  1. 組件解耦
  2. 能夠方便的跳轉到任何已註冊的頁面
  3. 不要硬編碼
  4. 模塊(組件)可獲取App生命週期
  5. 模塊(組件)對URL的註冊不須要手動調用
  6. 可能的話,實現3端同樣的跳轉邏輯
  7. 支持動態下發,如此即支持簡單的熱修復

其實34,已經跳出了組件路由設計的範疇。確切的說應該是模塊解耦的範疇。swift

2.1 實現

我把router設計成單例,目的是保證其持有的["String": func]的字典的惟一肯定性,其中func做爲閉包形式,傳入參數返回ViewController。那麼註冊環節就顯而易見的爲register(_ key: String, value: func),調用就會根據key,執行閉包func返回ViewController,以此解決1api

在對其中key的設計使用上,由於註冊方與調用方都會用到,因此咱們將其寫在公共組件內,又由於key會附帶傳一些簡單的值,因此我又加了一個方法對key進行賦值處理操做,目的是爲了保證第3條。bash

在模塊解耦問題的處理上,我設計了一個繼承AppDelegate方法的協議,暫稱爲AppLifeCycle,同時添加了一些方法用於初始化註冊操做。再又設計了一個腳本,能夠將遵循AppLifeCycle的實例生成一個plist文件,這樣在App啓動時候,一個方法調用就實現全部路由註冊功能,以此解決45服務器

對於第7點,在設計之初由於公司尚未服務器端動態下發的功能,因此又加了中間件作fallBack處理(固然也都沒用上)。閉包

3. Swifty組件化

雖然原有的路由設計與模塊解耦方案已經支持現階段業務需求,可是使用上過於複雜,不夠友好,並且也沒用上多少swift的特性,反而這些實現,若是用Objective-c實現起來,會更方便一些,好比腳本生成plistOC均可以不須要。app

最近有同事在對路由作抽離精簡,僅抽出router部分,主要在接口設計上進行優化。我在看完後對一些功能點提了優化可能,後續一直的交流溝經過程中,忽然想到,我能夠用Protocol Witness Table來實現這個路由啊!ide

其原理是: swift會維護一個Protocol Witness Table, 此表會保存實現了protocol協議的方法的指針地址,當咱們調用方法時,是經過獲取對象的內存地址和方法的位移去查找的。組件化

因此咱們能夠用一個協議定義入參,一個協議定義實現,同一個Enum(建議使用的)去實現,便可實現功能。

這種方式相似於target-action,無需註冊,接口約定,還具備其餘一些優勢:

  1. api接口及其簡單,上手難度0
  2. 接口能夠統一在一個庫內,須要的支持庫也變少了
  3. 無硬編碼

那麼如此,咱們的路由設計的核心代碼,以下:

public protocol MediatorTargetType {} // 用於接口定義,約束接口

public protocol MediatorSourceType {  // 用於枚舉實現
    var viewController: UIViewController? { get }
}

複製代碼

target須要遵循的協議就這麼些。

mediator須要遵循的協議與實現:

public protocol SwiftyMediatorType {
    func viewController(of target: MediatorTargetType) -> UIViewController?
}

extension SwiftyMediator: SwiftyMediatorType {
    public func viewController(of target: MediatorTargetType) -> UIViewController? {
        guard let t = target as? MediatorSourceType else {
            print("MEDIATOR WARNINIG: \(target) does not conform to MediatorSourceType")
            return nil
        }
        guard let viewController = t.viewController else { return nil }
        return viewController
    }
}

複製代碼

以上便是核心代碼。 經過接口收束,須要傳入MediatorTargetType,嘗試轉換成目標類型MediatorSourceType,以此返回viewController

4. 使用

在使用中,咱們仍然須要一個公共的組件庫,對路由目標進行定義。假設這個庫叫MediatorTargets,其內容以下:

public enum ModuleAMediatorType: MediatorTargetType {
    case home(title: String)
    case personal(color: UIColor)
}

複製代碼

而後在咱們寫的模塊庫中,此時咱們是路由目標的提供方,如3中核心代碼所示,咱們須要 讓ModuleAMediatorType再遵循協議MediatorSourceType,以此支持ModuleAMediatorType返回viewController

import SwiftyMediator
import MediatorTargets

extension ModuleAMediatorType: MediatorSourceType {
    public var viewController: UIViewController? {
        switch self {
        case .home(let title):
            let vc = UIViewController()
            vc.view.backgroundColor = .green 
            vc.title = title
            return vc
            
        case .personal(let color):
            let vc = PresentedViewController()
            vc.view.backgroundColor = color
            vc.title = "Presented"
            return vc
        }
    }
}
複製代碼

那麼實現方的調用,只須要:

import MediatorTargets
import SwiftyMediator

let vc = Mediator.viewController(of: ModuleAMediatorType.home(title: "Home"))

複製代碼

嗯,就是這麼簡單。

若是隻作簡單的模塊間通訊,到這是足夠的了, 主要的就是2個協議。

5. 路由化及動態化

固然,有些時候咱們須要作一些動態化的路由策略,好比作一下動態路由下發。我也對SwiftyMediator作了一些接口適配,使用方式以下:

  1. 先將須要路由動態化的已遵循MediatorTargetType的協議ModuleAMediatorType,繼續遵循協議MediatorRoutable,並實現協議:
extension ModuleAMediatorType: MediatorRoutable {
    public init?(url: URLConvertible) {
        switch url.pattern {
        case "sy://push":
            self = .push(title: url.queryParameters["title"] ?? "default")
        case "sy://present":
            self = .present(color: UIColor.red)
        default:
            return nil 
        }
    }
}
複製代碼
  1. 調用SwiftyMediatorfunc register(_ targetType: MediatorRoutable.Type),註冊ModuleAMediatorType
  2. 可選:如須要替換某個路由指向,調用SwiftyMediatorfunc replace(url: URLConvertible, with replacer: URLConvertible)方法便可
  3. 使用url的方式作路由:Mediator.push("sy://push?title=hahaha")

當須要實現動態化的時候,不可避免的要去註冊,並且要實現協議中的枚舉初始化。雖然有些不便,可是在總體的接口收束度上仍是挺不錯的。相比較註冊URL的方式來講,這些註冊就少不少了。

6. 模塊獲取App生命週期

鑑於目前系統有比較全面的生命週期通知定義,並且不須要在模塊中大量註冊url,因此這部分功能目前在考慮是否須要添加。


雖然代碼很簡單,實現也很簡單,可是跳出慣性思惟,再去嘗試一樣須要不少思考。

SwiftyMediator,歡迎star。

其餘使用方法見:

demo

參考資料:

WWDC - Protocol Witness Table

swift的witness table

URLNavigator

相關文章
相關標籤/搜索