美文网首页iOS
iOS-类结构(中)

iOS-类结构(中)

作者: Summit_yp | 来源:发表于2021-06-23 15:47 被阅读0次

    类结构

    hxdm,在iOS-isa指向图&类结构中说到了对象方法存在类中,类方法存在元类中。属性,成员变量被一笔带过了,本想偷偷懒的,发现其实里面道道还挺多的,那就再分析分析吧。
    老规矩,先在mian.m中添加如下代码:

    @interface YPPerson : NSObject
    {
        NSString *name;
    }
    @property (copy,nonatomic)  NSString *coName;
    @end
    
    @implementation YPPerson
    
    @end
    

    运行到断点位置,开始lldb调试:


    image.png

    在解释这个问题前,我们来看看一位大咖的视频WWDC关于runtime
    视频中提到了clean memory dirty memory,我们来看看定义大咖是怎么定义的。
    clean memory:指加载后不会发生更改的内存,class_ro_t就属于clean memoryclean memory可以被移除,从而节省更多的内存空间,因为如果需要,可以从磁盘中重新加载。
    dirty memory:指在进程运行时会发生更改的内存。只要进程在运行,dirty memory就必须一直存在。
    类结构一经使用就会变成dirty memory,因为运行时会向他写入新的数据 ,例如创建一个新的方法缓存并从类中指向它。所以class_rw_t出现了, 它可以读写类的继承关系,跟踪类的方法,属性,协议等,但只有大约10%的类会去修改它的方法,所以class_rw_ext_t出现了,90%内将不需要class_rw_ext_t,这能节省class_rw_t一半的空间。如下图:

    image.png
    综上所述,猜测成员变量是不是在class_ro_t中,我们来验证一下吧,分析源码结构,找到objc_class-> class_data_bits_t bits ->class_rw_t* data()->class_ro_t *ro()
    (lldb) x/4gx YPPerson.class
    0x100004500: 0x00000001000044d8 0x0000000100354140
    0x100004510: 0x000000010034b340 0x0000802400000000
    (lldb) p (class_data_bits_t *)0x100004520
    (class_data_bits_t *) $1 = 0x0000000100004520
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x0000000100757f30
    (lldb) p *$2
    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4294983880
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    (lldb) p $3.ro()
    (const class_ro_t *) $4 = 0x00000001000040c8
    (lldb) p *$4
    (const class_ro_t) $5 = {
      flags = 388
      instanceStart = 8
      instanceSize = 24
      reserved = 0
       = {
        ivarLayout = 0x0000000100003e07 "\x02"
        nonMetaclass = 0x0000000100003e07
      }
      name = {
        std::__1::atomic<const char *> = "YPPerson" {
          Value = 0x0000000100003dfe "YPPerson"
        }
      }
      baseMethodList = 0x0000000100004110//方法列表
      baseProtocols = 0x0000000000000000//协议
      ivars = 0x0000000100004160//成员变量
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x00000001000041a8//属性
      _swiftMetadataInitializer_NEVER_USE = {}
    }
    (lldb) p $5.ivars
    (const ivar_list_t *const) $6 = 0x0000000100004160
    (lldb) p *$6
    (const ivar_list_t) $7 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 2)
    }
    (lldb) p $7.get(0)
    (ivar_t) $8 = {
      offset = 0x00000001000044a0
      name = 0x0000000100003ead "name"//找到成员变量name了
      type = 0x0000000100003f55 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) p $7.get(1)
    (ivar_t) $9 = {
      offset = 0x00000001000044a8
      name = 0x0000000100003eb2 "_coName"//
      type = 0x0000000100003f55 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    

    由此我们也可以看出,动态添加属性可以,但成员变量是不行的(例外:动态创建类时,可以添加成员变量,因为此时类还未注册到内存中)因为ivars在ro中而不存在rw中,是只读哒,康康源码:


    class_addIvar源码
    RW_CONSTRUCTING宏定义

    属性相关扩展

    添加如下几个属性,并用clang编译,

    @interface YPPerson : NSObject
    @property (copy,nonatomic)    NSString *coName;
    @property (strong,nonatomic)  NSString *stName;
    @property (nonatomic)         NSString *noName;
    @property (atomic)            NSString *atName;
    @end
    

    找到关键代码

    image.png
    观察发现,由copy修饰的属性调用了objc_setProperty,而其他都是通过内存平移。查看llvm源码能看到大致流程,这里不分析了,说下结论:
    1、copy修饰属性触发objc_setProperty
    2、只给nonatomic或atomic,会默认添加strong修饰,会采用内存平移.

    类相关知识搞得差不多了,来检验一下学习成果

    isKindOfClass & isMemberOfClass

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        
          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]];
          
          
          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(@"\n%hhd\n,%hhd\n,%hhd\n,%hhd\n,%hhd\n,%hhd\n,%hhd\n,%hhd\n,",re1,re2,re3,re4,re5,re6,re7,re8);
        }
        return 0;
    }
    

    输出结果如下,说实话和想象的不大一样啊。


    输出结果

    直接看打断点,看源码吧,搜索isKindOfClass,

    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    这个时候就需要祭出isa走位图


    image.png

    咱们先看re1是怎么得来的吧。[NSObject class]调用isKindOfClass,很明显应该走+ (BOOL)isKindOfClass:(Class)cls

    re1分析
    所以re1返回1。

    同理re2分析如下


    re2分析

    所以re2返回0。

    re3:


    image.png

    其他结果就不挨个分析啦,看看源码和isa走位图就会非常清晰啦。总结一下

    1、+ (BOOL)isKindOfClass:(Class)cls 肯定是类对象调用,会从元类开始,遍历元类的父类与cls做比较
    2、- (BOOL)isKindOfClass:(Class)cls肯定是实例对象调用,会从类对象开始,遍历类对象的父类与cls做比较
    3、+ (BOOL)isMemberOfClass:(Class)cls类对象调用,取元类与cls做比较
    4、- (BOOL)isMemberOfClass:(Class)cls实例对象调用,取类对象与cls做比较

    如有不对,烦请指正。

    相关文章

      网友评论

        本文标题:iOS-类结构(中)

        本文链接:https://www.haomeiwen.com/subject/fobmyltx.html