isa分析之类的探究(中)

315 阅读5分钟

和谐学习!不急不躁!!我是你们的老朋友小青龙~

在上篇文章isa分析之类的探究(上),我们遗留了两个问题:成员变量和类方法存储在哪儿呢? 在解答这个问题之前,我们有必要先了解一下iOS app中,内存的分类:Clean Memory、Dirty Memory、Compressed Memory。

  • Clean Memory
为了节省内存空间,将一部分低优先级的数据存放到Clean Memory。
有人称之为是能够进行 Page Out 的部分。
  • Dirty Memory
指那些被 App 写入过数据的内存。
  • Compressed Memory
当内存不够用的时候,系统会将不使用的内存进行压缩,直到下一次访问的时候进行解压。
例如,当我们使用 Dictionary 去缓存数据的时候,假设现在已经使用了 3 页内存,
当不访问的时候可能会被压缩为 1 页,再次使用到时候又会解压成 3 页。

这里我们主要讨论的是Clean Memor和Dirty Memory。 上篇文章,我们在研究类的objc_class的时候,提到了class_rw_t,而class_rw_t里又包含了class_ro_t。

  • class_ro_t:只读。
包含了类在编译时期就确定了的属性、方法、协议。
  • 提到了class_rw_t:可读可写。
包含了运行时添加的方法,比如分类添加方法。

我们再来看一下class_rw_t的结构:

struct class_rw_t {
    ...
    const class_ro_t *ro() const {
        ...
    }
    void set_ro(const class_ro_t *ro) {
        ...
    }

    const method_array_t methods() const {
        ...
    }

    const property_array_t properties() const {
       ...
    }

    const protocol_array_t protocols() const {
        ...
    }
};

我们之前是通过访问properties来获取属性,但是最终没发现成员变量。根据上面的叙述,class_ro_t是包含了类在编译时期就确定的属性,那么我们的成员变量会不会就存在ro里呢?我们打印一下看看:

01.png

02.png

至此,我们就找到了成员变量。

那么类方法又在何处呢?

在回答这个问题之前,我们先来分析一下,我们之前打印DirectionChild类的方法都是实例方法,实例-》类 这一层关系是不是有点上下层的概念,所以我们猜想:既然实例方法放在类里面,那么类方法是不是放在类的上一层元类里呢?纸上谈兵不如实际操作,我们直接撸代码:

第一种验证方式:- - - - - - - - - - - - lldb调试

03.png

04.png 至此,我们也找到了类方法。
那么为什么苹果要把类方法放在元类里,而不是跟实例方法一样放在类里面呢?因为对于底层来说,不关实例方法还是类方法,都是对象方法(类也是一个对象),都是按照符号(方法名)来找,如果定义两个名字一样实例方法和类方法:

-(void)runAgain;
+(void)runAgain;

这样在调用runAgain方法的时候,系统就没法区分是调用实例方法的还是调用类方法。所以苹果设计了元类这么一个东西,把类方法都丢进去,这样就完美的解决了这个问题。

第二种验证方式 - - - - - - - - - - - - 遍历->类及元类的实例方法列表

其实我们还有一种方法可以验证类方法在元类里,就是通过api的形式去获取方法列表,然后将方法名遍历打印。我们这边要主要是遍历类的方法以及元类的方法,元类也是类,所以我们提取公用部分的代码写成一个方法:

///打印类方法名
///参数:Class类
void logClassMethod(Class cla)
{
    unsigned int methodsCount = 0;
    Method *me =  class_copyMethodList(cla, &methodsCount);
    for (unsigned int i = 0 ; i < methodsCount; i++) {
        Method methodI = me[i];
        SEL sel =  method_getName(methodI);
        NSString *name =  NSStringFromSelector(sel);
        NSLog(@"method name - %@",name);
    }
    ///这里不要忘记释放内存
    free(me);
}

///打印元类方法名
///参数:Class类
void logClassMetaMethod(Class cla)
{
    const char *className = class_getName(cla);
    Class metaClass = objc_getMetaClass(className);
    ///将元类作为参数
    logClassMethod(metaClass);
}


int main(int argc, const char * argv[]) {
    ...
    ///下面打印类方法、元类方法
    NSLog(@"打印 类方法-》");
    logClassMethod(DirectionChild.class);
    NSLog(@"打印 元类方法-》");
    logClassMetaMethod(DirectionChild.class);
    ...
    return 0;
}

为了方便对照,我直接把DirectionChild代码贴出来

@interface DirectionChild:Direction
{
    NSString *bgColorValue;
}

@property (nonatomic,strong) NSString *hobby;
@property (nonatomic,assign) NSInteger runSpeed;
@property (nonatomic,copy) NSString *indexZ;
///方法
-(void)runSpeedNonTime;
+(void)runSpeedWithTime:(NSString *)time;
-(void)eatSomethingNonTime;
+(void)eatSomethingWithTime:(NSString *)time;
@end

@implementation DirectionChild
-(void)runSpeedNonTime{}
+(void)runSpeedWithTime:(NSString *)time{}
-(void)eatSomethingNonTime{}
+(void)eatSomethingWithTime:(NSString *)time{}
@end

打印结果image.png

第三种验证方法: - - - - - - - - - - - - 打印实例方法地址

#pragma mark -- 第三种
void logInstanceMethodP(Class cla)
{
    NSLog(@"\n\n第三种 验证方式-》实例方法");
    /**打印DirectionChild - 类         是否包含runSpeedNonTime的函数地址 */
    Method runM_class = class_getInstanceMethod(cla, @selector(runSpeedNonTime));
    NSLog(@"class -> runSpeedNonTime -> %p",runM_class);
    /**打印DirectionChild - 元类    是否包含runSpeedNonTime的函数地址 */
    Class metaClass = objc_getMetaClass(class_getName(cla));
    Method runM_metaClass = class_getInstanceMethod(metaClass, @selector(runSpeedNonTime));
    NSLog(@"metaClass -> runSpeedNonTime -> %p",runM_metaClass);
    
    /**打印DirectionChild - 类         是否包含runSpeedWithTime:的函数地址 */
    Method runM_class2 = class_getInstanceMethod(cla, @selector(runSpeedWithTime:));
    NSLog(@"class -> runSpeedWithTime: -> %p",runM_class2);
    /**打印DirectionChild - 元类      是否包含runSpeedWithTime:的函数地址 */
    Class metaClass2 = objc_getMetaClass(class_getName(cla));
    Method runM_metaClass2 = class_getInstanceMethod(metaClass2, @selector(runSpeedWithTime:));
    NSLog(@"metaClass -> runSpeedWithTime: -> %p",runM_metaClass2);
}

打印结果

05.png

第四种验证方法: - - - - - - - - - - - - 打印类方法地址

#pragma mark -- 第四种
void logClassMethodP(Class cla)
{
    NSLog(@"\n\n第四种 验证方式-》类方法");
    /**打印DirectionChild - 类         是否包含runSpeedNonTime的函数地址 */
    Method runM_class = class_getClassMethod(cla, @selector(runSpeedNonTime));
    NSLog(@"class -> runSpeedNonTime -> %p",runM_class);
    /**打印DirectionChild - 元类    是否包含runSpeedNonTime的函数地址 */
    Class metaClass = objc_getMetaClass(class_getName(cla));
    Method runM_metaClass = class_getClassMethod(metaClass, @selector(runSpeedNonTime));
    NSLog(@"metaClass -> runSpeedNonTime -> %p",runM_metaClass);
    
    /**打印DirectionChild - 类         是否包含runSpeedWithTime:的函数地址 */
    Method runM_class2 = class_getClassMethod(cla, @selector(runSpeedWithTime:));
    NSLog(@"class -> runSpeedWithTime: -> %p",runM_class2);
    /**打印DirectionChild - 元类      是否包含runSpeedWithTime:的函数地址 */
    Class metaClass2 = objc_getMetaClass(class_getName(cla));
    Method runM_metaClass2 = class_getClassMethod(metaClass2, @selector(runSpeedWithTime:));
    NSLog(@"metaClass -> runSpeedWithTime: -> %p",runM_metaClass2);
}

打印结果

image.png 如图所示,类对应的这一行竟然打印出了runSpeedWithTime:方法的地址,按照前面的理解,类的类方法应该存放在元类的实例方法列表里才对,可是为什么在Class类这一层就打印了呢?我们Command+单机class_getClassMethod进去看一下里面的实现:

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

    return class_getInstanceMethod(cls->getMeta(), sel);
}
/**翻译:
1、Class或SEL为空,就返回nil;
2、class_getClassMethod方法在底层的实现是class_getInstanceMethod,并且第一个参数是cls的元类。
3、这里就解释通了,为什么图片上关于runSpeedWithTime:的打印,类和元类是打印的地址一样。

在底层没有类方法的概念,万物皆对象,只有对方方法的概念。
*/

第五种验证方法: - - - - - - - - - - - - 打印IMP地址

#pragma mark -- 第五种
void logIMP(Class cla)
{
    NSLog(@"\n\n第五种 验证方式-》IMP");
    /**打印DirectionChild - 类         是否包含runSpeedNonTime的函数地址 */
    IMP runM_class = class_getMethodImplementation(cla, @selector(runSpeedNonTime));
    NSLog(@"class -> runSpeedNonTime -> %p",runM_class);
    /**打印DirectionChild - 元类    是否包含runSpeedNonTime的函数地址 */
    Class metaClass = objc_getMetaClass(class_getName(cla));
    IMP runM_metaClass = class_getMethodImplementation(metaClass, @selector(runSpeedNonTime));
    NSLog(@"metaClass -> runSpeedNonTime -> %p",runM_metaClass);
    
    /**打印DirectionChild - 类         是否包含runSpeedWithTime:的函数地址 */
    IMP runM_class2 = class_getMethodImplementation(cla, @selector(runSpeedWithTime:));
    NSLog(@"class -> runSpeedWithTime: -> %p",runM_class2);
    /**打印DirectionChild - 元类      是否包含runSpeedWithTime:的函数地址 */
    Class metaClass2 = objc_getMetaClass(class_getName(cla));
    IMP runM_metaClass2 = class_getMethodImplementation(metaClass2, @selector(runSpeedWithTime:));
    NSLog(@"metaClass -> runSpeedWithTime: -> %p",runM_metaClass2);
}

打印结果06.png 图片上圈起来的两个地方,按照我们前面的理解,IMP应该是nil,这里应该是显示0x0,可是为什么会有地址呢? 带着这个疑问,我们点击进入class_getMethodImplementation去看看里面的实现:

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

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

    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

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

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

我们发现,当imp为nil的时候,会return一个默认的_objc_msgForward地址,这也就解释了为什么上面IMP的地址打印都不为空了。

百度网盘
链接:pan.baidu.com/s/1qWbB4c7l… 密码:vkh4

文章已经追加 - - - - - - - - - - - -