『ios』进阶

『ios』-objc_msgSend 全面解析(一)

2018-09-24  本文已影响14人  butterflyer
1537768624896.jpg

看别人写的框架,看到了如下代码:

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();

很完美。

ios自习室欢迎进入,一起学习一起进步。

IMG_7291.JPG
上一篇下一篇

猜你喜欢

热点阅读