iOS Lotusoot模块化
dengjiangszhan 人气:0下文,写的是 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) } } }
加载全部内容