您的位置:首页 > 移动开发 > IOS开发

iOS OC 类原理一

2020-04-20 15:02 1266 查看

iOS OC 类原理一

  • 2. 指针偏移
  • 3. 类的结构
  • 1.
    元类
    的创建时机

    前面简单提到

    元类
    的创建时机是在编译器,今天我们通过一下两种方法来验证一下:

    1.1 打印
    元类
    的指针

    首先看下面代码:

    main函数
    之前打印断点,

    通过

    p/x
    打印
    指针,如果能获得
    指针
    , 说明已经在内存中申请了内存空间

    然后

    x/4gx
    打印
    的内存结构,得到
    类 的isa
    ,然后
    isa & 掩码 ISA_MASK
    获得
    元类
    isa
    ,如果这个过程中能正常打印出相应的指针,则能简单验证
    元类
    的创建是在编译期创建的,打印结果如下:

    1.2
    command + B
    生成可执行文件,然后使用
    MachoView
    打开程序二进制可执行文件查看

    由此,可以验证

    元类
    是在编译期创建的,在运行项目
    alloc
    之前已经被创建出来了

    2. 指针偏移

    2.1 普通指针 值拷贝
    int a = 10; //
    int b = 10; //
    LGNSLog(@"%d -- %p",a,&a);
    LGNSLog(@"%d -- %p",b,&b);
    //      KC打印: 10 -- 0x7ffeefbff45c
    //      KC打印: 10 -- 0x7ffeefbff458
    2.2 指针拷贝
    // 对象 - 指针拷贝
    LGPerson *p1 = [LGPerson alloc];
    LGPerson *p2 = [LGPerson alloc];
    LGNSLog(@"%@ -- %p",p1,&p1);
    LGNSLog(@"%@ -- %p",p2,&p2);
    //      KC打印: <LGPerson: 0x100753be0> -- 0x7ffeefbff450
    //      KC打印: <LGPerson: 0x10074e740> -- 0x7ffeefbff448
    2.3 指针偏移
    // 数组指针
    int c[4] = {1,2,3,4};
    int *d   = c;
    NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
    NSLog(@"%p - %p - %p",d,d+1,d+2);
    
    for (int i = 0; i<4; i++) {
    // int value = c[i];
    int value = *(d+i);
    LGNSLog(@"%d",value);
    }
    NSLog(@"指针 - 内存偏移");
    //      0x7ffeefbff470 - 0x7ffeefbff470 - 0x7ffeefbff474
    //      0x7ffeefbff470 - 0x7ffeefbff474 - 0x7ffeefbff478
    //      KC打印: 1
    //      KC打印: 2
    //      KC打印: 3
    //      KC打印: 4

    首地址
    数组
    的第一个
    元素
    的地址,
    &c[0]
    &c[1]
    ,相差一个元素的大小,
    指针d + 1
    ,相当于偏移一个所占位数的元素的大小

    3. 类的结构

    3.1 类的结构是什么?

    通过

    clang
    查看看下面代码在
    c++
    文件中的编译:

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    LGPerson *person = [LGPerson alloc];
    Class pClass     = object_getClass(person);
    
    NSLog(@"%@ - %p",person,pClass);
    }
    return 0;
    }
    int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
    // id, SEL
    LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
    
    Class pClass = object_getClass(person);
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5s_4100t0cd5rn_d7gx0n5wqh8w0000gn_T_main_60f7a3_mi_9,person,pClass);
    }
    return 0;
    }

    我们探究的

    的结构,就是
    Class
    ,在
    cpp
    文件中不难发现
    结构是:

    typedef struct objc_class *Class;

    可以看出,

    objc_class
    类型的 结构体。

    struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
    };

    我们知道万物皆对象,

    objc_class
    继承自
    objc_object
    ,那么我们通过下图方法查看
    objc_class
    的源码:

    struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8         // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    class_rw_t *data() {
    return bits.data();
    }
    ··· // 方法和函数
    }

    源码中可以看到,有个隐藏的

    Class isa
    (为什么有个隐藏的
    Class isa
    ?),
    隐藏的属性必然是来自于
    继承
    继承
    objc_object
    ,看
    objc_object
    源码:

    object
    源码:

    struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };

    那么

    NSObject
    的定义是什么样的呢?

    其实

    NSObject
    的定义是
    结构体
    的一种仿写:

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }

    问: 为什么

    isa
    Class
    类型?

    答:万物皆对象,

    Clss
    本身继承自
    object
    ,用来接收
    isa
    可以的,早期调用
    isa
    就是为了返回
    ,
    后期优化了
    nonpointer isa

    问:

    objc_class
    NSObject
    的关系?
    objc_object
    NSObject
    的关系?

    NSObject
    是一种
    objc_class
    的类型,
    NSObject
    也是一个
    类class
    ,底层也是
    objc_class

    OC
    底层封装的
    C
    objc_object
    NSObject
    底层编译的写法。
    objc_object
    objc_class
    是底层的实现,对应当前
    NSObject(Class)
    NSObject

    3.2 类的结构分析

    通常我们会在

    中定义
    属性
    成员变量
    方法
    ,

    @interface LGPerson : NSObject{
    NSString *hobby;
    }
    
    @property (nonatomic, copy) NSString *nickName;
    
    - (void)sayHello;
    + (void)sayHappy;
    
    @end

    那么在

    中是如何存储这些定义的
    属性 成员变量 方法
    的呢?
    接下来我们来研究一下:

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    LGPerson *person = [LGPerson alloc];
    Class pClass     = object_getClass(person);
    
    NSLog(@"%@ - %p",person,pClass);
    }
    return 0;
    }

    通过

    x/4gx pClass
    打印
    结构,通过前面的查看源码得知如下图:

    objc_class
    Class ISA
    Class superclass
    分别占
    8字节

    cache_t cache
    16字节

    struct cache_t {
    struct bucket_t *_buckets; // 8
    mask_t _mask;  // 4  uint32_t mask_t
    mask_t _occupied; // 4
    
    public: // 下面是函数,函数不占内存
    struct bucket_t *buckets();
    // 方法
    ···
    };

    因为

    objc_class
    cache_t cache
    结构体
    ,而不是
    结构体指针占
    (结构体指针占
    8字节
    ), 所以
    cache_t cache
    占内存
    8 + 4 + 4 = 16字节

    猜测:

    属性 成员变量
    存储在
    class_data_bits_t bits
    中,通过指针偏移(偏移原理类比为数组),偏移
    32字节
    获取
    class_data_bits_t bits

    探索如下:

    pClass
    首地址
    0x100001278 + 32
    得到
    0x100001298(16进制)
    bits
    ,通过
    bits.data()
    得到
    class_rw_t *data()
    ,打印如下:

    class_rw_t
    的结构如下:

    struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    
    const class_ro_t *ro;
    
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    
    Class firstSubclass;
    Class nextSiblingClass;
    
    char *demangledName;
    
    #if SUPPORT_INDEXED_ISA
    uint32_t index;
    #endif
    
    void setFlags(uint32_t set)
    {
    OSAtomicOr32Barrier(set, &flags);
    }
    
    void clearFlags(uint32_t clear)
    {
    OSAtomicXor32Barrier(clear, &flags);
    }
    
    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear)
    {
    assert((set & clear) == 0);
    
    uint32_t oldf, newf;
    do {
    oldf = flags;
    newf = (oldf | set) & ~clear;
    } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
    };

    打印

    *data()
    :

    通过命名,猜测
    属性
    应该存储在
    properties
    中,打印
    properties
    ,然后并打印其中
    list
    :


    同理打印

    methods
    ,一系列操作后如下:

    由此我们探究出了

    属性 方法
    的存储位置,那么
    成员变量
    存储在什么地方呢?

    通过查看

    struct class_rw_t
    中的
    const class_ro_t *ro
    ,

    struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    #ifdef __LP64__
    uint32_t reserved;
    #endif
    
    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    
    method_list_t *baseMethods() const {
    return baseMethodList;
    }
    };

    里面分别有

    method_list_t * baseMethodList
    property_list_t *baseProperties
    const ivar_list_t * ivars
    ,我们猜测
    方法
    属性
    成员变量
    分别存储在对应的变量中,打印
    ro
    结果如下:

    由此可以看出

    LGPerson
    仅有的一个
    成员变量 nickName
    存储在
    bit.data()
    中的
    ro
    baseProperties
    中,

    那么为什么

    bit.data()
    property_array_t properties
    也等打印出
    成员变量
    呢?暂时先抛出个问题。

    接下来我们用同样的方法分别打印

    ivars
    baseMethodList
    ,如图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4iOVL2t-1586242928963)(https://user-gold-cdn.xitu.io/2019/12/28/16f4cc767683200b?w=745&h=355&f=png&s=43820)]

    baseMethodList
    打印:

    打印出

    count = 2
    ,分别打印
    ivars
    成员变量
    ,分别为
    hobby
    _nickName
    ,再次验证了
    @property
    生成的
    属性
    ,在系统底层会自动生成
    _属性
    成员变量
    ,并且会自动生成
    setter
    getter

    问题:从

    baseMethodList
    中并未打印出
    类方法 sayHappy
    ,那么
    类方法
    存储在什么地方呢?

    猜测:

    实例方法
    存在
    中,那么其实
    也是
    元类
    创建出来的
    类对象
    类方法
    应该存在
    元类
    中。

    通过下面代码,分别在

    元类
    中打印
    对象方法
    类方法

    void testInstanceMethod_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(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
    }
    
    打印结果
    2019-12-29 12:28:17.714554+0800 LGTest[799:13098] 0x100002198-0x0-0x0-0x100002130
    2019-12-29 12:28:17.715541+0800 LGTest[799:13098] testInstanceMethod_classToMetaclass

    由打印结果看出,

    对象方法
    存在于
    中,不存在于
    元类
    中,
    类方法
    存在于
    元类
    中,不存在于
    中。

    通过对
    结构的分析,得出:
    成员变量
    存在
    ivars
    中,
    属性
    存储在
    baseProperties
    中,
    对象方法
    存储在
    里面,
    类方法
    存储在
    元类
    里。
    • 点赞 1
    • 收藏
    • 分享
    • 文章举报
    亮亮不想说话 发布了18 篇原创文章 · 获赞 10 · 访问量 271 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: