『ios』-objc_msgSend 全面解析(一)
看别人写的框架,看到了如下代码:
id (*objc_msgSendGetCellIdentifier)(id self, SEL _cmd) = (void *)objc_msgSend;
CGFloat (*objc_msgSendGetCellHeight)(id self, SEL _cmd) = (void *)objc_msgSend;
id (*objc_msgSendCreateCellWithIndexPath)(id self, SEL _cmd, NSIndexPath *) = (void *)objc_msgSend;
Class cellClazz = NSClassFromString(model.containerCellClass);
if ([(id)cellClazz respondsToSelector:@selector(cellHeight)]) {
CGFloat cellHeight = objc_msgSendGetCellHeight(cellClazz, NSSelectorFromString(@"cellHeight"));
return cellHeight;
}
NSString *identifier = objc_msgSendGetCellIdentifier(cellClazz, NSSelectorFromString(@"cellReuseIdentifier"));
SideSlipBaseTableViewCell *templateCell = [self.templateCellDict objectForKey:identifier];
if (!templateCell) {
templateCell = objc_msgSendCreateCellWithIndexPath(cellClazz, NSSelectorFromString(@"createCellWithIndexPath:"), indexPath);
templateCell.delegate = self;
[self.templateCellDict setObject:templateCell forKey:identifier];
}
//update
[templateCell updateCellWithModel:&model indexPath:indexPath];
cell的.h文件
+ (NSString *)cellReuseIdentifier;
+ (CGFloat)cellHeight;
+ (instancetype)createCellWithIndexPath:(NSIndexPath *)indexPath;
cell的.m文件
+ (NSString *)cellReuseIdentifier {
NSAssert(NO, @"\nERROR: Must realize this function in subClass %s", __func__);
return nil;
}
+ (instancetype)createCellWithIndexPath:(NSIndexPath *)indexPath {
NSAssert(NO, @"\nERROR: Must realize this function in subClass %s", __func__);
return nil;
}
以前专门看过这块的源码,可是时间长了又忘了,从现在开始记录吧。
一点一点来吧、
先看下面代码
//编译前
[xh send];
//编译后
((void (*)(id, SEL))(void *)objc_msgSend)((id)xh, sel_registerName("send"));
oc中的方法调用实际上就是我们看到的现在这种情况,运用了objc_msgsend来发送消息。
继续来看下面的代码。
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)c;
@end
@implementation A
- (void)b {
NSLog(@"b");
}
+ (void)c {
NSLog(@"c");
}
@end
int main(int argc, char * argv[]) {
A *aObject = [[A alloc] init];
// 实例方法调用
[aObject b];
// 类方法调用
[A c];
}
经过clang转换之后
typedef struct objc_object A;
// objc_getClass方法
struct objc_class *objc_getClass(const char *);
// sel_registerName方法
SEL sel_registerName(const char *str) __attribute__((availability(ios,introduced=2.0)));
// 对应上述main函数
int main(int argc, char * argv[]) {
A *aObject = ((A *(*)(id, SEL))(void *)objc_msgSend)((id)((A *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("alloc")), sel_registerName("init"));
// 实例方法调用
((void (*)(id, SEL))(void *)objc_msgSend)((id)aObject, sel_registerName("b"));
// 类方法调用
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("c"));
}
直接对比上下两个代码,我想有些意思应该可以明白。比如sel_registerName这个方法返回的SEL这个鬼东西。sel_registerName("alloc")), sel_registerName("init")这两个分别对应着alloc和init方法。有木有搞懂?
上面还有两点不同是实例方法和类方法的调用。
// 实例方法调用
((void (*)(id, SEL))(void *)objc_msgSend)((id)aObject, sel_registerName("b"));
// 类方法调用
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("c"));
主要的区别,实例方法是aobject而类方法,则需要objc_getClass获取这个类。
继续往下进行。。
先看下类的构成
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;
我们都知道类是一个结构体。
那么方法的构成是什么鬼东西呢?
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
其实也是个结构体。 method_name 是方法名,method_types指的是方法的返回值和参数对照表。而method_imp其实就是一个方法指针,目的是找到这个方法。
下面有个例子
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
上面的方法是给类添加实例方法。如何来用?
int c(NSString *str) {
NSLog(@"c");
return 0;
}
int main(int argc, char * argv[]) {
class_addMethod([A class], @selector(c:), (IMP)c, "i@");
A *aObject = [[A alloc] init];
[aObject performSelector:@selector(c:)];
}
class cls 对应 [A class];
SEL name 对应 @selector(c:);
IMP imp 对应 (IMP)c
const char *types 则对应的是 “I@”;
其实前面说的都是构成之类的东西。那么objc_msgsend到底是如何工作呢?
看下面的源码
// 首先看一下objc_msgSend的方法实现的伪代码
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
// 关键代码(a)
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); // 调用这个函数,伪代码...
}
// 查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) {
... // 执行动态绑定
}
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward; // 这个是用于消息转发的
return imp;
}
// 遍历继承链,查找IMP
IMP lookUpImpOrNil(Class cls, SEL sel) {
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = cls;
IMP imp = nil;
do { // 先查缓存,缓存没有时重建,仍旧没有则向父类查询
if (!curClass) break;
if (!curClass->cache) fill_cache(cls, curClass);
imp = cache_getImp(curClass, sel);
if (imp) break;
} while (curClass = curClass->superclass); // 关键代码(b)
return imp;
}
首先在Class中的缓存查找imp(没缓存则初始化缓存),如果没找到,则向父类的Class查找。如果一直查找到根类仍旧没有实现,则进入消息转发的流程,四步。如果最后还没有那么只能抱错,至于消息转发的流程,我会另开一篇来讲述。
前面看过类方法和实例方法的objc_msgsend调用。那么有个疑问,实例方法和类方法都是存在class中的哪里,又是如何被找到的呢?
这里要涉及到几个地方,我们知道,类中有实例方法,而很多人可能不知道,类方法实际也是实例方法,只不过是父类的实例方法。我们看过前面类的结构中,有个isa指针,这个指针是机上就是指向这个class的父类。类指向父类,父类的指针一直向上一直指向根源类。
对于实例方法我们可以去类的methodlists里去取。
IMP bIMP = class_getMethodImplementation([A class], @selector(b));
那么类方法我们只能去,mataclass中的methodlist中去取。
// 获取A类对应的metaClass
Class aMeta = objc_getMetaClass(class_getName([A class]));
// 在metaClass中找类方法d
IMP dIMP = class_getMethodImplementation(aMeta, @selector(d));
dIMP();
很完美。