Runtime

简单说一下Objective-C

简单说一下Objective-C,它在C语言的基础上加入了面向对象的特性,同时还继承了SmallTalk的消息传递的特性。Objective-C会将编译和执行延迟到远行时。Objective-C中对象之间互相调用方法,可以看成对象之间互相传递消息,在对象发送消息后,所有消息的回应都会在运行时才会决定,并交给类别自行决定如何处理收到的消息。⚠️因此,一个类别收到消息并不能保证一定会做出回应。

下面主要讲一下Objective-C的主要特性运行时(RunTime)。

Runtime

Objective-C runtime 是一个主要由C和汇编写运行时库(Runtime Library)。Runtime 有两个版本:Modern Runtime(现代的 Runtime) 和 Legacy Runtime(过时的 Runtime)。Modern Runtime:覆盖所有64位的Mac OS X 应用和所有iPhone OS 的应用,Objective-C 2.0后采用。Legacy Runtime 覆盖所有32位的MAC OS X 应用。

Runtime的交互方法

  • 方式一: Objective-C源代码。

    ​大部分时间我们只管写ObjC代码,runtime系统会自动工作。例如:[receiver message] 编译器会转化为 objc_msgSend(receiver,selector) 含有参数的话 objc_msgSend(receiver,selector,arg1,arg2,...) 消息的执行会使用到编译器为实现动态语言特性而创建的数据结构和函数,例如Objc中的类,方法和协议等都是在runtime中由一些数据结构定义的。

  • 方式二:NSObject方法。

    ​Cocoa 中大多数类都是继承NSObject类。最特殊的例外是NSProxy,他是抽象超类,他实现了一些消息转发的相关方法。有的NSObject 中的类方法起到了抽象接口的作用,如description方法需要你重载它并为你定义的类提供描述内容。还有一些方法可以在运行时获取类的信息,检查一些特性。如:class isKindOfClass isMemberOfClass respondsToSelector confromsToProtocol检查对象是否实现了指定协议类的方法 methodForSelector则返回指定方法实现的地址。

  • 方式三:Runtime函数。

    runtime的头文件在/usr/include/objc目录下。对于RunTime的函数可以查看官方文档

Runtime 术语

  • objc_msgSend:方法的真身->id objc_msgSend(id self,SEL op, ...);

  • SEL:是selector类型。selector是方法选择器,可以理解为区分方法的ID。SEL的数据结构是typedef struct objc_selector *SEL;它就是映射到方法的C字符串。你可以使用@selector()或者Runtime系统的sel_registerName 函数来获取一个SEL类型的方法选择器。

    ⚠️不同类中相同名字的方法所对应的方法选择器是相同的,但是方法名字相同而变量类型不同也会导致她们具有相同的方法选择器。

  • id: objc_msgSend 第一个参数是id ,它是指向类实例的指针typedef struct objc_object *id ,那么objc_object是什么?->struct objc_object {Class isa;}; objc_object结构体包含一个isa指针,根据isa指针就可以找到对象所属的类。

    ⚠️isa 指针不总是指向实例对象所属的类,我们应该用class 确定实例对象的类而不是用isa。在KVO的实现机理中,被观察者的isa指针指向中间的类而不是真实的类,这种叫做isa-swizzling技术官方文档

  • Class:之所以说isa是指针是因为Class 其实是指向objc_class结构体的指针:typedef struct objc_class *Class , 接下来看一下objc_class中的结构:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;//指向metaclass
  
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;//指向父类
    const char *name                                         OBJC2_UNAVAILABLE;//类名
    long version                                             OBJC2_UNAVAILABLE;//版本信息
    long info                                                OBJC2_UNAVAILABLE;//标识信息
    long instance_size                                       OBJC2_UNAVAILABLE;//实例变量大小
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;//变量
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;//方法列表
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;//最近的使用的方法指针
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;//遵守的协议
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

我们可以看到运行时一个类关联的超类指针,类名和成员变量,方法,缓存以及附属的协议列表。在objc_class 中「ivars」是「objc_ivar_list」的指针;「methodLists」是「objc_method_list」指针的指针。也就是说我们可以动态修改「*methodLists」的值来添加方法,这也是category的实现原理,同样解释了category不添加属性的原因。深入理解 Object-C category。 在category中我们也可以添加@dynamic 的属性,并利用运行期动态提供存取的方法。或者使用关联度对象「AssociatedObject」其中objc_ivar_listobjc_method_list分别对应成员变量和方法列表,看一下关于属性,方法以及各自对应的列表方法的数据结构:

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}                                                            OBJC2_UNAVAILABLE;

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

我们可以看到objc_class 中有一个isa 对象,这是因为ObjC类本身也是一个对象,为了处理类和对象之间的关系,runtime创建了一个叫做元类(meta class)的东西,他用来表示类对象本身所具备的元数据。类方法就定义于此,这些方法可以理解成类对象的实例方法。一个类只有一个类对象,类对象仅有一个相关的元类。当一个类发送[NSObjec alloc] 消息时,事实上是 把这个消息发送给类对象,这个类对象必须有一个元类的实例,而这个元类的同时也是一个类的根元类(root meta class)的实例。所有的元类最终指向根元类的超类。所有的元类的方法列表都能响应消息的类方法。所以当[NSObject_alloc] 这条消息发送给类对象的时候,objc_msgSend() 会去它的元类里面查找能够响应消息的方法,找到后这个类对象执行方法。


msgSend.png

上图是super_class 指针,虚线是isa 指针。可以看到根元类的超类是NSObject ,而他的isa指向了自己,也就是它没有超类。

  • Method:代表某个方法的类型。typedef struct objc_method *Method , objc_method在上面方法的列表中存储方法名,类型和实现:
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
} 

SEL:方法名类型。

方法类型method_types是个char类型,其实存储这方法的参数类型和返回值类型。

method_imp指向的是方法的实现,本质是一个函数指针。
  • Ivar:类中实例变量类型。typedef struct objc_ivar *Ivar; 看一下 objc_ivar结构体:
struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
} 
我们还可以根据实例来查找其在类中的名字,拿来主义,也就是“反射”:
-(NSString *)nameWithInstance:(id)instance {
    unsigned int numIvars = 0;
    NSString *key=nil;
    Ivar * ivars = class_copyIvarList([self class], &numIvars);
    for(int i = 0; i < numIvars; i++) {
        Ivar thisIvar = ivars[i];
        const char *type = ivar_getTypeEncoding(thisIvar);
        NSString *stringType =  [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
        if (![stringType hasPrefix:@"@"]) {
            continue;
        }
        if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌!
            key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
            break;
        }
    }
    free(ivars);
    return key;
}

class_copyIvarList 函数获取的不仅有函数的实例变量,还有属性。

  • IMP:typedef id(*IMP)(id,SEL,...); 函数指针,这个是有编译器生成的。IMP 指向这个方法的实现。再次我们可以利用IMP来直接执行方法而不通过消息传递机制。但是在本人看来这个方法虽然效率很高省去了消息传递过程但是对于程序的调试不太方便(此句仅代表个人看法)。IMP和 objc_msgSend函数的参数都包含id和SEL类型。没个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的SEL对应的方法实现肯定是唯一的,通过一组id和SEL 参数就能确定唯一的方法实现地址。
  • objc_cache:缓存。
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};
Cache为方法调用的性能进行优化,当每个实例对象接收到消息时,它不会直接在isa指向的类的方法列表中遍历查找能够响应的消息方法,而优先在Cache中查找。Runtime系统会在被调用的方法存到Cache中,下次查找效率会更高(对于这点本人知之甚少)。
  • Property:属性 typedef struct objc_property *Property typedef struct objc_property *objc_property_t//更常用
    可以通过class_copyPropertyList和protocol_copyPropertyList获取类中和协议中的属性。
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

返回值时指向数组的指针,数组中都是objc_property_t指针。

然后我们可以根据property_getName函数来查找属性名称。const char *property_getName(objc_property_t property)

我们还可以利用class_getProperty 和 protocol_getProperty 通过给出的名称来在类和协议中获取属性的引用:
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
你可以用property_getAttributes函数来发掘属性的名称和@encode类型字符串:
const char *property_getAttributes(objc_property_t property)
把上面的代码放一起,你就能从一个类中获取它的属性啦:
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(class, &outCount);
for (i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}

对比下 class_copyIvarList 函数,使用 class_copyPropertyList 函数只能获取类的属性,而不包含成员变量。但此时获取的属性名是不带下划线的。

​ Runtime运行时先看这些,愿我们都可以站在巨人的肩膀来提升自我😄。

参考文献:

http://yipingmi.top/391/
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
http://www.cocoachina.com/ios/20141008/9844.html
https://zh.wikipedia.org/wiki/Objective-C
http://www.cnblogs.com/yswdarren/p/3619303.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容