公司半年前做了一个组件化的项目,用到了CTMediator这个三方框架 ,主要是为了完成组件与主工程间数据传递及页面间的交互处理。其中CTMediator就是借助了Runtime的消息转发实现了这些功能 ,Runtime是Objective-C 里面最核心最重要的技术,接下来我会通过一些实际应用来进行一下介绍 。
一、Runtime 概念
Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,通过 Foundation 框架的NSObject类定义的方法,通过对 runtime 函数的直接调用。大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。
- RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
- 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
- OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
- 只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
二: 获取类信息方法
-
获取方法列表:
let methodList = class_copyMethodList(object_getClass(SwiftClass), &count) for ind in 0..<numericCast(count) { let method = method_getName(methodList![ind]) print("属性成员方法:",String.init(NSStringFromSelector(method))) }
-
属性成员变量:
let IvarList = class_copyIvarList(object_getClass(SwiftClass),&count) for index in 0..<numericCast(count) { let Ivar = ivar_getName((IvarList?[index])!) print("属性成员变量:",String.init(utf8String: Ivar!) ?? "没有找到你想要的成员变量") }
-
协议列表:
let protocalList = class_copyProtocolList(object_getClass(self),&count) for index in 0..<numericCast(count) { let protocal = protocol_getName((protocalList?[index])!) print("协议:",String.init(utf8String: protocal) ?? "没有找到你想要的协议") }
三: 使用场景
- 添加关联对象
- 消息转发
- 动态交换两个方法的实现
- 在方法上增加额外功能
- 实现NSCoding的自动归档和解档
- 实现字典转模型的自动转换
1)添加关联对象
给UIButton 添加一个count属性
extension UIButton {
private static var ADJ_KEY: Void?
var count: Int {
get {
(objc_getAssociatedObject(self, &Self.ADJ_KEY) as? Int) ?? 0
}
set {
objc_setAssociatedObject(self, &Self.ADJ_KEY, newValue, .OBJC_ASSOCIATION_COPY)
}
}
}
使用
override func viewDidLoad() {
super.viewDidLoad()
let test = UIButton()
test.count = 200
print(test.count)
}
objc_getAssociatedObject 和 objc_setAssociatedObject
2)消息转发()
这里以实例方法的动态解析为例子
class TestObject:NSObject{
override class func resolveInstanceMethod(_ sel: Selector!) -> Bool {
let swizzledSelector = #selector(resolveInstanceRun)
let methodName = NSStringFromSelector(sel)
print("未找到此方法"+methodName)
guard let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
return super.resolveInstanceMethod(sel)
}
let imp:IMP = method_getImplementation(swizzledMethod)
class_addMethod(self, sel, imp, "v@:")
return true
//拦截指定未实现的方法完成转发可执行下面写法
// guard methodName == "CCCC" else{
// return super.resolveInstanceMethod(sel)
// }
// let swizzledSelector = #selector(resolveInstanceRun)
//
// let methodName = NSStringFromSelector(sel)
//
// print("未找到此方法"+methodName)
//
// guard let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
//
// return super.resolveInstanceMethod(sel)
// }
// let imp:IMP = method_getImplementation(swizzledMethod)
// class_addMethod(self, sel, imp, "v@:");
//
// return true;
}
@objc func resolveInstanceRun(){
print("未实现的方法,都转发到我这里了哦")
}
}
被转发的方法 resolveInstanceRun
@objc func resolveInstanceRun(){
print("未实现的方法,都转发到我这里了哦")
}
方法调用
override func viewDidLoad() {
super.viewDidLoad()
let Vc = TestObject()
let Selector = NSSelectorFromString("CCCC")
//会走转发方法
Vc.perform(Selector)
// 这种调用方式不会 走转发方法
//Vc.responds(to: Selector)
}
当调用一个类的未实现的实例方法时,就会调用类的动态解析方法,然后可以在动态解析方法中转发
3)方法交换
对于纯粹的Swift类,由于无法拿到类的属性方法等,也就没办法进行方法的替换,但是对于继承自NSObject的类,由于集成了OC的所有特性,所以是可以利用Runtime的属性来进行方法替换,需要动态替换的方法前面 需要使用dynamic关键字。
方法myMethod 与 myChangeMethod方法替换 class_addMethod
class TestObject{
@objc dynamic func myMethod(name:String,age:Int){
print("myMethod -->\(name) 历经 --->\(age) 年")
}
}
extension TestObject{
public class func initializeMethod(){
let originalSelector = #selector(TestObject.myMethod(name:age:))
let swizzledSelector = #selector(TestObject.myChangeMethod(name:age:))
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
//在进行 Swizzling 的时候,需要用 class_addMethod 先进行判断一下原有类中是否有要替换方法的实现
let didAddMethod: Bool = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
//如果 class_addMethod 返回 yes,说明当前类中没有要替换方法的实现,所以需要在父类中查找,这时候就用到 method_getImplemetation 去获取 class_getInstanceMethod 里面的方法实现,然后再进行 class_replaceMethod 来实现 Swizzing
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
@objc func myChangeMethod(name:String,age:Int) {
print("myChangeethod -->\(name) 历经 --->\(age) 年")
}
}
didFinishLaunchingWithOptions 方法中调用方法initializeMethod
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
TestObject.initializeMethod()
return true
}
使用
override func viewDidLoad() {
super.viewDidLoad()
let Vc = TestObject()
Vc.myMethod(name: "中国", age: 5000)
// Do any additional setup after loading the view.
}
4)在方法上增加额外功能
这个功能可以沿用方法交换的代码 ,只需要在交换的方法中调用一下原方法。
@objc func myChangeMethod(name:String,age:Int) {
//调用原方法(因为myChangeMethod 与 myMethod进行了方法交换,所以调用myChangeMethod实际执行的是方法交换之前myMethod方法的实现)
myChangeMethod(name: name, age: age)
//下面是需要实现的新功能代码 例如新功能是打印 abcd
print("abcd")
}
参考链接:
https://www.jianshu.com/p/46dd81402f63
文章持续更新中、希望对各位有所帮助、有问题可留言 大家共同学习.
网友评论