C本身是一个静态语言,数据类型和代码运行的结果都是在编译的时候确定的。而Objective-C的runtime机制赋予了C一个新的活力,即运行时机制。这也就是说,OC代码或者C代码在编译过后的机器码并不能得出运行结果。而这个结果需要在运行的时候才能获得,这样就给了我们一个新的操纵代码的空间,也就是运行时。在OC中,运行时是一段提前写完的一个模块的代码。可以这么说,OC的运行时就是这段代码赋予的。动态语言
一、NSObject的方法
OC没法在编译时刻确定一个对象的类型,结合objc_msgSend理解函数调用消息发送机制,理解respondsToSelector:函数。可以参考一份比较全的介绍RunTime的文档类似的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 - (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
类交换方法,会有机率失败,最好采用下面方式
#import "UIViewController+funsChange.h"
#import
@implementation UIViewController (funsChange)
+(void)load{
Method otherMethod = class_getInstanceMethod([UIViewController class], @selector(my_viewDidLoad));
Method method = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
method_exchangeImplementations(otherMethod, method);
}
-(void)my_viewDidLoad{
NSLog(@"my_viewDidLoad 运行了");
[self my_viewDidLoad];
}
@end
结合分类给系统的类扩展功能,交换函数,比如我想在viewcontroller 调用之前打印当前控制器的信息日志
#import "UIViewController+AddFun.h"
#import
@implementation UIViewController (AddFun)
+(void)load{
NSString *className = NSStringFromClass(self.class);
NSLog(@"%@",className);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSel = @selector(viewWillAppear:);
SEL swissSel = @selector(my_viewWillAppear:);
//获取实例方法
Method originalMethod = class_getInstanceMethod(class, originalSel);
Method swissMethod = class_getInstanceMethod(class, swissSel);
//添加方法
BOOL didAddMethod = class_addMethod(class, originalSel, method_getImplementation(swissMethod), method_getTypeEncoding(swissMethod));
if (didAddMethod) {
//替换方法
class_replaceMethod(class, swissSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
//交换方法实现
method_exchangeImplementations(originalMethod, swissMethod);
}
});
}
-(void)my_viewWillAppear:(BOOL)animated{
//在每次viewwillappear 执行的时候都会打印日志
NSLog(@"%s viewwill appear", class_getName([self class]) );
/*
新方法在实现的时候,调用的是 [self my_viewWillAppear:animated]; 因为在上面已经用my_viewWillAppear 和 viewwillAppear 互换了。所以实际上执行的是系统的viewwillAppear
这个时候可能你又有疑问了,为什么实现是- (void)my_viewWillAppear:(BOOL)animated{} 这样的
这是因为 SEL swizzledSelector = @selector(my_viewWillAppear:); 拿的就是我们新写的方法。
*/
[self my_viewWillAppear:animated];
}
@end
给类扩展属性,Person 类集成自NSObject
//
// Persion+bob.m
// Test1
//
//
#import "Persion+bob.h"
#import
#import
//可以通过 Category 给一个现有的类添加属性,但是却不能添加实例变量
@implementation Persion (bob)
/*
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
这个函数先根据对象地址在 AssociationsHashMap 中查找其对应的 ObjectAssociationMap 对象,如果能找到则进一步根据 key 在 ObjectAssociationMap 对象中查找这个 key 所对应的关联结构 ObjcAssociation ,如果能找到则返回 ObjcAssociation 对象的 value 值,否则返回 nil
objc_getAssociatedObject有两个参数,第一个参数为从该object中获取关联对象,第二个参数为想要获取关联对象的key;
对于第二个参数const void *key,有以下四种推荐的key值:
声明 static char kAssociatedObjectKey; ,使用 &kAssociatedObjectKey 作为 key 值;
声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey;,使用 kAssociatedObjectKey 作为key值;
用 selector ,使用 getter 方法的名称作为key值;
而使用_cmd可以直接使用该@selector的名称,即tempPro,并且能保证改名称不重复。(与上一种方法相同)
*/
@dynamic tempPro;
-(NSString*)tempPro{
NSString* _tempPro = objc_getAssociatedObject(self, @selector(tempPro));
if (!_tempPro) {
objc_setAssociatedObject(self, @selector(tempPro), _tempPro, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
NSLog(@"tempPro getting");
return _tempPro;
}
/*
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
参数说明:
object和key同于objc_getAssociatedObject;
value:需要和object建立关联引用对象的value;
policy:关联策略,等同于给property添加关键字,具体说明如下表关联策略
关联策略
|关联策略 |等价属性|说明|
|------|------|------|------|
|OBJC_ASSOCIATION_ASSIGN| @property (assign) or @property (unsafe_unretained)| 弱引用关联对象|
|OBJC_ASSOCIATION_RETAIN_NONATOMIC| @property (strong, nonatomic)| 强引用关联对象,且为非原子操作|
|OBJC_ASSOCIATION_COPY_NONATOMIC| @property (copy, nonatomic)| 复制关联对象,且为非原子操作|
|OBJC_ASSOCIATION_RETAIN| @property (strong, atomic)| 强引用关联对象,且为原子操作
|OBJC_ASSOCIATION_COPY| @property (copy, atomic)| 复制关联对象,且为原子操作|
*/
-(void)setTempPro:(NSString *)tempPro{
objc_setAssociatedObject(self, @selector(tempPro), tempPro, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"tempPro setting");
}
@end
四、小结
本章介绍了runtime的一些OC方法和runtime的数据结构,另外把runtime库public部分中所有与class相关的C方法都介绍了。下一节,将继续介绍runtime库public部分中中的其他的api。(其实,我觉得runtime学习不要太关注应用,准确的说,每一个api都是一种应用)
参考文章
介绍runtime很详细