iOS Lotusoot模塊化工具應用的動態思路

下文,寫的是 Swift 依賴

OC 庫,沒有命名空間

組件化的要點-約定

個人覺得

例如,URL 路由的註冊,就是把約定的信息,傳過去。作為服務。

Lotusoot 包含服務調用,短鏈的註冊與調用

下面著重講服務調用,短鏈略

場景

project 有兩個依賴 A (協議) 和 B (協議的實現者,提供服務)

project 引用 A,知道瞭協議信息

project 不引用 B , project 對 B 一無所知

這樣 project 把 B 去掉,編譯的更快

B 依賴 A, 引用 A, 實現 A 的協議,提供服務

調用服務

        // 拿到 key
        let lotus = s(AccountLotus.self)
        // kv 取得提供服務的實例
        let accountModule: AccountLotus = LotusootCoordinator.lotusoot(lotus: lotus) as! AccountLotus
        // 調用服務
        accountModule.login(username: "zhoulingyu", password: "wow") { (error) in
            print(error ?? "1234")
        }

第 3 步,調用服務很簡單

第 2 步,挺精彩,充分使用瞭 Swift 編譯時的靜態特性

協議的方法,編譯時確定瞭

需要幾個參數,啥類型的,一般都可以顯式使用

不用看到一個參數字典,啊,這是啥

// 第 2 步。從下面取
// 鍵值對,值是提供服務的對象
var lotusootMap: Dictionary = Dictionary<String, Any>()

第 1 步, 拿到鍵

這裡把協議名,作為 key

// 使用泛型,取其描述
// 協議,轉協議名
public extension String {
    init<Subject>(_ instance: Subject) {
        self.init(describing: instance)
    }
}
/// 通過 Subject 快速獲取字符串
public func s<Subject>(_ instance: Subject) -> String {
    return String(instance)
}

註冊服務

1, Project 沒有 import B ( 提供服務 ), 怎麼使用 B 的功能?

public static func registerAll(serviceMap: Dictionary<String, String>) {
        for (lotus, lotusootName) in serviceMap {
            // lotus, 協議名
            // lotusootName, 包名.類名
            let classStringName = lotusootName
            // 反射,產生類
            // 提供服務的類,一定是 NSObject 的子類,擁有 init 方法 ( 這是個約定 )
            let classType = NSClassFromString(classStringName) as? NSObject.Type
            if let type = classType {
                // 產生對應的實例,強轉為遵守協議的 ,即可
                let lotusoot = type.init()
                register(lotusoot: lotusoot, lotusName: lotus)
            }
        }
    }

2, 這裡使用 python 腳本註冊,編譯的時候拿到信息 協議名:包名.類名

通過約定, 標記

// @NameSpace(ZLYAccountModule)
// @Lotusoot(AccountLotusoot)
// @Lotus(AccountLotus)
class AccountLotusoot: NSObject, AccountLotus {}

python 腳本找出標記,整合,放入 plist 文件中

1, 腳本入口

lotusootSuffix = 'Lotusoot'
length = len(sys.argv)
if length != 3 and length != 4:
    print 'parameter error'
    os._exit(1)
if length == 4:
    lotusootSuffix = sys.argv[3]
    lotusootFiles = findLotusoots(scanPath, lotusootSuffix + '.swift')
else:
    // 走這裡
    lotusootFiles = findAmbiguityLotusoots(scanPath)

翻閱每一個 swift 文件

def findAmbiguityLotusoots(path):
    list = []
    for root, subFolders, files in os.walk(path):
        # Ignore 'Target Support Files' and 'Pods.xcodeproj'
         // 不需要處理的,不處理
        if 'Target Support Files' in subFolders:
            subFolders.remove('Target Support Files')
        // 不需要處理的,略
        if 'Pods.xcodeproj' in subFolders:
            subFolders.remove('Pods.xcodeproj')
        // 每一個文件
        for f in files:
             // 每一個 Swift 文件
            if f.endswith('.swift'):
                // 獲取標記的配置
                tup = getLotusootConfig(os.path.join(root, f))
                if tup[0] and tup[1] and tup[2]:
                    // 三者都滿足,把文件路徑,給添加瞭
                    list.append(f)
    return list

掃描每一行,

獲取配置,上面看到的包名,命名空間

@NameSpace(ZLYAccountModule)

上面看到的類名

@Lotusoot(AccountLotusoot)

上面看到的 key ( 協議名 )

@Lotus(AccountLotus)

def getLotusootConfig(file):
    lotus = ''
    lotusoot = ''
    namespace = ''
    // 翻閱,文件的每一行
    for line in open(file):
        // 上面看到的 key
        if getLotus(line):
            lotus = getLotus(line)
         // 上面看到的類名
        if getLotusoot(line):
            lotusoot = getLotusoot(line)
        // 上面看到的包名,命名空間
        if getNameSpace(line):
            namespace = getNameSpace(line)
    return (lotus, lotusoot, namespace)

… 還有好多,

邏輯是獲取配置,寫入一個 plist

運行的時候,啟動

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        LotusootCoordinator.registerAll()
        return true
    }

註冊就是,讀取剛才寫入的 plist, 作為 [協議名 : 包名.類名 ] 的字典,

@objc public static func registerAll() {
        let lotusPlistPath = Bundle.main.path(forResource: "Lotusoot", ofType: "plist")
        if let lotusPlistPath = lotusPlistPath {
            let map = NSDictionary(contentsOfFile: lotusPlistPath)
            registerAll(serviceMap:  map as! Dictionary<String, String>)
        }
    }

與上文,相呼應

動態思路

進入動態思路, 動態地註冊 KV ( 協議名: 服務庫名.類名)

上文有一個印象,知道場景,即可

寫文,當寫長,

怎樣體現我,10 年工作經驗?

上文沒有,堅持湊字數

1,project 拿到 key (協議名) ,可以的

2,project 拿到所有的依賴信息

通過 MachO 可以

3,project 拿到服務類的名稱

確保 module B 的類名 = key ( 協議 ) + Cls

MachO 拿到所有依賴庫的名稱, 每一個 + “.” + key ( 協議 ) + Cls= MachO 拿到所有依賴庫的名稱, 每一個 + “.” + module B 的類名

然後,看能不能實例化,

能夠實例化,就 OK

試瞭一圈,不能夠實例化,就報錯

可能依賴 B, 沒有添加

可能依賴 B 的類名,寫錯瞭

project 拿到服務類的名稱, 優雅的

確保 module B 的類名 = key ( 協議 ) + Cls,

硬編碼,是不好的

依賴 A ( 放協議的 ), 添加一個協議

public protocol Maid{
    var name: String{ get }
}

module B 裡面,必須有一個叫 key (協議) + C 的類

該類,遵守 Maid 協議。

通過協議屬性,返回 B 中服務類的名稱

class AccountLotusC: NSObject, Maid{
    var name: String{
        return String(reflecting: AccountLotusoot.self)
    }
}

這個過程,與上文模塊化利用協議的設計,比較一致

約定是,實現協議的服務模塊,

一定有一個 key + C 的類

提供服務類的名稱

硬編碼,比較輕微

代碼實現

1、MachO 獲取命名空間

import MachO
lazy var moduleNames: [String] = { () -> [String] in
        // 找到 project 名稱,一會去除
        let mainNameTmp = NSStringFromClass(LotusootCoordinator.self)
        guard let mainName = mainNameTmp.components(separatedBy: ".").first else{
            fatalError("emptyMainProject")
        }
        var result = [String]()
        let cnt = _dyld_image_count()
        // 處理所有的包,系統的,用戶的
         for i in 0..<cnt{
             if let tmp = _dyld_get_image_name(i){
                 let name = String(validatingUTF8: tmp)
                 // 系統的,不用管
                 if let candidate = name, candidate.hasPrefix("/Users"){
                     if let tmp = candidate.components(separatedBy: "/").last{
                         // 去除 project 的
                         if tmp != mainName{
                             // 拿到用戶依賴
                             result.append(tmp)
                         }
                     }
                 }
             }
         }
         return result
    }()

以上,模擬器 OK, 真機沒試過 ( 手頭沒開發證書 )

2、包名+類名的驗證

@objc public static func lotusoot(lotus: String) -> Any? {
        // 已經緩存瞭
        if let val = sharedInstance.lotusootMap[lotus]{
            return val
        }
        else{
            var i = 0
            let names = LotusootCoordinator.sharedInstance.moduleNames
            let cnt = names.count
            // 遍歷,用戶包
            while i < cnt{
                // 按照約定,嘗試制造助手類
                let classType = NSClassFromString(names[i] + "." + lotus + "C") as? NSObject.Type
                if let type = classType {
                    // 實例化,助手類
                    let assist = type.init()
                    if let maid = assist as? Maid{
                         // 拿到 module B 的服務類的名稱
                        let classType = NSClassFromString(maid.name) as? NSObject.Type
                        if let type = classType {
                            // 將 module B 的服務類,實例化
                            let lotusoot = type.init()
                            register(lotusoot: lotusoot, lotusName: lotus)
                        }
                        // 默認是,一個 module 一個服務類,
                        // 排除掉,使用過的用戶類
                        LotusootCoordinator.sharedInstance.moduleNames.remove(at: i)
                        break
                    }
                }
                i+=1
            }
            if let val = sharedInstance.lotusootMap[lotus]{
                return val
            }
            else{
                fatalError("name Module of" + lotus)
            }
        }
    }

GitHub repo

到此這篇關於iOS Lotusoot模塊化工具應用的動態思路的文章就介紹到這瞭,更多相關iOS Lotusoot模塊化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: