Runtime奇技淫巧之类(Class)和对象(id)以及方法(SEL)

在学习Runtime的时候,你可能要脱离原来你所认知的区域,比如:你真的了解类和对象么?你真的理解实例方法和类方法么?你真的以为你看到的就是所有的东西么?网上的那些所谓的实用技巧你真的理解什么意思么?磨刀不误砍柴工,我们先说一下很重要的几个概念。

1. id 以及 Class

  • (我姑且认为大家印象中:id就是对象,Class就是类。)

大家对于idClass其实并不陌生,我们做一个实验,创建一个Person的类,然后创建一个Person对象,然后这样:

Person* person = [[Person alloc] init];
NSLog(@"%p",person);
//
NSLog(@"%p",[person class]);
NSLog(@"%p",[Person class]);
//
NSLog(@"%p",object_getClass(person));
NSLog(@"%p",object_getClass([person class]));

打印结果:

RuntimeSkill[2048:247155] 0x60000000ed30
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6a8

我擦嘞,就问你懵逼了没有?


从结构看功能

写一个Class去看系统的API:
typedef struct objc_class *Class;同时你会发现有一个这个东西typedef struct objc_object *id;
发现id是一个结构体,并且里面只有一个Class类型的指针isa:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

再看Class其实也是系统定义的一个结构体,只不过结构复杂很多:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#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 *` */

通过对比我们可以确认几点:

  • 在Objective-C中,所有的类其实也是一个对象,我们可以认为id中的isa指针指向的是一个类对象,并且在Class结构体中的isa指针指向元类(后面做解释)。
  • 在Runtime中,以obj_开头的方法大多是针对id类型的对象进行操作,而以class_开头方法主要是针对Class类型的类对象进行操作。
  • Runtime的以class_开头的方法是针对与Class结构体的各个元素进行操作的。
  • 结构体元素的作用详见注释,不再多余赘述。

元类(Meta Class)

objc_object结构体中,有一个objc_class类型的指针,奇怪的是,在objc_class结构体中又有一个objc_class类型的指针,这也就引入了元类的概念。
首先,元类是类对象的类,同样也就是一个对象,它的结构也是objc_class结构,有人现在会说了,你这不是扯淡吗?这么说那元类的isa指针有指向哪里?我读书少,你不要骗我!!!

其实这就涉及了一种特殊的机制,为了不让这种结构无限延伸下去,Objective-C让所有的元类的isa指针指向基类的元类,以此作为它们的所属类。即,任何NSObject继承体系下的元类都使用NSObject的元类作为自己的所属类,而基类的元类的isa指针是指向它自己,并且基类的元类的父类是基类。这样就形成了一个完美的闭环。太他妈有想法了:

图画的不好,大家将就着看

这样我们就可以解释刚开始的打印结果了,第一个地址0x60000000ed30为创建的Person对象,第二,第三,第四个地址0x10702c6d0Person类对象的地址,最后一个0x10702c6a8Person类的元类的地址。(注意:元类的调用只能用object_getClass ()或者objc_getClass ()获得,使用类对象调用class方法是无法获取到元类的,它只是返回当前类对象而已。)

实例方法和类方法

下面我们在方法调用方面来研究一下元类存在的必然性,那就要说到实例方法和类方法发送消息的机制,在Class的结构体中有一个元素methodLists,当我们调用一个方法时,系统会在这个列表中进行查找(这里不考虑cache),同样元类也有一个methodLists,所以:

  • 当给对象发送消息时(调用实例方法),系统会在当前对象对应的类对象的methodLists中进行查找。
  • 当给类发送消息时(调用类方法),系统会在当前类的元类的methodLists中进行查找。

也就是说类对象存储着一个类的所有实例方法,元类存储着一个类的所有类方法。同时每个类都会有一个单独的元类,因为每个类的类方法基本不可能完全相同。看到这有人睡说,元类中还有很多的元素,比如成员变量的链表,那类变量怎么说?目前我没有找到关于类变量的信息。

2. SEL、Method、IMP

关于SEL相信大家都很熟悉,但是对于MethodIMP就相对陌生了,下面我们详解这几个关于方法的数据类型。

SEL

SEL是系统在编译过程中,会根据方法的名字以及参数序列生成一个用来区分这个方法的唯一ID编号,这个 ID 就是 SEL 类型的。我们需要注意的是,只要方法的名字和参数序列完全相同,那么它们的 ID编号就是相同的。
获取SEL的几种方法:

SEL aSel = @selector(didReceiveMemoryWarning);
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
SEL a_Sel = sel_registerName("didReceiveMemoryWarning");
NSLog(@"%p___%p___%p",aSel,a_sel,a_Sel);

打印结果:

RuntimeSkill[1741:214328] 0x10957b985___0x10957b985___0x10957b985

Method

Method从字面上一看就是方法的意思。Method其实就是 objc_method的结构体指针,结构如下:

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;  //方法名
    char *method_types                                       OBJC2_UNAVAILABLE;  //参数类型以及返回值类型编码
    IMP method_imp                                           OBJC2_UNAVAILABLE; //方法实现指针
}  

获取Method的方法:

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

IMP

IMPImplementation,为指向函数实现的指针,如果我们能够获取到这个指针,则可以直接调用该方法,充分证实了它就是一个函数的指针。
获取IMP的方法:

//通过Method获取IMP
IMP method_getImplementation(Method m);
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

获取到IMP之后可直接调用方法:

SEL aSel = @selector(didReceiveMemoryWarning);
Method method = class_getInstanceMethod([self class], aSel);
IMP imp = method_getImplementation(method);
((void (*) (id, SEL)) (void *)imp)(self, aSel);

3. Ivar

Class结构体中,有一个ivars的链表结构,其中存储着所有变量信息(Ivar的数组),每一个Ivar指针对应一个变量元素。同时通过系统的API,我们看到Ivar也是一个结构体,`typedef struct objc_ivar *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
} 

对于Ivar`的操作有以下方法:

// 获取类中指定名称成员变量
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 获取类变量
Ivar class_getClassVariable ( Class cls, const char *name );
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
  • 需要注意的是:class_copyIvarList这个函数函数,返回全部实例变量的数组,数组中每个Ivar指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。我们必须使用free()来释放这个数组
  • 关于类变量的传说连听过都没听过,你要是吹牛逼说你知道,那麻烦您教一下我,必有重谢,哈哈哈哈哈。

总结

对于上面的很多的示例代码只是提供给大家帮助理解的,我提醒你一点:开发中千万不要这么写代码,不然你升职加薪,迎娶白富美,走上人生巅峰本来就不可能,现在可能连温饱也是个问题了。这也不是单纯的扯淡,明确概念之后,我们讲Runtime的实际用法才事半功倍。

传送门 : Runtime实用技巧(不扯淡,不套路)

再提示一遍,开发中千万不要装逼这样写!!!

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

推荐阅读更多精彩内容