runtime变奏曲,那些藏在runtime中的接口(一)

C本身是一个静态语言,数据类型和代码运行的结果都是在编译的时候确定的。而Objective-C的runtime机制赋予了C一个新的活力,即运行时机制。这也就是说,OC代码或者C代码在编译过后的机器码并不能得出运行结果。而这个结果需要在运行的时候才能获得,这样就给了我们一个新的操纵代码的空间,也就是运行时。在OC中,运行时是一段提前写完的一个模块的代码。可以这么说,OC的运行时就是这段代码赋予的。
前几篇文章中,我提到了objc_msgSend的流程,是为了让大家对runtime的过程有一个大致的了解。但是,对于大部分人来说,比起原理,更关注的是怎么用。所以本章的内容就是我在runtime小序曲,从运行时多态看这股神秘力量中提到的runtime的除了objc_msgSend的另外两种应用:NSObject的方法和runtime的函数。
学习进度:

一、NSObject的方法

前几天,和群里的一位骚年讨论了runtime的问题。他的看法是,runtime并没有什么用,不用runtime照样可以开发。其实,前半句并没有什么毛病,runtime确实没有什么用,因为大部分开发工作基本用不着(其实我们公司用的蛮多的)。问题出在第二句,不用runtime就可以开发。OC作为一种高级语言,能让你方便的使用它的一些接口。比如:

- (BOOL)respondsToSelector:(SEL)aSelector;

这个方法大家应该经常使用,尤其是在使用delegate的场合,基本是必用的。那么,我们从逻辑上看这个方法。从runtime小序曲,从运行时多态看这股神秘力量中我就说过,OC没法在编译时刻确定一个对象的类型。而这个方法是判断一个继承自NSObject的class有没有实现一个SEL。很明显,编译时刻做不了这件事,它是在运行时刻做的。所以,不用runtime就可以开发是错误的。其实,这种事情并不罕见,大部分人不想学习runtime的原因也是如此。

  • runtime是C和汇编写的,看不懂。
  • runtime开发用不着。

换个方式再说一下,runtime赋予C面向对象的能力,所以有了OC。那么说实话,只要你用到class,其实都是和runtime相关的,怎么可能避开。好像跑题了,现在我们回来。其实,和respondsToSelector:类似的OC方法还有很多,下述我会整理一些常用的。

// 在usr/include中的objc/runtime.h可以查看

// 获取对象对应的class
- (Class)class;
// 判断一个对象或者类是不是某个class或者这个class的派生类
- (BOOL)isKindOfClass:(Class)aClass;
// 判断一个对象或者类是不是某个class
- (BOOL)isMemberOfClass:(Class)aClass;
// 判断一个对象或者类对应的objc_class里面是否实现了某个协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 判断一个对象或者类对应的objc_class里面有没有某个方法
- (BOOL)respondsToSelector:(SEL)aSelector;

二、runtime中的数据结构

一中举出了一些OC的runtime方法,很易懂,因为OC就是我们的开发语言,下面我会一个个解读runtime中的C语言的接口。因为C中没有class的概念,只有struct,所以在介绍C语言接口之前,这里我将挨个介绍在runtime中使用到的常见结构体。

1、objc_class

类结构体,对应class。前几篇文章提到最多的一个结构。
objc_class

struct objc_class {
    // 指向元类的的指针,如果本身是元类,则指向rootMeta
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    // 指向父类
    Class super_class                                        OBJC2_UNAVAILABLE;
    // 类名
    const char *name                                         OBJC2_UNAVAILABLE;
    // 版本号,可以用runtime方法set,get
    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;
    // 方法cache,msgSend遍历继承链的时候需要辅助使用
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    // 协议列表
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

OBJC2_UNAVAILABLE宏定义是苹果在 Objc 中对系统运行版本进行约束的操作,为的是兼容非Objective-C 2.0的遗留逻辑,但我们仍能从中获得一些有价值的信息,有兴趣的可以查看源代码,见底部文献。

2、与objc_class直接相关

这里面举出了存储于上述objc_class结构体之中的一些相关结构体。
objc_method

// 存在objc_method_list里面
struct objc_method {
    // 方法名
    SEL method_name                                          OBJC2_UNAVAILABLE;
    // 参数,返回值编码
    char *method_types                                       OBJC2_UNAVAILABLE;
    // 方法地址指针
    IMP method_imp                                           OBJC2_UNAVAILABLE;
} 

objc_method_description

// 方法描述
struct objc_method_description {
    // 方法名
    SEL name;     
    // 参数,返回值编码             
    char *types;           
};

objc_protocol_list

struct objc_protocol_list {
    // 指向下一个objc_protocol_list的指针
    struct objc_protocol_list *next;
    long count;
    // 协议结构
    Protocol *list[1];
};

objc_ivar

struct objc_ivar {
    // 成员变量名
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    // 成员变量的类型,比如@"NSString" 代表NSString
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}  

3、与分类相关

分类是不会在编译时刻绑定到对应的类里的,只能在运行时动态绑定。所以这里把它拿出来。
objc_category

struct objc_category {
    // 分类名称
    char *category_name                                      OBJC2_UNAVAILABLE;
    // 绑定的类名
    char *class_name                                         OBJC2_UNAVAILABLE;
    // 分类中的实例方法
    struct objc_method_list *instance_methods                OBJC2_UNAVAILABLE;
    // 分类中的类方法
    struct objc_method_list *class_methods                   OBJC2_UNAVAILABLE;
    // 分类实现的协议
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
} 

分类和类扩展区别:
extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。但是category则完全不一样,它是在运行期决议的。如想学习更多category相关底层,参见:http://tech.meituan.com/DiveIntoCategory.html

4、属性相关

这里为什么把属性拿出来呢?因为上述的兼容OC1.0的objc_class里面没有property。看了runtime的源码之后,发现property有一个具体的存储地点,也在objc_class(实质是继承了objc_object)中,至于上述objc_class没有,只是因为apple不想让我们看见。如想了解更多,参考:runtime源码的objc-runtime-new.mm和objc-private.h。
objc_property_t

// 属性对应的结构体objc_property,至于具体结构,一般看不着
typedef struct objc_property *objc_property_t;

// 在objc-runtime-old.h中的结构体作为参考吧
struct old_property {
    // 属性名
    const char *name;
    // objc_property_attribute_t的char*表示
    const char *attributes;
};

objc_property_attribute_t

// 这是用来修饰表示属性的一些修饰词,比如nonatomic、copy等等
typedef struct {
    const char *name;          
    const char *value;         
} objc_property_attribute_t;

objc_property_attribute_t具体参考:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1

三、runtime的函数

到这也就到了正菜了。本章我会把runtime库中public部分所有和class相关的api都调用一遍,解释一遍。至于ivar相关、method相关、protocol相关、property相关和具体应用,且待下回分解。

前提

// 为获取class的protocol准备
@protocol AProtocol <NSObject>
- (void)aProtocolMethod;
@end
// 为获取class的相关信息
@interface A : NSObject {
    NSString *strA;
}
@property (nonatomic, assign) NSUInteger uintA;
@end
@implementation A
@end
// 为为class添加方法准备
void aNewMethod() {
    NSLog(@"aNewMethod");
}
void aReplaceMethod() {
    NSLog(@"aReplaceMethod");
}

class相关使用场景1(class一些基础信息获取):

// 代码
// 获取类名
const char *a = class_getName([A class]); 
NSLog(@"%s", a);  // a
// 获取父类
Class aSuper = class_getSuperclass([A class]);
NSLog(@"%s", class_getName(aSuper));  // b
// 判断是否是元类
BOOL aIfMeta = class_isMetaClass([A class]);
BOOL aMetaIfMeta = class_isMetaClass(objc_getMetaClass("A"));
NSLog(@"%i  %i", aIfMeta, aMetaIfMeta);   // c
// 类大小
size_t aSize = class_getInstanceSize([A class]);
NSLog(@"%zu", aSize);  // d
// 获取和设置类版本号
class_setVersion([A class], 1);
NSLog(@"%d", class_getVersion([A class]));  // e
// 获取工程中所有的class,包括系统class
unsigned int count3;
int classNum = objc_getClassList(NULL, count3);
NSLog(@"%d", classNum);    // f
// 获取工程中所有的class的数量
objc_copyClassList(&count3);
NSLog(@"%d", classNum);    // g
Class aClass;
// 获取name为"A"的class
aClass = objc_getClass("A");    
NSLog(@"%s", class_getName(aClass));    // h
// 获取name为"A"的class,比getClass少了一次检查
aClass = objc_lookUpClass("A");
NSLog(@"%s", class_getName(aClass));    // i
// 获取name为"A"的class,找不到会crash
aClass = objc_getRequiredClass("A");
NSLog(@"%s", class_getName(aClass));    // j
// 获取name为"A"的class元类
Class aMetaClass = objc_getMetaClass("A");
NSLog(@"%d", class_isMetaClass(aMetaClass));    // k

// 输出
2017-01-21 12:15:55.909 block[2493:1919841] A    // a
2017-01-21 12:15:55.912 block[2493:1919841] NSObject    // b
2017-01-21 12:15:55.913 block[2493:1919841] 0  1    // c
2017-01-21 12:15:55.913 block[2493:1919841] 24    // d
2017-01-21 12:15:55.914 block[2493:1919841] 1    // e
2017-01-21 12:44:32.401 block[5103:1948802] 4733    // f
2017-01-21 12:44:32.402 block[5103:1948802] 4733    // g
2017-01-21 12:44:32.402 block[5103:1948802] A    // h 
2017-01-21 12:44:32.403 block[5103:1948802] A    // i
2017-01-21 12:44:32.403 block[5103:1948802] A    // j
2017-01-21 12:44:32.403 block[5103:1948802] 1    // k

class相关使用场景2(class中的ivar和property)

// 代码

// 获取类实例成员变量,只能取到本类的,父类的访问不到
Ivar aInstanceIvar = class_getInstanceVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aInstanceIvar));    // a
// 获取类成员变量,相当于class_getInstanceVariable(cls->isa, name),感觉除非给metaClass添加成员,否则不会获取到东西
Ivar aClassIvar = class_getClassVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aClassIvar));    // b
// 往A类添加成员变量不会成功的。因为class_addIvar不能给现有的类添加成员变量,也不能给metaClass添加成员变量,那怎么添加,且往后看
if (class_addIvar([A class], "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
    NSLog(@"绑定成员变量成功");    // c
}
// 获取类中的ivar列表,count为ivar总数
unsigned int count;
Ivar *ivars = class_copyIvarList([A class], &count);
NSLog(@"%i", count);    // d
// 获取某个名为"uIntA"的属性
objc_property_t aPro = class_getProperty([A class], "uintA");
NSLog(@"%s", property_getName(aPro));    // e
// 获取类的全部属性
class_copyPropertyList([A class], &count);
NSLog(@"%i", count);    // f
// 创建objc_property_attribute_t,然后动态添加属性
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", @"aNewProperty"] UTF8String] };  //variable name
objc_property_attribute_t attrs[] = { type, ownership0, ownership, backingivar };
if(class_addProperty([A class], "aNewProperty", attrs, 4)) {
    // 只会增加属性,不会自动生成set,get方法
    NSLog(@"绑定属性成功");    // g
}
// 创建objc_property_attribute_t,然后替换属性
objc_property_attribute_t typeNew = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0New = { "C", "" }; // C = copy
objc_property_attribute_t ownershipNew = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivarNew  = { "V", [[NSString stringWithFormat:@"_%@", @"uintA"] UTF8String] };  //variable name
objc_property_attribute_t attrsNew[] = { typeNew, ownership0New, ownershipNew, backingivarNew };
class_replaceProperty([A class], "uintA", attrsNew, 4);
// 这有个很大的坑。替换属性指的是替换objc_property_attribute_t,而不是替换name。如果替换的属性class里面不存在,则会动态添加这个属性
objc_property_t pro = class_getProperty([A class], "uintA");
NSLog(@"123456   %s", property_getAttributes(pro));    // h
// class_getIvarLayout、class_setIvarLayout、class_getWeakIvarLayout、class_setWeakIvarLayout用来设定和获取成员变量的weak、strong。参见http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/

// 输出
2017-01-21 12:44:32.377 block[5103:1948802] strA    // a
2017-01-21 12:44:32.377 block[5103:1948802] (null)    // b
2017-01-21 12:44:32.377 block[5103:1948802] 2    // d
2017-01-21 12:44:32.377 block[5103:1948802] uintA    // e
2017-01-21 12:44:32.378 block[5103:1948802] 1    // f
2017-01-21 12:44:32.378 block[5103:1948802] 绑定属性成功    // g
2017-01-21 12:44:32.379 block[5103:1948802] 123456   T@"NSString",C,N,V_uintA    // h

class相关使用场景3(class中的method)

// 代码

// 动态添加方法
class_addMethod([A class], @selector(aNewMethod), (IMP)aNewMethod, "v");
// 向元类动态添加类方法
class_addMethod(objc_getMetaClass("A"), @selector(aNewMethod), (IMP)aNewMethod, "v");
// 获取类实例方法
Method aMethod = class_getInstanceMethod([A class], @selector(aNewMethod));
// 获取元类中类方法
Method aClassMethod = class_getClassMethod([A class], @selector(aNewMethod));
NSLog(@"%s", method_getName(aMethod));    // a
NSLog(@"%s", method_getName(aClassMethod));    // b
// 获取类中的method列表
unsigned int count1;
Method *method = class_copyMethodList([A class], &count1);
// 多了一个方法,打印看出.cxx_destruct,只在arc下有,析构函数
NSLog(@"%i", count1);    // c
NSLog(@"%s", method_getName(method[2]));    // d
 // 替换方法,其实是替换IMP
class_replaceMethod([A class], @selector(aNewMethod), (IMP)aReplaceMethod, "v");
 // 调用aNewMethod,其实是调用了aReplaceMethod
[[A new] performSelector:@selector(aNewMethod)];    // aReplaceMethod会输出 e
// 获取类中某个SEL的IMP
IMP aNewMethodIMP = class_getMethodImplementation([A class], @selector(aNewMethod));
aNewMethodIMP();    // 会调用aReplaceMethod的输出 f
// 获取类中某个SEL的IMP
IMP aNewMethodIMP_stret = class_getMethodImplementation_stret([A class], @selector(aNewMethod));
aNewMethodIMP_stret();    // 会调用aReplaceMethod的输出 g
// 判断A类中有没有一个SEL
if(class_respondsToSelector([A class], @selector(aNewMethod))) {
    NSLog(@"存在这个方法");    // h
}

// 输出
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod    // a
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod    // b
2017-01-21 12:44:32.380 block[5103:1948802] 4    // c 
2017-01-21 12:44:32.380 block[5103:1948802] setUintA:    // d
2017-01-21 12:44:32.380 block[5103:1948802] aReplaceMethod    // e
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod    // f
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod    // g
2017-01-21 12:44:32.381 block[5103:1948802] 存在这个方法    // h

class相关使用场景4(动态创建类)

// 代码

// 动态创建一个类和其元类
Class aNewClass = objc_allocateClassPair([NSObject class], "aNewClass", 0);
// 添加成员变量
if (class_addIvar(aNewClass, "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
    NSLog(@"绑定成员变量成功");    // a
}
// 注册这个类,之后才能用
objc_registerClassPair(aNewClass);
// 销毁这个类和元类
objc_disposeClassPair(aNewClass);    

// 输出
2017-01-21 12:44:32.382 block[5103:1948802] 绑定成员变量成功    // a

class相关使用场景5(class中的protocol)

// 代码

// 添加protocol到class
if(class_addProtocol([A class], @protocol(AProtocol))) {
    NSLog(@"绑定Protocol成功");    // a
}
// 查看类是不是遵循protocol
if(class_conformsToProtocol([A class], @protocol(AProtocol))) {
    NSLog(@"A遵循AProtocol");    // b
}
// 获取类中的protocol
unsigned int count2;
Protocol *__unsafe_unretained  *aProtocol = class_copyProtocolList([A class], &count2);
NSLog(@"%s", protocol_getName(aProtocol[0]));    // c

// 输出
2017-01-21 12:44:32.381 block[5103:1948802] 绑定Protocol成功    // a
2017-01-21 12:44:32.381 block[5103:1948802] A遵循AProtocol    // b
2017-01-21 12:44:32.382 block[5103:1948802] AProtocol    // c

四、小结

本章介绍了runtime的一些OC方法和runtime的数据结构,另外把runtime库public部分中所有与class相关的C方法都介绍了。下一节,将继续介绍runtime库public部分中中的其他的api。(其实,我觉得runtime学习不要太关注应用,准确的说,每一个api都是一种应用)

五、文献

1、https://developer.apple.com/reference/objectivec/1657527-objective_c_runtime?language=objc
2、https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
3、http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/
4、http://tech.meituan.com/DiveIntoCategory.html
5、https://github.com/opensource-apple/objc4

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

推荐阅读更多精彩内容