之前看各种Runtime机制的详解等一系列诸如此类的文章,对文章中各种术语都一知半解,这几天看了Block原理的详解,又遇到类似相关的知识,要深入理解Runtime机制,必须得从Objective-C类的定义理解开始。
此文章大部分内容来源于以下两个博客,本人仅记录个人所需要的知识点。
Objective-C Runtime
Objective-C isa 指针 与 runtime 机制
Objective-C Runtime 运行时之四:Method Swizzling
1. Objective-C对类的定义
Xcode中objc.h中对类的定义typedef struct objc_selector *SEL;
方法选择器,可以理解为区分方法的ID,此ID的数据结构是SEL
方法的selector用于表示运行时方法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL
两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个SEL。所以在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。
typedef struct objc_object *id;
id是一个 objc_object 结构类型的指针,是一个指向类实例的指针
它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; };
objc_object 结构体包含一个isa指针,可以通过这个指针找到对象所属的类
当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后即运行这个方法。
typedef struct objc_class *Class;
(本质上是Class 是一个指向objc_class结构体的指针,于是我们就可以在这个结构体里面找到相应的信息)
- objc_class里面也有一个isa指针,这个指针是指向meteClass(元类)
元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。元类也是类,也是对象,最终指向根元类(root meteClass),根元类isa指针指向自己,形成闭环。 - Class super_class 父类,如果是最顶层的根类它为NULL。
- const char *name 类名
- long version/info 版本信息,默认是0/标识
- long instance_size 该类实例变量大小
- struct objc_ivar_list *ivars 和 struct objc_method_list **methodLists
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_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;
}
可以理解为objc_ivar_list结构体存储着objc_ivar数组列表(变量数组),而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表(方法数组),而objc_method结构体存储了类的某个方法的信息。
- struct objc_cache *cache 存储被调用过的方法,提高效率
- struct objc_protocol_list *protocols 指向该类的协议
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
// 方法名类型为SEL,相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
// 方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。
// method_imp指向了方法的实现,本质上是一个函数指针。
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
} OBJC2_UNAVAILABLE;
typedef id (*IMP)(id, SEL, ...);
它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。
你会发现IMP指向的方法与objc_msgSend函数类型相同,参数都包含id和SEL类型。每个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的SEL对应的方法实现肯定是唯一的,通过一组id和SEL参数就能确定唯一的方法实现地址;反之亦然。
2. Runtime机制
当看懂第一部分类的定义之后,接下来Runtime机制都是在该基础上进行操作。
Runtime的应用:
1.动态创建一个类(比如KVO的底层实现)
2.动态地为某个类添加属性\方法, 修改属性值\方法
3.遍历一个类的所有成员变量(属性)\所有方法
实质上,以上的是通过相关方法来获取对象或者类的isa指针来实现的。
相关函数
- 增加
增加函数:class_addMethod
增加实例变量:class_addIvar
增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成
增加Protocol:class_addProtocol - 获取
获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName ...
获取属性列表及每个属性的信息:class_copyPropertyList property_getName
获取类本身的信息,如类名等:class_getName class_getInstanceSize
获取变量列表及变量信息:class_copyIvarList
获取变量的值 - 替换
将实例替换成另一个类:object_setClass
替换类方法的定义:class_replaceMethod - 其他常用方法:
交换两个方法的实现:method_exchangeImplementations.
设置一个方法的实现:method_setImplementation.
举例获取所有方法名
u_int count;
// 获取一个类的所有方法名
Method *methods = class_copyMethodList([self class], &count);
for (int i = 0; i < count; i++) {
SEL name = method_getName(methods[i]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"方法名:%@", strName);
}
获取所有方法名例子结果
举例获取该类某一成员变量类型和变量名字
NSLog(@"选择的变量类名是什么:%@", [self nameWithInstance:self.color]);
- (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];
NSLog(@"方法类型:%@", stringType);
if (![stringType hasPrefix:@"@"]) {
continue;
}
if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌!
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}
举例获取该类某一成员变量类型和变量名字
举例修改方法的实现(替换方法的实现)
这里我们用UIViewController的viewWillAppear方法举例
首先新建一个UIViewController的Category
#import <objc/runtime.h>
#import "UIViewController+Tracking.h"
@implementation UIViewController (Tracking)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 获取当前class
Class class = [self class];
// Class class = object_getClass((id)self);
// 获取SEL
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(swizzl_viewWillAppear:);
// 获取Method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 是否添加方法
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// 这里如果originalSelector已经被实现了的话,则返回NO,如果还未被实现,则添加方法(swizzled的IMP)
if (didAddMethod) {
// 返回YES,说明已经把originalSelector和swizzledIMP绑定在一起,现在只需要把swizzledSelector和originalIMP绑定在一起,就实现了交换
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else{
// 这里返回NO,说定originalIMP已经被实现,这里需要交换IMP
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)swizzl_viewWillAppear:(BOOL)animated{
// 以下这个方法已经制定给UIViewController的viewWillAppear方法,不会产生无限循环
// 如果这里调用[self viewWillAppear]的话,则会陷入无限循环
[self swizzl_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
}
@end
2016-04-08 14:09:26.977 WebSocket[2060:124911] viewWillAppear: UINavigationController
2016-04-08 14:09:28.551 WebSocket[2060:124911] viewWillAppear: WSViewController
2016-04-08 14:09:31.623 WebSocket[2060:124911] viewWillAppear: UIInputWindowController
代码的注释可以帮助了解这个过程,在这里还需要提一下的是:
Swizzling应该总是在+load中执行
在Objective-C中,运行时会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用
Swizzling应该总是在dispatch_once中执行
因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施
网友评论