iOS-组件化
在做iOS组件化的时候,我没有找到像ARouter一样兼容性和使用性比较好的框架。经过权衡后,决定使用CTMediator来作为iOS组件化底层结构,再对CTMediator进行扩展来满足我们的要求。
扩展支持Swift跳转
Swift和OC在类和方法字符串展示上稍有不同:
类名:OC直接通过类名就能反射出Class,Swift有一套特殊格式生成
方法:OC直接通过action name就能反射出action,Swift需要加冒号:
- 类名扩展
open func transformString(targetName: String, isTargetClass: Bool, shouldCacheTarget:Bool = false) -> AnyObject? {
var targetClass: AnyClass? = self.cachedTarget[targetName]
if targetClass == nil {
// 首先通过OC的方式去反射Class
targetClass = NSClassFromString(targetName)
if targetClass == nil {
// 如果OC的方式反射失败,再使用Swift的方式
targetClass = NSObject.swiftClassFromString(targetName)
}
}
/***/
}
通过字符串去反射Class的时候,首先通过OC的方式,如果OC的方式反射失败,再使用Swift的方式。关键点就是swiftClassFromString是如何找回Swift 类名的:
extension NSObject {
// create a static method to get a swift class for a string name
@objc public class func swiftStringClassFromString(_ className: String, bundle: Bundle = Bundle.main) -> String? {
// get the project name
if let appName = bundle.object(forInfoDictionaryKey: "CFBundleExecutable") as? String {
// generate the full name of your class (take a look into your "YourProject-swift.h" file)
let classStringName = "_TtC\(appName.utf16Length)\(appName)\(className.count)\(className)"
// return the class!
return classStringName
}
return nil
}
// create a static method to get a swift class for a string name
public class func swiftClassFromString(_ className: String) -> AnyClass? {
if let classStringName = NSObject.swiftStringClassFromString(className) {
return NSClassFromString(classStringName)
}
return nil
}
}
在我们项目中对NSObject进行扩展方法,在swiftStringClassFromString生成Swift类名。特别注意这个方法前面要加上@objc,让其通过OC方式调用。
- 方法扩展
和获取Class的时候类似,先使用OC方式去查看是否能响应,没有响应再使用Swift方式去查看。否则处理异常。
fileprivate func performSelector(_ isTargetClass: Bool, targetName: String, actionName: String, params: Any, shouldCacheTarget: Bool, shouldReturn: Bool) -> Any? {
/***/
var action = NSSelectorFromString(actionString)
// 通过OC方式去看是否响应该方法
if target.responds(to:action) {
let obj = target.perform(action, with:params)
return shouldReturn ? obj?.takeUnretainedValue() : nil
} else {
// 有可能target是Swift对象
actionString = actionName + ":"
action = NSSelectorFromString(actionString)
if target.responds(to:action) {
let obj = target.perform(action, with:params)
return shouldReturn ? obj?.takeUnretainedValue() : nil
} else {
/*无响应请求的地方*/
}
}
}
提供程序接口Provide
在iOS里面,该功能需要我们自己实现。这里我参考ARouter里面IProvide的实现方法实现了自己的一套提供程序接口。
在基础Module里面定义Provide方法,在具体Module里面继承该Provide后重写该方法。因为项目主项目肯定都包含这些具体Module的,所以可以在主项目里面初始化这些子类,并把父类里面公共对象指向子类。 以后直接调用父类里面公共对象就可以了。
talk is cheap, show me the code
- 基础Module里面父类
open class PassportModuleProvider {
public static var shareInstance: PassportModuleProvider?
public init() {
PassportModuleProvider.shareInstance = self
}
open func passportUpdatePWD() {
fatalError("子类实现passportUpdatePWD")
}
open func passportPrepareVC() -> UIViewController? {
fatalError("子类实现passportPrepareVC")
}
}
- 具体Module里面实现类
public class PassportProviderLmpl: PassportModuleProvider {
open override func passportUpdatePWD() {
/**/
}
open override func passportPrepareVC() -> UIViewController? {
return UIViewController()
}
}
- 主项目里初始化实现类
class func initProvider() {
_ = PassportProviderLmpl()
/***/
}
这样在初始化实现类的时候,就会自动把PassportModuleProvider.shareInstance指向PassportProviderLmpl了。然后使用的时候,通过该shareInstance就能在每个Module中调用实现类里面的方法了。
我一直在想能不能把主项目里初始化实现类这个步骤去除,没有想到特别好的方法。事实上虽然Swift无法像Android一样定义自己的注解,不过OC可以,只不过这里我们项目的原则是尽可能Swift,所以通过OC添加注解,再和Swift混编的方式我们就暂时没有使用。 不过如果你的项目是OC项目,那么第3步完全可以通过注解处理的。
页面跳转拦截
针对页面跳转拦截,iOS中我们之前已经使用RxSwift处理了这部分流程,所以这部分暂时还没有放入组件化中。不过这部分后续我会放入组件化中,到时候会更新本文章。
经过上面方式的处理,我们项目结构变成和Android类似的结构
image.png image.png