iOS开发技巧

OC底层原理05-成员、实例变量、元类

2020-09-18  本文已影响0人  夏天的枫_

iOS--OC底层原理文章汇总

属性 & 成员变量 & 实例变量

属性(property)有setter、getter方法;
成员变量(ivar)没有setter、getter方法;属性 = 下划线成员变量 + setter + getter方法
实例变量:特殊的成员变量--是类的实例化

// 成员变量 vs 属性
@interface Book : NSObject
{
    NSString * name; // 成员变量,诸如:NSArray,NSDictionary...
    NSObject *objc; // 实例变量-特殊的成员变量 类的实例化
    UIButton * btn; // 实例变量,诸如:UIView、UILabel、UITextView...
}

元类 & 元类方法

上一章OC底层原理04中,在类中,属性、实例方法都在bits中找到。对象方法不在对象中,而在类中;类方法却存在其元类中。
为了验证,继续在781源码中通过LLDB调试

获取元类method.list
打印之后,结果如下:
类方法在其元类中

元类中为什么会有类方法?

首先定义两个类Person,Teacher

#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end
#import "LGPerson.h"
@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}

@end

在main中首先定义可以获取类中方法名的方法

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[I];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}

然后调用并打印

LGPerson *person = [LGPerson alloc];
Class pClass     = object_getClass(person);
lgObjc_copyMethodList(pClass);

其结果打印为:

Method, name: sayHello  // 只有一个对象方法,能打印出sayHello

元类中未什么会出现类方法这是有会可能出现在面试题中的,当然方法不会这么直接的问。

void lgInstanceMethod_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));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

打印结果

lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148

class_getInstanceMethod 返回的是类的实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回空


class_getInstanceMethod

method1、method4都打印出地址,method2、method3为空,进一步印证:对象方法存在类中,而类方法存在元类中

void lgClassMethod_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));
    // 元类 为什么有 sayHappy 类方法 
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

打印结果

lgClassMethod_classToMetaclass-0x0-0x0-0x100003148-0x100003148

method1、method2中未找到,method3、method4找到了类方法。首先我们再来看看class_getClassMethod,它是用来获取类

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

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

获取类的类方法就是等同于获取元类的实例方法,最后走的依然是class_getInstanceMethod。不同的是cls->getMeta():获得元类,而在此例中,从元类中获取元类,也就是根元类,但是它直接返回了自身。

在原理04中分析到isa走位图,类的元类是与类同名的元类,元类的元类是superMeta(如果有),superMeta的元类是根元类,根元类的元类是根元类自身,如果再继续查找就会陷入递归循环,一直查找的是根元类。

所以苹果在判断获取元类的类方法时,判断如果cls是元类,就返回元类自身this;否则就返回类cls的isa,即cls的元类,这也就是method3能找到sayHappy类方法的解释。
所以类中存在类方法,去元类寻找类方法时,就返回元类自身,自然也就能在元类中找到类方法了。

 // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)sayHello;
    // + (void)sayHappy;
    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(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

SEL: 类成员方法的指针,它与C语言中的函数指针有所不同,函数指针是保存了方法的地址,但SEL只是获取方法编号。@selector()就是取类方法的编号。
IMP: 函数指针,保存着方法的地址。

class_getMethodImplementation:返回具体方法的实现。Developer Documentation 瞄一下:

class_getMethodImplementation文档
大概说:向这个函数中传递一个类的实例消息时,它将返回传入方法实现函数的指针。顺带说了下,class_getMethodImplementationmethod_getImplementation(class_getInstanceMethod(cls, name))更快。
而返回的函数指针可能是runtime内部的函数,而不一定是实际的方法实现。 如果类的实例不响应传入方法,则返回的函数指针将成为runtime消息转发机制的一部分。
打印结果
 0x100001d00-0x7fff6b58c580-0x7fff6b58c580-0x100001d30

imp1:实例方法sayHellopClass中有具体实现,所以返回该方法imp函数指针地址。
imp2:实例方法sayHello存在类中,并未在metaClass中,所以进行消息转发。
imp3:类方法sayHappy存在元类中,并未在pClass中自然不会有具体实现,所以进行消息转发。
imp4:类方法sayHappy存在元类中,且有具体实现,所以返回的imp函数指针地址。

总结

拓展面试题:iskindOfClass & isMemberOfClass 差别

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

打印结果:

 re1 :1
 re2 :0
 re3 :0
 re4 :0

先来瞅一瞅源码

+ (BOOL)isKindOfClass:(Class)cls {
    // 获取类的元类 vs 传入类
    // 根元类 vs 传入类
    // 根类 vs 传入类
    // 如:re3 元类->根元类->NSObject -> nil vs LGPerson  ,return NO
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}


//获取类的元类,与 cls对比
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
// objc_object.h,之前分析部分
inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

+ isKindOfClass第一次是将获取cls的metalClass 与 cls对比,再对比的是获取上次结果的父类superClass与 cls 进行对比 ,到根元类的父类与cls 对比,如果一直没有,一直追溯到根类的父类nil 与 cls对比
此例中对比过程:元类(isa) -> 根元类(父类) -> 根类NSObject(父类) -> nil(父类) 与 cls的对比
+isMemberOfClass获取类的元类与 cls对比,有则返回YES。

 re1 :1 // NSObject vs NSObject(传入类) -> YES
 re1 :0 // NSObjec -> 根元类 vs NSObject(传入类) ->NO
 re1 :0 // LGPerson -> 元类(首次是找元类) -> 根元类(开始找父类) -> 根类(NSObject)-> nil vs cls(LGPseron),从LGPerson依次查找元类,到后续查找父类查找对比都不相等,输出 NO
 re1 :0 // LGPerson的元类 vs cls(LGPerson),-> NO
        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];     
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];      
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     
        NSLog(@" \nre5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

打印结果:

 re5 :1 
 re6 :1 
 re7 :1 
 re8 :1

再看看源码:

// 将获取的对象类 与 传入类cls对比,相等返回YES,停止循环。如果不相等,将上次获取的 类的父类 与传入类cls 对比,依然循环对比
- (BOOL)isKindOfClass:(Class)cls {
     // 获取对象的类 vs 传入的类 
     // 父类 vs 传入的类
     // 根类 vs 传入的类
     // nil vs 传入的类
     // 如:LGPerson -> NSObject -> nil vs LGPerson 
    */
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

// 获取对象的类,与 传入类对比
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

- isKindOfClass将获取的对象类 与 传入类cls对比,相等返回YES,停止循环。如果不相等,将上次获取的 类的父类 与传入类cls 对比,依然循环对比
此例中对比过程:类对象 -> 父类(如果有) -> 根类NSObject -> nil 与 cls的对比
+isMemberOfClass获取对象的类,与 传入类cls对比,有则返回YES,否则返回NO。

 re5 :1 // 对象的isa=NSObject(根类) vs NSObject(传入类,即根类) -> YES
 re6 :1 // 对象的类=NSObject(根类) vs NSObject(传入类,即根类) -> YES
 re7 :1 // 对象的类=LGPseron  vs cls(LGPseron),为YES,无需查找父类
 re8 :1 // 对象的类=LGPerson vs cls(LGPerson),为YES
上一篇 下一篇

猜你喜欢

热点阅读