1 对象模型图
- NSObject 的类中定义了实例方法,例如 -(id)init 方法 和 - (void)dealloc 方法。
- NSObject 的元类中定义了类方法,例如 +(id)alloc 方法 和 + (void)load 、+ (void)initialize 方法。
- NSObject 的元类继承自 NSObject 类,所以 NSObject 类是所有类的根,因此 NSObject 中定义的实例方法可以被所有对象调用,例如 - (id)init 方法 和 - (void)dealloc 方法。
- NSObject 的元类的 isa 指向自己。
2 实例对象在内存中的结构
实例的内存结构是由其类决定的,已经存在的类,是无法动态加成员变量的。因为如果类加了成员变量,该类的所有实例,其内存结构必须做相应的增加,试想一下如果是增加了NSObject类的成员变量,那内存中所有的实例都得修改,成本太高。
3 消息发送和转发
- 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain, release 这些函数了。
- 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
- 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
- 如果 cache 找不到就找一下方法分发表。
- 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
-
如果还找不到就要开始进入动态方法解析和消息转发
动态方法解析
- 重载resolveInstanceMethod:和resolveClassMethod:方法分别添加实例方法实现和类方法实现
- class_addMethod函数完成向特定类添加特定方法实现
- 如果返回YES,就会重新启动一次消息发送过程
#import <Foundation/Foundation.h>
@interface Student : NSObject
+ (void)learnClass:(NSString *) string;
- (void)goToSchool:(NSString *) name;
@end
#import "Student.h"
#import <objc/runtime.h>
@implementation Student
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(learnClass:)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(goToSchool:)) {
class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
+ (void)myClassMethod:(NSString *)string {
NSLog(@"myClassMethod = %@", string);
}
- (void)myInstanceMethod:(NSString *)string {
NSLog(@"myInstanceMethod = %@", string);
}
@end
重定向
- 通过重载- (id)forwardingTargetForSelector:(SEL)aSelector方法替换消息的接受者为其他对象
- 替换类方法的接受者,需要覆写 + (id)forwardingTargetForSelector:(SEL)aSelector 方法,并返回类对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
转发
- 重写methodSignatureForSelector:方法,否则会抛异常
- 重写forwardInvocation:,forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的”吃掉“某些消息,因此没有响应也没有错误。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
整体流程图
相关问题
编译器如何编译成objc_msgSend
objc_msgSend是汇编语言,其实在 [objc-msg-x86_64.s] 中包含了多个版本的 objc_msgSend方法,它们是根据返回值的类型和调用者的类型分别处理的:
- objc_msgSendSuper:向父类发消息,返回值类型为 id
- objc_msgSend_fpret:返回值类型为 floating-point,其中包含 objc_msgSend_fp2ret入口处理返回值类型为 long double的情况
- objc_msgSend_stret:返回值为结构体
- objc_msgSendSuper_stret:向父类发消息,返回值类型为结构体
这也是为什么 objc_msgSend要用汇编语言而不是 OC、C 或 C++ 语言来实现,因为单独一个方法定义满足不了多种类型返回值,有的方法返回 id,有的返回 int
。考虑到不同类型参数返回值排列组合映射不同方法签名(method signature)的问题,那 switch 语句得老长了。。。这些原因可以总结为 [Calling Convention],也就是说函数调用者与被调用者必须约定好参数与返回值在不同架构处理器上的存取规则,比如参数是以何种顺序存储在栈上,或是存储在哪些寄存器上。除此之外还有其他原因,比如其可变参数用汇编处理起来最方便,因为找到 IMP 地址后参数都在栈上。要是用 C++ 传递可变参数那就悲剧了,prologue 机制会弄乱地址(比如 i386 上为了存储 ebp
向后移位 4byte),最后还要用 epilogue 打扫战场。而且汇编程序执行效率高,在 Objective-C Runtime 中调用频率较高的函数好多都用汇编写的。
消息缓存
struct objc_cache {
uintptr_t mask; /* total = mask + 1 */
uintptr_t occupied;
cache_entry *buckets[1];
};
嗯,objc_cache的定义看起来很简单,它包含了下面三个变量:
1)、mask:可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1
2)、occupied:被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目
3)、buckets:用数组表示的hash表,cache_entry类型,每一个cache_entry代表一个方法缓存
(buckets定义在objc_cache的最后,说明这是一个可变长度的数组)
消息是如何缓存起来的
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
buckets = (cache_entry **)cache->buckets;
for (index = CACHE_HASH(sel, cache->mask);
buckets[index] != NULL;
index = (index+1) & cache->mask)
{
// empty
}
buckets[index] = entry;
//hash的方式
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
方法缓存存在什么地方?
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
我们看到在类的定义里就有cache字段,没错,类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份。
类的方法缓存大小有没有限制?
/* When _class_slow_grow is non-zero, any given cache is actually grown
* only on the odd-numbered times it becomes full; on the even-numbered
* times, it is simply emptied and re-used. When this flag is zero,
* caches are grown every time. */
static const int _class_slow_grow = 1;
其实不用再看进一步的代码片段,仅从注释我们就可以看到问题的答案。注释中说明,当_class_slow_grow是非0值的时候,只有当方法缓存第奇数次满(使用的槽位超过3/4)的时候,方法缓存的大小才会增长(会清空缓存,否则hash值就不对了);当第偶数次满的时候,方法缓存会被清空并重新利用。 如果_class_slow_grow值为0,那么每一次方法缓存满的时候,其大小都会增长。
所以单就问题而言,答案是没有限制,虽然这个值被设置为1,方法缓存的大小增速会慢一点,但是确实是没有上限的。
其他问题:编译器如何编译成objc_msgSend,消息Cache机制,消息转发机制,objc_msgSend的各个版本,objc_msgSend的实现,跳板机制
Method Swizzling
- class_replaceMethod 替换类方法的定义,当类中没有想替换的原方法时,该方法会调用class_addMethod来为该类增加一个新方法,也因为如此,class_replaceMethod在调用时需要传入types参数,而method_exchangeImplementations和method_setImplementation却不需要
- method_exchangeImplementations 交换 2 个方法的实现,其内部实现相当于调用了 2 次method_setImplementation方法
- method_setImplementation 设置 1 个方法的实现
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(aClass, originalSelector);
// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);
BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
参考文章
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
http://blog.devtang.com/2013/10/15/objective-c-object-model/
http://tech.meituan.com/DiveIntoMethodCache.html