首頁 > 軟體

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

2022-08-01 22:02:08

下文,寫的是 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模組化內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com