下文,写的是 Swift 依赖
OC 库,没有命名空间
组件化的要点-约定
个人觉得
例如,URL 路由的注册,就是把约定的信息,传过去。作为服务。
Lotusoot 包含服务调用,短链的注册与调用
下面着重讲服务调用,短链略
场景
project 有两个依赖 A (协议) 和 B (协议的实现者,提供服务)
project 引用 A,知道了协议信息
project 不引用 B , project 对 B 一无所知
这样 project 把 B 去掉,编译的更快
B 依赖 A, 引用 A, 实现 A 的协议,提供服务
调用服务
1
2
3
4
5
6
7
8
|
// 拿到 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 编译时的静态特性
协议的方法,编译时确定了
需要几个参数,啥类型的,一般都可以显式使用
不用看到一个参数字典,啊,这是啥
1
2
3
|
// 第 2 步。从下面取
// 键值对,值是提供服务的对象
var lotusootMap: Dictionary = Dictionary<String, Any>()
|
第 1 步, 拿到键
这里把协议名,作为 key
1
2
3
4
5
6
7
8
9
10
11
|
// 使用泛型,取其描述
// 协议,转协议名
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 的功能?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 脚本注册,编译的时候拿到信息 协议名:包名.类名
通过约定, 标记
1
2
3
4
|
// @NameSpace(ZLYAccountModule)
// @Lotusoot(AccountLotusoot)
// @Lotus(AccountLotus)
class AccountLotusoot: NSObject, AccountLotus {}
|
python 脚本找出标记,整合,放入 plist 文件中
1, 脚本入口
1
2
3
4
5
6
7
8
9
10
11
|
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 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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
运行的时候,启动
1
2
3
4
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
LotusootCoordinator.registerAll()
return true
}
|
注册就是,读取刚才写入的 plist, 作为 [协议名 : 包名.类名 ] 的字典,
1
2
3
4
5
6
7
|
@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 ( 放协议的 ), 添加一个协议
1
2
3
|
public protocol Maid{
var name: String{ get }
}
|
module B 里面,必须有一个叫 key (协议) + C 的类
该类,遵守 Maid 协议。
通过协议属性,返回 B 中服务类的名称
1
2
3
4
5
|
class AccountLotusC: NSObject, Maid{
var name: String{
return String(reflecting: AccountLotusoot.self)
}
}
|
这个过程,与上文模块化利用协议的设计,比较一致
约定是,实现协议的服务模块,
一定有一个 key + C 的类
提供服务类的名称
硬编码,比较轻微
代码实现
1、MachO 获取命名空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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、包名+类名的验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@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 https://github.com/BoxDengJZ/ModuleUnImport