欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

iOS Lotusoot模块化工具应用的动态思路,

来源: 开发者 投稿于  被查看 25787 次 评论:65

iOS Lotusoot模块化工具应用的动态思路,


目录
  • 组件化的要点-约定
    • 场景
    • 调用服务
    • 注册服务
  • 动态思路
    • 代码实现
      • 1、MachO 获取命名空间
      • 2、包名+类名的验证

    下文,写的是 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模块化内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

    您可能感兴趣的文章:
    • iOS模块化开发浅析
    • iOS 模块化之JLRoute路由示例
    • iOS中关于模块化开发解决方案(纯干货)

    用户评论