lincf0912/Blog

探索方法归属以及类的归属(5)

Opened this issue · 0 comments

类存在几份?

  • 由于类的信息内存永远只存在一份,所以 类对象只有一份

objc_object 与 对象的关系

  • 所有的对象 都是以 objc_object为模板继承过来的
  • 所有的对象 是 来自 NSObject(OC) ,但是真正到底层的 是一个objc_object(C/C++)的结构体类型
  • objc_object对象关系继承关系

什么是 属性 & 成员变量 & 实例变量 ?

  • (property):在OC中是通过@property开头定义,且是带下划线成员变量 + setter + getter方法的变量

  • 成员变量(ivar):在OC的类中{}中定义的,且没有下划线的变量

  • 实例变量:通过当前对象类型,具备实例化的变量,是一种特殊的成员变量,例如 NSObject、UILabel、UIButton等

成员变量 和 实例变量什么区别?

  • 实例变量(即成员变量中的对象变量 就是 实例变量):以实例对象实例化来的,是一种特殊的成员变量

  • NSString常量类型, 因为不能添加属性,如果定义在类中的{}中,是成员变量

  • 成员变量中 除去基本数据类型、NSString,其他都是 实例变量(即可以添加属性成员变量),实例变量主要是判断是不是对象

探索方法的归属

class_getInstanceMethod 方法

void InstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

// Student 实现
@implementation Student

- (void)sayHello {
    
}

+ (void)sayHappy {
    
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *student = [Student alloc];
        Class pClass     = object_getClass(student);
        
        InstanceMethod_classToMetaclass(pClass);
    }
    return 0;
}

// 输出结果为
// InstanceMethod_classToMetaclass - 0x1000030e0-0x0-0x0-0x100003078

这个方法 class_getInstanceMethod我们从字面上的意思去理解这个方法,应该是 获取某个类的实例方法,我们根据 isa 走位图,可以得出,实例方法存储在类里面,类方法存储在元类

class_getInstanceMethod 的源码实现

/***********************************************************************
* class_getInstanceMethod.  Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
  • 当我们调用class_getInstanceMethod方法时, 它在源码里会根据传入的cls, 来查找它的结构体里有没有我们要获取的方法。
  • class_getInstanceMethod(pClass, @selector(sayHello));这段代码, 就是要让我们根据cls类的bits里, 来查找是否有sel这个实例方法, 验证(平移cls内存地址获取bits, 打印methods)后, 发现是有这个方法的。
  • class_getInstanceMethod(metaClass, @selector(sayHello));这段代码, 发现元类里是没有sayHello方法的, 但是我们找到了sayHappy的方法。
  • 在源码的世界里, 只有实例方法, 是没有类方法这个说法的(OC层面才会有这些说法), 因为每个类每个元类, 他们都是对象, 他们的方法都存储在他们的父级里。

class_getClassMethod 方法

void ClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *student = [Student alloc];
        Class pClass     = object_getClass(student);
        
        ClassMethod_classToMetaclass(pClass);
    }
    return 0;
}

// 输出结果为
// ClassMethod_classToMetaclass - 0x0-0x0-0x100003078-0x100003078

class_getClassMethod 的源码实现

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

其实 class_getClassMethod 方法最终也是调用到class_getInstanceMethod 方法里面来。只是传进去的 class 会变成传递 classMetaClass

为什么method4能正常输出结果呢,这就要根据源码来分析了,我们看到去查找方法的时候,传入的是 cls->getMeta(),我们跟进去看他究竟是怎样实现的

// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

这样就非常清晰了,注释都已经提示我们了,如果我们传入的 classMetaClass 就直接返回。

那么为什么苹果这样设计呢,其实就是为了防止 无限递归,所以就知道为什么是这样的输出结果了。

method1: 类 -> 元类 -> 元类(isMetaClass返回自身)中没有sayHello实例方法.
method2: 元类-> 元类(isMetaClass返回自身)中没有sayHello实例方法.
method3: 类 -> 元类 -> 元类(isMetaClass返回自身)中有sayHappy类方法
method4: 元类 -> 元类(isMetaClass返回自身)中有sayHappy类方法

class_getMethodImplementation 方法

void IMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%s - %p-%p-%p-%p",__func__,imp1,imp2,imp3,imp4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *student = [Student alloc];
        Class pClass     = object_getClass(student);
        
        IMP_classToMetaclass(pClass);
    }
    return 0;
}

// 输出结果为
// IMP_classToMetaclass - 0x100001990-0x7ff6b57d580-0x7ff6b57d580-0x100001990

class_getMethodImplementation 的源码实现

/** 
 * Returns the function pointer that would be called if a 
 * particular message were sent to an instance of a class.
 * 
 * @param cls The class you want to inspect.
 * @param name A selector.
 * 
 * @return The function pointer that would be called if \c [object name] were called
 *  with an instance of the class, or \c NULL if \e cls is \c Nil.
 *
 * @note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
 * @note The function pointer returned may be a function internal to the runtime instead of
 *  an actual method implementation. For example, if instances of the class do not respond to
 *  the selector, the function pointer returned will be part of the runtime's message forwarding machinery.
 */
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward; // 0x7ff6b57d580 实际上就是它
    }

    return imp;
}

地址出现了2次:0x7fff6b57d580,这个是在调用class_getMethodImplementation()方法时,无法找到对应实现时返回的相同的一个地址,无论该方法是在实例方法或类方法,无论是否对一个实例调用该方法,返回的地址都是相同的,但是每次运行该程序时返回的地址并不相同。

如果向一个类的实例发送一条消息,该函数会返回该条消息的 IMPclass_getMethodImplementation可能比method_getImplementation 更高效。

返回的指针可能会是一个方法的 IMP,也可能是runtime 内部的一个函数。比如说,如果一个类的对象不能响应一个 selector,这个函数指针返回的就会是 runtime 里面消息转发机制的一部分。

method1: 类中有sayHello实例方法, 所以也查找到它的imp
method2: 元类中没有sayHello实例方法, 返回msg_forward_name地址
method3: 类中没有sayHappy类方法, 返回msg_forward_name地址
method4: 元类有sayHappy类方法, 所以也查找到它的imp

探索isKindOfClassisMemberOfClass

isKindOfClass探索

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[Student class] isKindOfClass:[Student class]];
BOOL re4 = [(id)[Student class] isMemberOfClass:[Student class]];
NSLog(@" \nre1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[Student alloc] isKindOfClass:[Student class]];
BOOL re8 = [(id)[Student alloc] isMemberOfClass:[Student class]];
NSLog(@" \nre5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

分析一下源码这两个函数的对象实现

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

+ (BOOL)isMemberOfClass:(Class)cls { // 通过与查找自身的元类, 并与自身进行对比
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls { // 通过与查找自身的类型, 并与自身进行对比
    return [self class] == cls;
}

BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
}

objc_opt_isKindOfClass通过查找obj的元类, 并递归循环元类的superclass与otherClass进行对比。

需要注意isa也就是元类的继承关系, 是的元类也存在继承关系:
元类 -> 根元类(metaClass) -> 根类(NSObject) -> nil

re1 结果分析

  • + (BOOL)isKindOfClass:(Class)cls 方法,会先调用 objc_opt_isKindOfClass(id obj, Class otherClass) ,为什么会调用这个呢,因为是 LLVM 优化的结果,然后会调用 ISA() 方法中获得 MetaClass 的指针。

  • 接着在 isKindOfClass 中有一个循环,先判断class 是否等于 MetaClass,不等就继续循环判断是否等于 superclass,不等再继续取 superclass,如此循环下去。

  • [NSObject class] 执行完之后调用isKindOfClass,第一次判断先判断 NSObjectNSObjectMetaClass 是否相等,从之前的 isa 的走位图上我们也可以看出,NSObjectMetaClass 与本身不等。接着第二次循环判断 NSObjectMetaClasssuperclass是否相等。还是从那张图上面我们可以看到:Root class(meta)superclass 就是 Root class(class),也就是 NSObject 本身。所以第二次循环相等,于是 re1 输出应该为YES

re3 结果分析

  • [Student class] 执行完之后调用 objc_opt_isKindOfClass ,然后开始循环遍历,第一次 for循环,StudentMetaClass[Student class] 不等,第二次 for 循环,Student(MetaClass)superclass 指向的是 NSObject(MetaClass), 和 [Student class] 不相等。第三次 for 循环,NSObject(MetaClass)superclass 指向的是 NSObject(Class),和 [Student class] 不相等。第四次循环,NSObject(Class)superclass 指向 nil, 和 [Student class] 不相等。第四次循环之后,退出循环,所以 re3 输出为 NO

re2 结果分析

  • + (BOOL)isMemberOfClass:(Class)cls 方法内部,会先调用 ISA() 方法中获得 MetaClass 的指针,然后直接比较当前类的 MetaClass 是否等于 [NSObject class],所以 re2 输出为 NO

re4 结果分析

  • 比较的是 Student(MetaClass)[Student class],所以 re4 输出为 NO

re5 结果分析

  • 此时应该输出 YES,因为在 objc_opt_isKindOfClass 函数中,判断 NSObjectisa 指向是否是自己的类NSObject,第一次 for 循环就能输出 YES 了。

re7 结果分析

  • 同上,第一次 for 循环就输出 YES

re6 结果分析

  • 因为在 - (BOOL)isMemberOfClass:(Class)cls 函数中,获取当前类的 class[NSObject class] 进行比较,输出结果为 YES

re8 结果分析

  • 同理,输出结果为 YES

最后

给大家一份练习题,以下这段代码的运行会崩溃吗?

@interface Teacher : NSObject

+ (void)sayByebye;

@end

@implementation Teacher

- (void)sayByebye {
    NSLog(@"88");
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
    [Teacher sayByebye];
    
}