Runtime

Oct 15, 2014


来源

Objective-C 来源于Smalltalk,使用消息结构(message structure),运行时执行的代码由运行环境决定,这个过程叫做动态绑定。Swift则是函数调用(function calling),运行时执行的代码由编译器决定,从虚方法表查出需要执行的函数。

在C语言基础上增加了面相对象特性;如类为结构体指针,方法为函数指针。

Runtime是一套API,是一个层级,OC代码被编译成C语言,然后调用runtime进而执行程序。

所以我们可以通过runtime的API动态完成一些功能。

编译.m文件

通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)

获取类名

class_getName

// Objective-C
const char *classChar = class_getName(ClassName.self);
NSString *className = [NSString stringWithUTF8String:classChar];
// Swift
let className = String(cString: class_getName(ClassName.self)

备注:

非runtime方法获取类名:

Objective-C NSString *className = NSStringFromClass(ClassName.self)

Swift let className = String(describing: ClassName.self)

注意:通过Objective-C方法(如class_getName、NSStringFromClass)获取 Swift class 得到的类名会有模块前缀。也可以理解为:Swift类在Objective-C中会有模块前缀

获取成员变量

class_copyIvarList And ivar_getName

// Objective-C
- (void)fetchIvarList:(Class)aClass{
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(aClass, &count);
    for (int i = 0; i < count ; i++) {
        const char *ivarName = ivar_getName(ivarList[i]);
        const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
        NSLog(@"%@ ivar name = %@,type = %@",aClass,[NSString stringWithUTF8String:ivarName],[NSString stringWithUTF8String:ivarType]);
    }
    free(ivarList);
}
// Swift
func fetchIvarList(aClass: AnyClass) {
    var count: UInt32 = 0
    let ivarList =  class_copyIvarList(aClass, &count)
    for i in 0..<count {
        let name = String(cString: ivar_getName(ivarList![Int(i)]))
        let type = String(cString: ivar_getTypeEncoding(ivarList![Int(i)]))
        print("\(aClass) ivar name = ",name, "type = ", type)
    }
    free(ivarList)
}

备注:

Swift中的class_copyIvarList也可以获取纯Swift类的成员变量。

只可以获取当前类的成员变量,无法获取父类的成员变量。

获取属性列表

class_copyPropertyList And property_getName

// Objective-C
- (void)fetchPropertyList:(Class)aClass{
    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList(aClass, &count);
    for (int i = 0; i < count ; i++) {
        const char *proName = property_getName(propertyList[i]);
        const char *proType = property_getAttributes(propertyList[i]);
        NSLog(@"%@ property name = %@, type = %@",aClass, [NSString stringWithUTF8String:proName],[NSString stringWithUTF8String:proType]);
    }
    free(propertyList);
}
// Swift
func fetchPropertyList(aClass: AnyClass) {
    var count: UInt32 = 0
    let propertyList = class_copyPropertyList(aClass, &count)
    for i in 0..<count {
        let name = String(cString: property_getName(propertyList![Int(i)]))
        let type = String(cString: property_getAttributes(propertyList![Int(i)]))
        print("\(aClass) property = ",name, "type = ", type)
    }
    free(propertyList)
}

备注:

Swift类中的属性可以class_copyIvarList 也可以通过class_copyPropertyList获取到,可以说,Swift类中的属性,也同样是成员变量。

获取方法列表

class_copyMethodList And method_getName

// Objective-C
- (void)fetchMethodList:(Class)aClass{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(aClass, &count);
    for (int i = 0; i < count; i++) {
        SEL methodName = method_getName(methodList[i]);
        const char *name = sel_getName(methodName);
        const char *methodType = method_getTypeEncoding(methodList[i]);
        NSLog(@"%@ method name = %@, type = %@",aClass,[NSString stringWithUTF8String:name],[NSString stringWithUTF8String:methodType]);
    }
    free(methodList);
}
// Swift
func fetchMethodList(aClass: AnyClass) {
    var count: UInt32 = 0
    let methodList = class_copyMethodList(aClass, &count)
    for i in 0..<count {
        let sel = method_getName(methodList![Int(i)])
        let name = String(cString: sel_getName(sel))
        let type = String(cString: method_getTypeEncoding(methodList![Int(i)]))
        print("\(aClass) methond = ",name, "type = ", type)
    }
    free(methodList)
}

备注:

继承NSObject的类,无论是Swift还是OC,都可以通过class_copyMethodList获取属性的set和get方法,也可以获取实例方法。

如果Swift中方法前面添加了fileprivate关键词,则无法获取到

如果是纯的Swift类,则无法获取到实例方法。

纯Swift类或者添加了fileprivate关键词的实例方法无法获取到,添加关键词@objc之后就可以获取到。

@objc在Swift类中,为function/variable添加@objc以便可以在OC中使用;@objc 修饰符的另一个作用是为 Objective-C 侧重新声明方法或者变量的名字。另外,如果你用 Swift 写的 class 是继承自 NSObject 的话,Swift 会默认自动为所有的非 private 的类和成员加上 @objc。但是需要注意的是,添加 @objc 修饰符并不意味着这个方法或者属性会变成动态派发,Swift 依然可能会将其优化为静态调用。

方法交换

class_getInstanceMethod And method_exchangeImplementations

// Objective-C
- (void)exchangeMethod:(Class)aClass method:(SEL)method1 otherMethod:(SEL)method2 {
    Method m1 = class_getInstanceMethod(aClass, method1);
    Method m2 = class_getInstanceMethod(aClass, method1);
    method_exchangeImplementations(m1, m2);
}
// Swift
func exchangeMethod(withClass aClass:AnyClass, method1: Selector, method2: Selector) {
    let m1 = class_getInstanceMethod(aClass, method1)
    let m2 = class_getInstanceMethod(aClass, method2)
    method_exchangeImplementations(m1, m2)
}

备注:

Swift类中的方法没有动态特性,即使继承了NSObject。

但在其方法前添加dynamic关键词后,可以获得动态特性,从而交换方法。

另外:若方法的参数、属性类型为Swift特有、无法映射到Objective-C的类型(如Character、Tuple),则此方法、属性无法添加dynamic修饰(会编译错误)

属性关联

objc_setAssociatedObject and objc_getAssociatedObject

// Objective-C
static char nameKey;
-(void)setName:(NSString *)name{
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name{
    return objc_getAssociatedObject(self, &nameKey);
}
// Swift
extension MyClass {
    private static var nameKey: UInt8 = 0
    var name: String?{
        set{
            objc_setAssociatedObject(self, &MyClass.nameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        get{
            return objc_getAssociatedObject(self, &MyClass.nameKey) as? String
        }
    }
}