死磕Objective-C runtime运行时之一

天才小飞猫

说到Objc运行时,如果你还不清楚,那可要看仔细了,如果你是靠颜值而不是才华能够顺利通过面试,喵了个咪的,我也想去试试

Objc运行时2.0

iOS出现时就是运行时2.0版本了,和旧的相比拥有两大特性:
第一,就是修改,增加或删除一个类的实例变量,不用再重新编译子类就可以用了。
第二,就是加为@property加入了Synthesize

实战问题一(答案在最后):

请将属性property(atomic,nonatomic, assign, weak, strong, copy, readonly, readwrite blah!blah!)按照功能分组

实战问题二(答案在最后):

请回答下列@property变量cool和cat的默认属性

@interface ViewController : UIViewController

@property BOOL cool;
@property NSObject *cat;

@end

Objc运行时

Objc运行时相当于Objective-C的操作系统,当我们编译代码时,编译器把源码编译成运行时能够懂得数据结构(比如isa包括类的继承信息)和运行时方法(比如objc_msgSend), 剩下的就交给运行时动态处理了。

NSObject成员变量isa

@interface NSObject{
    objc_class *isa
}

//点开isa链接搜索struct objc_class : objc_object能够查到其声明
struct objc_class : objc_object {
    // Class ISA;
    objc_class *superclass;
    ....
    方法派遣表   selector对应的C语言函数
    ....
}

从objc_class声明可以看出,每一个isa->superclass同样是objc_class类型,
这样就组成了一个继承的链表结构

运行时3种使用方式

1.写Objective-C代码必然用到,虽然没这种感觉
2.调用NSObject运行时方法
3.直接调用运行时API

运行时objc_msgSend

objc方法只不过是C语言方方法,加上两个特殊的参数第一个是receiver(self),第二个参数是selector(_cmd)

比如以下的方法调用
[receiver message:arg1] //假设定义arg1为BOOL类型
具体实现:
(void (*)(id, SEL, BOOL))[target methodForSelector:@selector(message:)];
运行时:

`objc_msgSend(receiver, selector, arg1)`

objc_msgSend做的事情如下:

  1. 根据receiver和selector先在receiver的方法派遣表里面查找是否有selector这个方法实现, 如果没找到,receiver->isa->superclass去查找,以此类推,直到找到对应的方法实现,若直到NSObject都没有找到对应实现,中间过程在下文解释,最后掉用[receiver doesNotRecognizeSelector:_cmd]就抛异常出错了

  2. 将所有参数传给具体实现方法调用

  3. 将具体实现结果反回来

跳过运行时objc_msgSend

尽管objc_msgSend在成功调用一次方法之后,每个Class会有缓存,下次重复调用该方法时,查找速度会大大提高,但是还是有运行时消息发送的时间,如果一个函数被调用成千上万次,为了减少消息派遣时间,可以跳过运行时objc_msgSend,直接调用方法实现

void (*message)(id, SEL, BOOL);
int i;
 
message = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(message:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(message:), YES);

问题一答案:

原子性:atomic,nonatomic
读写性: readwrite, readonly
ARC版内存管理: assign, strong, copy, weak

问题二答案:

@property cool = TB,V_cool
@property cat = [email protected]"NSObject",&,V_cat

子问题1: 你是怎么得到答案的?答案就是用运行时啦!!!

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    
    id LenderClass = objc_getClass("ViewController");
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        fprintf(stdout, "@property %s = %s\n", property_getName(property), property_getAttributes(property));
    }
}

子问题2: 如何解读?首先说明这奇怪的字符时编译器干的好事啦,参考主要是这里还有这里

The string returned by |property_getAttributes| starts with a T followed by the @encode type and a comma, and finishes with a V followed by the name of the backing instance variable.

上面一坨就是说,property_getAttributes获得的字符串的格式,以T开头,然后是<变量类型>,然后是一个逗号,然后是<属性>,最后是一个V,再往后就是下划线_和变量名了


/*根据文档:
如果是readonly应该有R属性, 所以默认应该是readwrite
如果是weak,strong,copy应该分别有W, &, C, 所以默认应该是assign
若果有nonatomic应该有N, 所以默认是atomic       

TB,V_cool B代表BOOL, 中间什么属性都没有对不对,根据以上分析,默认是atomic, assign, readwrite 
[email protected]"NSObject",&,V_cat 则是atomic, strong, readwrite
*/
阅读 2.9k

320 声望
16 粉丝
0 条评论
320 声望
16 粉丝
文章目录
宣传栏