runtime:Objective-C是动态语言,它将很多静态语言在编译和链接时做的事放到了运行时,这个运行时系统就是runtime
。
runtime
其实就是一个库,它基本上是用C和汇编写的一套API,这个库使C语言有了面向对象的能力。
静态语言
:在编译的时候会决定调用哪个函数。
动态语言(OC)
:在运行的时候根据函数的名称找到对应的函数来调用。
isa
:OC中,类和类的实例在本质上没有区别,都是对象,任何对象都有isa
指针,它指向类或者元类。
isa
:是一个Class
类型的指针。每个实例对象有个isa
的指针,他指向对象的类,而Class
里也有个isa
的指针,指向meteClass
(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa
指针,它的isa
指针最终指向的是一个根元类(root meteClass)。根元类的isa
指针指向本身,这样形成了一个封闭的内循环。
SEL
:SEL(选择器)
是方法的selector
的指针。方法的selector
表示运行时方法的名字。OC
在编译时,会依据每一个方法的名字、参数,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL
。
IMP
:IMP
是一个函数指针,指向方法最终实现的首地址。SEL
就是为了查找方法的最终实现IMP
。
Method
:用于表示类定义中的方法,它是结构体中包含一个SEL
和IMP
,相当于在SEL
和IMP
之间做了一个映射。
消息机制
:任何方法的调用本质就是发送一个消息。编译器会将消息表达式[receiver message]
转化为一个消息函数objc_msgSend(receiver,selector)
。
Runtime的使用
:获取属性列表,获取成员变量列表,获得方法列表,获取协议列表,方法交换(黑魔法),动态的添加方法,调用私有方法,为分类添加属性。
一、什么是Runtime运行时
runtime主要做了两件事情:
1.封装:
runtime
把对象用C语言的结构体来表示,方法用C语言的函数来表示。这些结构体和函数被runtime
封装后,我们就可以在程序运行的时候,对类/对象/方法进行操作。
2.寻找方法的最终执行:当执行
[receiver message]
的时候,相当于想receiver
发送一条message
。runtime
会根据receiver
能否处理这条message
,从而做出不同的反应。
在OC中,类是用Class来表示的,而Class实际上是一个指向objc_class结构体的指针。
struct objc_class{
Class isa OBJC_ISA_AVAILABILITY; // isa指针
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
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;
Cache
用于缓存最近使用的方法。一个类只有一部分方法是常用的,每次调用一个方法之后,这个方法就被缓存到cache
中,下次调用时runtime
会先在cache
中查找,如果cache
中没有,才会去methodList
中查找。有了cache
,经常用到的方法的调用效率就提高了!
你只要记住,runtime
其实就是一个库,它是一套API,这个库使C语言有了面向对象的能力。我们可以运用runtime
这个库里面的各种方法,在程序运行时的时候对类/实例对象/变量/属性/方法等进行各种操作。
二、 什么是iso指针
在解释isa
之前,你需要知道,在Objective-C中,所有的类自身也是一个对象,我们可以向这个对象发送消息(调用类方法)。
先来看一下runtime
中实例对象的结构体objc_object
。
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
从结构体中可以看到,这个结构体只包含一个指向其类的isa指针。
isa指针的作用:当我们向一个对象发送消息时,runtime
会根据这个对象的isa
指针找到这个对象所属的类,在这个类的方法列表及父类的方法列表中,寻找与消息对应的selector
指向的方法,找到后就运行这个方法。
要彻底理解isa,还需要了解一下元类的概念。
(meta-class)元类中存储着一个类的所有类方法。
向一个对象发送消息时,
runtime
会在这个对象所属的类的方法列表中查找方法;
向一个类发送消息时,会在这个类的meta-class(元类)
的方法列表中查找。
meta-class
是一个类,也可以向它发送消息,那么它的isa又是指向什么呢?为了不让这个结构无限延伸下去,Objective-C的设计者让所有的meta-class
的isa
指向基类(NSObject)
的meta-class
,而基类的meta-class
的isa
指针是指向自己(NSObject)
三、 什么是SEL,IMP,Method
SEL
SEL
又叫选择器,是方法的selector
的指针。
typedef struct objc_selector *SEL;
方法的selector
用于表示运行时方法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL
。
两个类之间,无论它们是父子关系,还是没有关系,只要方法名相同,那么方法的SEL就是一样的,每一个方法都对应着一个SEL,所以在Objective-C同一个类中,不能存在两个同名的方法,即使参数类型不同也不行。
SEL是一个指向方法的指针,是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,所以速度上非常优秀,它的存在只是为了加快方法的查询速度。
不同的类可以拥有相同的selector
,不同类的实例对象执行相同的selector时,会在格子的方法列表中取根据selector
寻找对应的IMP
。SEL
就是为了查找方法的最终实现IMP
。
IMP
IMP实际上是一个函数指针,指向方法实现的首地址。代表了方法的最终实现。
id (*IMP)(id,SEL,…)
第一个参数是指向self
的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),省略号是方法的参数。
每个方法对应唯一的SEL
,通过SEL
快速准确的获得对应的IMP
,取得IMP
后,就获得了执行这个方法代码了。
Method
Method是用于表示类的方法。
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
Method
结构体中包含一个SEL
和IMP
,实际上相当于在SEL
和IMP
之间作了一个映射。有了SEL
,我们便可以找到对应的IMP
,从而调用方法的实现代码。
四、 什么是消息机制
当执行了[receiver message]
的时候,相当于向receiver
发送一条message
。runtime
会根据receiver
能否处理这条message
,从而做出不同的反应。
方法(消息机制)的调用流程
消息直到运行时才绑定到方法的实现上。编译器会将消息表达式[receiver message]
转化为一个消息函数,即objc_msgSend(receiver,selector)
。
objc_msgSend(receiver,selector)
// 如果消息中还有其它参数
objc_msgSend(receiver,selector,arg1,arg2,…)
objc_msgSend
做了如下事情:
1.通过对象的
isa
指针获取类的结构体。
2.在结构体的方法表里查找方法的selector
。
3.如果没有找到selector
,则通过objc_msgSend
结构体中指向父类的指针找到父类,并在父类的方法表里找到方法的selector
。
4.依次会一直找到NSObject
。
5.一旦找到selector
,就会获取到方法实现IMP
。
6.传入相应的参数来执行方法的具体实现。
7.如果最终没有定位到selector
,就会走消息转发流程。
消息转发机制
以[receiver message]
的方式调用方法,如果receiver
无法响应message
,编译器会报错。但如果是以performSelecror
来调用,则需要等到运行时才能确定object
是否能接收message
消息。如果不能,则程序崩溃。
当我们不能确定一个对象是否能接收某个消息时,会先调用respondsToSelector:
来判断一下
if ([self respondsToselector:@selector(doSomething)]) {
[self performSelector:@selector(doSomething)];
}
如果不使用respondsToSelector:
来判断,那么这就可以用到“消息转发”机制。
当对象无法接收消息,就会启动消息转发机制,通过这一机制,告诉对象如何处理未知的消息。
这样就就可以采取一些措施,让程序执行特定的逻辑,从而避免崩溃。措施分为三个步骤。
1.动态方法解析
对象接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:
(实例方法)或者+resolveClassMethod:
(类方法)。
在这个方法中,我们有机会为该未知消息新增一个“处理方法”。使用该“处理房啊”的前提是已经实现,只需要在运行时通过class_addMethod
函数,动态的添加到类里面就可以了。代码如下。
void functionForMethod1(id self,SEL _cmd) {
NSLog(@“%@,%p”,self,_cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(self);
if ([selectorString isEqualToString:@“method1”]) {
class_addMethod(self.class,@selector(method1),(IMP)functionForMethod1,”@:”);
}
return [super resolveInstanceMethod:sel];
}
2. 备用接收者
如果在上一步无法处理消息,则runtime
会继续调下面的方法。
- (id)forwardingTargetForSelector:(SEL)aSelector
如果这个方法返回一个对象,则这个对象会作为消息的新接收者。注意这个对象不能是self自身,否则就是出现无限循环。如果没有指定对象来处理aSelector
,则应该return [super forwardingTargetForSelector:aSelector]
。
但是我们只将消息转发到另一个能处理该消息的对象上,无法对消息进行处理,例如操作消息的参数和返回值。
@interface RuntimeTest() {
RuntimeMethod *_TestMethod;
}
@implementation RuntimeTest
- (instancetype)init {
self = [super init];
if (self != nil) {
_TestMethod = [[TestMethod alloc] init];
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@“forwardingTargetForSelector”);
NSString *selectorString = NSStringFromSelector(aSelector);
// 将消息转发给_TestMethod来处理
if ([selectorString isEqualToString:@“method2”]) {
return _TestMethod;
}
return [super forwardingTargetForSelector:aSelector];
}
3.完整消息转发
如果在上一步还是不能处理未知消息,则唯一能做的就是启用完整的消息转发机制。此时会调用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([RuntimeMethod instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invakeWithTarget:_TestMethod];
}
}
这是最后一次机会将消息转发给其它对象。创建一个表示消息的NSInvocation
对象,把与消息的有关全部细节封装在anInvocation
中,包括selector
,目标(target)和参数。在forwardInvocation
方法中将消息转发给其它对象。
forwardInvocation:方法的实现有两个任务:
1.定位可以响应封装在anInvocation中的消息的对象。
2.使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,runtime会提取这一结果并发送到消息的原始发送者。
在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改。另外,若发现消息不应由本类处理,则应调用父类的同名方法,以便集成体系中的每个类都有机会处理。
另外,必须重写下面的方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息转发机制从这个方法中获取消息来创建NSInvocation对象。完整的示例如下:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([RuntimeMethod instanceRespondToSelector:aSelector]) {
signature = [RuntimeMethod instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([RuntimeMethod instanceRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_TestMethod];
}
}
NSObject
的forwardInvocation
方法只是调用了doesNotRecognizeSelector
方法,它不会转发任何消息。如果不在以上所述的三个步骤中处理未知消息,则会引发异常。
forwardInvocation
就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象,取决于具体的实现。
消息的转发机制可以用下图来帮助理解。
五、 runtime的使用
- 获取属性列表
- 获取成员变量列表
- 获取方法列表
- 获取协议列表
- 方法交换(黑魔法)
- 添加方法
- 调用私有方法
- 为分类添加属性
1.获取属性列表
运用class_copyPropertyList
方法来获得属性列表,遍历把属性加入数组中,最终返回此数组。其中[self dictionaryWithProperty:properties[i]]
方法是用来拿到属性的描述,例如copy
,readonly
,NSString
等信息。
/** 属性列表 */
- (NSArray *)propertiesInfo
{
return [[self class] propertiesInfo];
}
+ (NSArray *)propertiesInfo
{
NSMutableArray *propertieArray = [NSMutableArray array];
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([self class],&propertyCount);
for (int index = 0; index < propertyCount; index++)
{
[propertieArray addObject:({
NSDictionary *dictionary = [self dictionaryWithProperty:properties[index]];
dictionary;
})];
}
free(properties);
return propertieArray;
}
// 格式化之后的属性列表
+ (NSArray *)propertiesWithCodeFormat
{
NSMutableArray *array = [NSMutableArray array];
NSArray *properties = [[self class] propertiesInfo];
for (NSDictionary *item in properties)
{
NSMutableString *format = ({
NSMutableString *formatString = [NSMutableString stringWithFormat:@“property ”];
// attribute
NSArray *attribute = [item objectForKey:@“attribute”];
attribute = [attribute sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
if (attribute && attribute.count > 0)
{
NSString *attributeStr = [NSString stringWithFormat:@“(%@)”,[attribute componentsJoinedByString:@“, ”]];
[formatString appendString:attributeStr];
}
// type
NSString *type = [item objectForKey:@“type”];
if (type)
{
[formatString appendString:@“ ”];
[formatString appednString:type];
}
// name
NSString *name = [item objectForKey:@“name”];
if (name)
{
[formatString appendString:@“ ”];
[formatString appendString:name];
[formatString appendString:@“;”];
}
formatString;
});
[array addObject:format];
}
return array;
}
+ (NSDictionary *)dictionaryWithProperty:(objc_property_t)property
{
NSMutableDictionary *result = [NSMutableDictionary dictionary];
// name
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[result setObject:propertyName forKey:@“name”];
// attribute
NSMutableDictionary *attributeDictionary = ({
[NSMutableDictionary dictionary];
unsigned int attributeCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property,&attributeCount);
for (int index = 0; index < attributeCount; index++)
{
NSString *name = [NSString stringWithCString:attrs[index].name encoding:NSUTF8StringEncoding];
NSString *value = [NSString stringWithCString:attrs[index].value encoding:NSUTF8StringEncoding];
}
free(attrs);
dictionary;
});
NSMutableArray *attributeArray = [NSMutableArray array];
// R
if ([attributeDictionary objectForKey:@“R”])
{
[attributeArray addObject:@“readonly”];
}
// C
if ([attributeDictionary objectForKey:@“C”])
{
[attributeArray addObject:@“copy”];
}
// &
if ([attributeDictionary objectForKey:@“&”])
{
[attributeArray addObject:@“strong”];
}
// N
if ([attributeDictionary objectForKey:@“N”])
{
[attributeArray addObject:@“nonatomic”];
} else
{
[attributeArray addObject:@“atomic”];
}
// G<name>
if ([attributeDictionary objectForKey:@“G”])
{
[attributeArray addObject:[NSString stringWithFormat:@“getter=%@”,[attributeDictionary objectForKey:@“G”]]];
}
// S<name>
if ([attributeDictionary objectForKey:@“S”])
{
[attributeArray addObject:[NSString stringWithFormat:@“setter=%@”,[attributeDictionary objectForKey:@“G”]]];
}
// D
if ([attributeDictionary objectForKey:@“D”])
{
[result setObject:[NSNumber numberWithBool:YES] forKey:@“isDynamic”];
} else
{
[result setObject:[NSNumber numberWithBool:NO] forKey:@“isDynamic”];
}
// W
if ([attributeDictionary objectForKey:@“W”])
{
[attributeArray addObject:@“weak”];
}
// P
if ([attributeArray objectForKey:@“P”])
{
}
// T
if ([attributeDictionary objectForKey:@“T”])
{
NSDictionary *typeDic = @{
@“c”:@“char”,
@“i”:@“int”,
@“s”:@“short”,
@“l”:@“long”,
@“q”:@“long long”,
@“C”:@“unsigned char”,
@“I”:@“unsigned int”,
@“S”:@“unsigned short”,
@“L”:@“unsigned long”,
@“Q”:@“unsigned long long”,
@“f”:@“float”,
@“d”:@“double”,
@“B”:@“BOOL”,
@“v”:@“void”,
@“*”:@“char *”,
@“@”:@“id”,
@“#”:@“Class”,
@“:”:@“SEL”,
};
// TODO:An array
NSString *key = [attributeDictionary objectForKey:@“T”];
id type_str = [typeDic objectForKey:key];
if (type_str == nil)
{
if ([[key substringToIndex:1] isEqualToString:@“@”] && [key rangeOfString:@“?”].location == NSNotFound)
{
type_str = [[key substringWithRange:NSMakeRange(2,key.length - 3)] stringByAppendingString:@“*”] ;
}
else if ([[key substringToIndex:1] isEqualToString:@“^”])
{
id str = [typeDic objectForKey:[key substringFromIndex:1]];
if (str)
{
type_str = [NSString stringWithFormat:@“%@ *”,str];
}
}
else
{
type_str = @“unknow”;
}
}
[result setObject:type_str forKey:@“type”];
}
[result setObject:attributeArray forKey:@“attribute”];
return result;
}
2.获取成员变量列表
运用class_copyIvarList
方法来获得变量列表,通过遍历把变量加入到数组中,最终返回此数组。其中[[self class] decodeType:ivar_getTypeEncoding(ivars[i])]
方法是用来拿到变量的类型,例如char
,int
,unsigned long
等信息。
/** 成员变量列表 */
- (NSArray *)ivarInfo
{
return [[self class] invarInfo];
}
+ (NSArray *)ivarInfo
{
unsigned int outCount;
Ivar *ivars = class_copyIvarList([self class],&outCount);
for (int index = 0; index < outCount; index++)
{
NSString *type = [[self class] decodeType:ivar_getTypeEncoding(ivars[index])];
NSString *name = [NSString stringWithCString:ivar_getName(ivars[index]) encoding:NSUTF8StringEncoding];
NSString *ivarDescription = [NSString stringWithFormat:@“%@ %@”,type,name];
[result addObject:ivarDescription];
}
free(ivars);
return result.count ? [result copy] : nil;
}
+ (NSString *)decodeType:(const char *)cString
{
if (!strcmp(cString,@encode(char)))
{
return @“char”;
}
if (!strcmp(cString,@encode(int)))
{
return @“int”;
}
if (!strcmp(cString,@encode(short)))
{
return @“short”;
}
if (!strcmp(cString,@encode(long)))
{
return @“long”;
}
if (!strcmp(cString,@encode(long long)))
{
return @“long long”;
}
if (!strcmp(cString,@encode(unsigned char)))
{
return @“unsigned char”;
}
if (!strcmp(cString,@encode(unsigned int)))
{
return @“unsigned int”;
}
if (!strcmp(cString,@encode(unsigned short)))
{
return @“unsigned short”;
}
if (!strcmp(cString,@encode(unsigned long)))
{
return @“unsigned long”;
}
if (!strcmp(cString,@encode(unsigned long long)))
{
return @“unsigned long long”;
}
if (!strcmp(cString,@encode(float)))
{
return @“float”;
}
if (!strcmp(cString,@encode(double)))
{
return @“double”;
}
if (!strcmp(cString,@encode(bool)))
{
return @“bool”;
}
if (!strcmp(cString,@encode(_Bool)))
{
return @“_Bool”;
}
if (!strcmp(cString,@encode(void)))
{
return @“void”;
}
if (!strcmp(cString,@encode(char *)))
{
return @“char *”;
}
if (!strcmp(cString,@encode(id)))
{
return @“id”;
}
if (!strcmp(cString,@encode(Class)))
{
return @“class”;
}
if (!strcmp(cString,@encode(SEL)))
{
return @“SEL”;
}
if (!strcmp(cString,@encode(BOOL)))
{
return @“BOOL”;
}
NSString *result = [NSString stringWithCString:cString encoding:NSUTF8StringEncoding];
if ([[result substringToIndex:1] isEqualToString@“@”] && [result rangeOfString:@“?”].location == NSNotFound)
{
result = [[result substringWithRange:NSMakeRange(2,result.length - 3)] stringByAppendingString:@“*”];
}
else
{
if ([[result substringToIndex:1] isEqualToString:@“^”])
{
result = [NSString stringWithFormat:@“%@ *”,[NSString decodeType:[result substringFromIndex:1] cStringUsingEncoding:NSUTF8StringEncoding]];
}
}
retuen result;
}
3.获取方法列表
通过runtime
的class_copyMethodList
方法来获取方法列表,通过遍历把方法加入到数组中,最终返回此数组。
// 对象方法列表
- (NSArray *)instanceMethodList
{
u_int count;
NSMutableArray *methodList = [NSMutableArray array];
Method *methods = class_copyMethodList([self class],&count);
for (int index = 0; index < count; index++)
{
SEL name = method_getName(methods[index]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
[methodList addObject:strName];
}
free(methods);
return methods;
}
+ (NSArray *)instanceMethodList
{
u_int count;
NSMutableArray *methodList = [NSMutableArray array];
Method *methods = class_copyMethodList([self class],&count);
for (int index = 0; index < count; index++)
{
SEL name = method_getName(methods[index]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
}
free(methods);
return methodList;
}
// 获取类方法列表
+ (NSArray *)classMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(object_getClass(self),&count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int index = 0; index < count; index++)
{
Method method = methodList[index];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
4.获取协议列表
运用class_copyProtocolList
方法来获得协议列表。
//协议列表
- (NSDictionary *)protocolList
{
retuen [[self class] protocolList];
}
+ (NSDictionary *)protocolList
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
unsigned int count;
Protocol * __unsigned_unretained *protocols = class_copyProtocolList([self class],&count);
for (int index = 0; index < count; index ++)
{
Protocol *protocol = protocols[index];
NSString *protocolName = [NSString stringWithCString:protocol_getName(protocol)];
NSMutableArray *superProtocolArray = ({
NSMutableArray *array = [NSMutableArray array];
unsigned int superProtocolCount;
Protocol * __unsafe_unretained *superProtocols = protocol_copyProtocolList(protocol,&superProtocolCount);
for (int ii = 0; ii < superProtocolCount; ii++)
{
Protocol *superProtocol = superProtocols[ii];
NSString *superProtocolName = [NSString stringWithCString:protocol_getName(superProtocol) encoding:NSUTF8StringEncoding];
[array addObject:superProtocolName];
}
free(superProtocols);
array;
});
[dictionary setObject:superProtocolArray forKey:protocolName];
}
free(protocols);
return dictionary;
}
5.方法交换(黑魔法)
runtime
的重头戏,被称作黑魔法的方法交换Swizzling
。交换方法是在method_exchangeImplementations
里发生的。
/** 交换实例方法 */
+ (void)SwizzlingInstanceMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
Class class = [self class];
SEL originalSelector = oldMethod;
SEL swizzledSelector = newMethod;
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
if (didAddMethod)
{
class_replaceMethod(class,swizzledSelector,method_getImpLementation(originalMethod),method_getTypeEncoding(originalMethod));
}
else
{
method_exchangeImplementations(originalMethod,swizzledMethod);
}
}
/** 交换类方法 */
+ (void)SwizzlingClassMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
Class class = object_getClass((id)self);
SEL originalSelector = oldMethod;
SEL swizzledSelector = newMethod;
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
if (didAddMethod)
{
class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
}
else
{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
使用Swizzling
的过程中要注意两个问题:
Swizzling要在+load方法中执行
运行时会自动调用每个类的两个方法,
+load
与+initialize
。
+load
会在main
函数之前调用,并且一定会调用。
+initialize
是在第一次调用类方法或实例方法之前被调用,有可能一直不被调用一般使用
Swizzling
是为了影响全局,所以为了方法交换一定成功,Swizzling
要放在+load
中执行。
新建工程创建类,A类和B类的
load
顺序是无序的 但是运行过一次后load
顺序就固定了
假如A类先load
在A类的load
方法中创建B类的实例 会使B类先调用initialize
方法 后调用load
方法
正常情况下,会先调用load
方法,后调用initialize
方法
Swizzling要在dispatch_once中执行
Swizzling
是为了影响全局,所以只让它执行一次就可以了,所以要放在dispatch_once
中。
6.添加方法
/** 添加方法 */
+ (void)addMethodWithSEL:(SEL)methodSEL methodIMP:(SEL)methodIMP
{
Method method = class_getInstanceMethod(self, methodIMP);
IMP getMethodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
class_addMethod(self, methodSEL, getMethodIMP, types);
}
添加方法的运用
接收到未知的消息时,首先会调用所属类的类方法
+resolveInstanceMethod:(实例方法)
或+resolveClassMethod:(类方法)
。
第一种情况是,根据已知的方法名动态的添加一个方法。
第二种情况是,直接添加一个方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 1.如果方法名是addMethod,就添加一个MethodOne方法来执行
if (sel == NSSelectorfromString(@“addMethod”))
{
class_addMethod(self, sel, (IMP)MethodOne, “v@:”);
return YES;
}
// 2.如果找不到方法,就添加一个addMethod来执行
[self addMethodWithSEL:sel methodIMP:@selector(addMethod)];
return YES;
}
7.调用私有方法
由于消息机制,runtime
可以通过objc_msgSend
来帮我们调用一些私有方法
TestRuntime *test = [[TestRuntime alloc] init];
objc_msgSend(test, @selector(privateMethod));
使用objc_msgSend
需要注意两个问题:
1.需要导入头文件
#import <objc/message.h>
2.在Build Settings
里设置Enable Strict Checking of objc_msgSend Calls
的值为NO
8.为分类添加属性
在分类中属性不会自动生成实例变量和存取方法,但是可以运用runtime
的关联对象(Associated Object)
来解决这个问题。
@interface UIImage (swizzlingImage)
// 用关联对象为分类添加属性
@property (nonatomic, copy) NSString *categoryProperty;
@end
@implementation UIImage (swizzlingImage)
// getter
- (NSString *)categoryProperty
{
return objc_getAssociatedObject(self, _cmd);
}
// setter
- (void)setCategoryProperty:(NSString *)categoryProperty
{
objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
使用objc_getAssociatedObject
和objc_setAssociatedObject
来做到存取方法,使用关联对象模拟实例变量。下面是两个方法的原型:
id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
方法中的@selector(categoryProperty)
就是参数key
,使用@selector(categoryProperty)
作为key
传入,可以确保key
的唯一性。
OBJC_ASSOCIATION_COPY_NONATOMIC
是属性修饰符
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy)
{
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_PETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};