一、前言
1、什么是静态语言:
所谓静态语言,就是在程序运行前决定了所有的类型判断,类的所有成员、方法,在编译阶段就确定好了内存地址。即所有类对象只能访问属于自己的成员变量和方法,否则编译器会直接报错。
2、什么是动态语言:
所谓动态语言,指类型的判断、类的成员变量、方法的内存地址,都是在程序的运行阶段才最终确定,并且还能动态的添加成员变量和方法。也就意味着你调用一个不存在的方法时,编译也能通过,甚至一个对象它是什么类型的并不是表面我们所看到的那样,只有运行之后才能决定其真正的类型。
3、什么是运行时:
所谓运行时,就是程序在运行时做的一些事。苹果提供了一套纯C语言的api,即Runtime。
二、Runtime数据结构
在Objective-C中,使用 [receiver message] 语法时,并不会马上执行 receiver 对象的 message 方法的代码,而是向 receiver 发送一条 message 消息,这条消息可能有 receiver 来处理,也可能转发给其他对象来处理,也可能假装没有接收到这条消息而没有处理。
[receiver message] 被编译器转化为: id objc_msgSend(id self, SEL op, … );
1、SEL:
SEL 是函数 objc_msgSend 第二个参数的数据类型,表示方法选择器。
// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
// 获取一个SEL类型的方法选择器
SEL sel1 = @selector(funcname);
SEL sel2 = sel_registerName("funcname");
// 将SEL转化为字符串
NSString *funcString = NSStringFromSelector(sel1);
2、id:
id 是 objc_msgSend 第一个参数的数据类型,id 是通用类型指针,能够表示任何对象,它其实就是一个指向 objc_object 结构体指针,它包含一个 Class isa 成员,根据 isa 指针就可以顺藤摸瓜找到对象所属的类
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
3、Class:
isa 指针的数据类型就是 Class, Class 表示对象所属的类, Class 其实也是一个 objc_class 结构体指针。
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *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 *` */
- isa 表示一个对象的Class。
- super_class 表示示例对象对应的父类。
- name 表示类名。
- ivars 表示多个成员变量,它指向 objc_ivar_list 结构体,objc_ivar_list 其实就是一个链表,存储多个 objc_ivar,而 objc_ivar 结构体存储类的单个成员变量信息。
- methodLists 表示方法列表,它指向 objc_method_list 结构体的二级指针, objc_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息(可以动态修改 *methodLists的值来添加成员方法,也是Category的实现原理,同样也解析Category不能添加实例变量的原因)。
- cache 用来缓存经常访问的方法,它指向 objc_cache 结构体。
- protocols 用来表示遵循哪些协议。
4、Method:
Method 表示类中的某个方法,它指向 objc_method 结构体指针,它存储了方法名(method_name),方法类型(method_types),方法实现(method_imp)等信息
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
5、Ivar:
Ivar 表示类中的实例变量,它指向 objc_ivar 结构体指针,包含了变量名(ivar_name),变量类型(ivar_type)等信息。
/// An opaque type that represents an instance variable.
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
}
6、IMP:
IMP 本质上就是一个函数指针,指向方法的实现。当你向某个对象发送一条消息,可以由这个函数指针指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
7、Cache:
Cache 其实就是一个存储 Method 的链表,用来缓存经常调用的方法,主要是为了优化方法调用的性能,当调用方法时,优先在Cache查找,如果没有找到,再到 methodLists 查找。
三、消息发送
在Objective-C中,任何方法的调用,本质都是发送消息。也就是说,我们OC调用一个方法是,其实质就是转换为Runtime中的一个函数 objc_msgSend(),这个函数的作用是向obj对象,发送了一条消息,告诉它,你该去执行某个方法。
1、objc_msgSend函数:
- 当对象 receiver 调用方法 message 时,首先根据对象 receiver 的 isa 指针查找它对应的类 class。
- 优先在类 class 的 Cache 中查找 message 方法。
- 如果没找到,就在类的 methodLists 中搜索方法。
- 如果还没有找到,就使用 super_class 指针到父类中的 methodLists 查找。
- 一旦找到 message 这个方法,就执行它实现的 IMP。
- 如果还没找到,有可能消息转发,也有可能忽略了该方法。
四、消息转发
[receiver message] 调用方法时,如果 message 方法在 receiver 对象的类继承体系中,没有找到方法,一般情况下,程序在运行时就会Crash掉,抛出 unrecognized selector sent to.. 类似的异常信息。但在抛出异常之前,还有三次机会按以下顺序让你拯救程序。
- Method Resolution: 由于Method Resolution不能像消息转发那样可以交给其他对象处理,所以只适用于在原来的类中代替掉。
- Fast Forwarding: 可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
- Normal Forwarding: 跟Fast Forwarding一样可以消息转发,但它能能过NSInvocation对象获取更多消息发送的信息,如:target、selector、arguments和返回值等信息。
1、Method Resolution:
Objective-C在运行时调用 + resolveInstanceMethod: 或 + resolveClassMethod: 方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。如果返回NO,运行时就跳转到下一步: 消息转发(Message Forwarding)
// 1、正常情况下
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation Message
- (void)sendMessage:(NSString *)word {
NSLog(@"normal way : send message = %@",word);
}
@end
// 2、注释掉 sendMessage 的实现方法后,覆盖 resolveInstanceMethod 方法:
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation Message
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sendMessage:)) {
class_addMethod([self class],
sel,
imp_implementationWithBlock(^(id self, NSString *word){
NSLog(@"method resolution way : send message = %@",word);
}), "v@*");
}
return YES;
}
@end
其中 v@* 表示方法的返回值和参数,详情参考 Type Encodings 。
2、Fast Forwarding:
如果目标对象实现 - forwardingTargetForSelector: 方法,系统会在运行时调用这个方法,只要这个方法返回的不是nil或者self,也会重启消息发送的过程,把该消息转发给其他对象来处理。否则,就会继续Normal Forwarding.
// 3、注释掉 sendMessage 的实现方法后,覆盖 forwardingTargetForSelector 方法:
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation Message
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(sendMessage:)) {
return [[MessageForwarding alloc] init];
}
return nil;
}
@end
// 转发给该对象处理
@interface MessageForwarding : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation MessageForwarding
- (void)sendMessage:(NSString *)word {
NSLog(@"fast forwarding way : send message = %@",word);
}
@end
3、Normal Forwarding:
如果没有使用Fast Forwarding来消息转发,最后只有使用 Normal Forwarding来进行消息转发。
它首先调用 - methodSignatureForSelector: 方法来获取函数的参数和返回值。如果返回nili,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用 - forwardInvocation: 方法。
// 4、注释掉 sendMessage 的实现方法后,通过 methodSignatureForSelector 方法获取函数签名,并通过 forwardInvocation 方法来转发处理:
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation Message
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
MessageForwarding *messageForwarding = [[MessageForwarding alloc] init];
if ([messageForwarding respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding];
}
}
@end
// 转发给该对象处理
@interface MessageForwarding : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation MessageForwarding
- (void)sendMessage:(NSString *)word {
NSLog(@"fast forwarding way : send message = %@",word);
}
@end
五、我们可以用运行时做什么
1、互换方法的实现:
因为 selector 和 IMP 之间的关系是在运行时才决定的,所以我们可以通过 void method_exchangeImplementations(Method m1, Method m2) 方法来改变 selector 和 IMP 的对应关系。
@implementation NSObject (ExchangeMethod)
// 此方法会在此类第一次被加进内存是调用,且仅调用一次
+ (void)load {
// 获取系统的dealloc方法
Method m1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
// 获取自己声明的my_dealloc
Method m2 = class_getInstanceMethod(self, @selector(my_dealloc));
// 交换两个方法的实现,即调用dealloc方法时,会实现my_dealloc,调用my_dealloc方法时,才会调用
method_exchangeImplementations(m1, m2);
}
- (void)my_dealloc {
// do something
NSLog(@"my_dealloc");
// 这里需要调用自己,调用自己就是调用原来的dealloc进行释放操作
[self my_dealloc];
}
@end
2、动态添加方法:
动态语言调用一个没有的方法时,编译阶段不会报错,但是运行时便会抛出异常闪退,但我们可以动态为某个了添加方法。这里用到的其实就是上面提到的,消息转发时的拯救程序机制。
3、动态添加属性:
有时我们想为系统的类或者一些不便修改的第三方框架的类,增加一些自定义的属性,以满足开发的需求。比如使用类目,但是类目只能为一个类添加方法,不能添加属性。这是便可用到下面的方法:
#import "UIView+TintColor.h"
#import <objc runtime="runtime">
@implementation UIView (TintColor)
- (nullable NSString *)tintColorName {
return objc_getAssociatedObject(self, @selector(tintColorName));
}
- (void)setTintColorName:(NSString *)tintColorName {
if (!tintColorName || tintColorName.length == 0) {
return;
}
UIColor *color;
SEL sel = NSSelectorFromString(tintColorName);
if ([UIColor respondsToSelector:sel]) {
color = [UIColor performSelector:sel];
}
if (!color) {
return;
}
self.tintColor = color;
objc_setAssociatedObject(self,
@selector(tintColorName),
tintColorName,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
4、获取类中所有的成员变量和属性:
在开发中,有时会遇到想要改变系统自带类的某一个值,却找不到与之对应的api,便可用运行时来获取该类的所有成员变量。
// 获取类中所有的成员变量
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
// 获取类中所有的属性
objc_property_t class_getProperty(Class cls, const char *name)
unsigned int count;
Ivar *ivars = class_copyIvarList([UIButton class], &count);
for (NSInteger i = 0; i < count; i++) {
// 取出成员变量
Ivar ivar = ivars[i];
// 获取属性名字
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获取属性类型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSLog(@"%@:%@",type,name);
}
六、结束语
将XCode升级到6后,报Too many arguments to function call, expected 0, have *,在XCode5.1里能编译通过的,到xcode6就报错
objc_msgSend(self.beginRefreshingTaget, self.beginRefreshingAction, self);
Too many arguments to function call, expected 0, have *
解决办法:
选中项目 - Project - Build Settings - Enable Strict Checking of objc_msgSend Calls 将其设置为 NO 即可