美文网首页
IOS类分析(一)

IOS类分析(一)

作者: XingKongMap | 来源:发表于2021-07-26 17:14 被阅读0次

    与类相关的概念有,对象,类,元类

    对象由类生成,类由元类生成

    对象可以有多个,类都是单例。

    苹果官方isa走位图和继承图

    isa流程图.png

    探索Objective-C中的class和对象在C++中的原理

     typedef struct objc_class * Class   // 可知Class 是一个struct objc_class结构的指针
    

    在objc源码中搜索 struct objc_class { 发现:

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE; //OBJC2 不可用
    

    可以发现一个定义,但是有注释,objc2不可用。继续探索,搜索struct objc_class

    发现有挺多的,在objc-runtime-old和objc-runtime-new.h里都有定义,猜测应该是objc-runtime-new.h这里的

    如图发现继承objc_object

    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    

    那么objc_class的存储结构是

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom
    }
    

    isa 组成

    见isa组成

    验证isa走位图和继承图

    运行objc源码,然后lldb调试如下

    //isa 走位验证
    
    (lldb) p teacher
    (LGTeacher *) $0 = 0x000000010223a070
    (lldb) x/4gx 0x000000010223a070
    0x10223a070: 0x011d800100008369 0x0000000000000000
    0x10223a080: 0x0000000000000000 0x0000000000000000
    (lldb) p/x 0x011d800100008369 & 0x00007ffffffffff8  //LGTeacher对象的isa指向
    (long) $1 = 0x0000000100008368
    (lldb) po 0x0000000100008368
    LGTeacher
    
    (lldb) x/4gx 0x0000000100008368           //LGTeacher 类的内存
    0x100008368: 0x0000000100008390 0x00000001000083b8
    0x100008378: 0x000000010034f390 0x0000802800000000
    (lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8  //LGTeacher类的isa指向
    (long) $3 = 0x0000000100008390
    (lldb) po 0x0000000100008390
    LGTeacher
    
    (lldb) po 0x00000001000083b8
    LGPerson
    
    (lldb) x/4gx 0x0000000100008390            // LGTeacher 元类的内存
    0x100008390: 0x00000001003570f0 0x00000001000083e0
    0x1000083a0: 0x00000001018499b0 0x0001e03100000007
    (lldb) p/x 0x00000001003570f0 & 0x00007ffffffffff8  // LGTeacher 元类的isa指向
    (long) $6 = 0x00000001003570f0
    (lldb) po 0x00000001003570f0
    NSObject
    
    (lldb) x/4gx 0x00000001003570f0                     // NSObject元类的isa 指向等于自己
    0x1003570f0: 0x00000001003570f0 0x0000000100357140
    0x100357100: 0x0000000101849d60 0x0004e03100000007
    
    //isa 继承
    (lldb) p teacher
    (LGTeacher *) $0 = 0x000000010223a070
    (lldb) x/4gx 0x000000010223a070
    0x10223a070: 0x011d800100008369 0x0000000000000000
    0x10223a080: 0x0000000000000000 0x0000000000000000
    (lldb) p/x 0x011d800100008369 & 0x00007ffffffffff8
    (long) $1 = 0x0000000100008368
    (lldb) po 0x0000000100008368
    LGTeacher
    
    (lldb) x/4gx 0x0000000100008368           //LGTeacher 类的内存
    0x100008368: 0x0000000100008390 0x00000001000083b8
    0x100008378: 0x000000010034f390 0x0000802800000000
    (lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8
    (long) $3 = 0x0000000100008390
    (lldb) po 0x0000000100008390
    LGTeacher
    
    (lldb) po 0x00000001000083b8                            //查看LGTeacher继承的类
    LGPerson
    (lldb) x/4gx 0x00000001000083b8                     //LGPerson类的内存
    0x1000083b8: 0x00000001000083e0 0x0000000100357140
    0x1000083c8: 0x000000010034f390 0x0000802800000000
    (lldb) po 0x0000000100357140             //查看LGPerson继承的类
    NSObject
    ...
    

    如上验证,虽然不全,但是都通过了,我们对objc_class的存储结构和isa走位图及继承图有了更加深刻的了解。

    探索类的cache_t

    struct cache_t {
    private:
        explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
        union {
            struct {
                explicit_atomic<mask_t>    _maybeMask; // 4
    #if __LP64__                                                                            // 判断是否是64位机器
                uint16_t                   _flags;  // 2
    #endif
                uint16_t                   _occupied; // 2
            };
            explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
        };
    }
    

    可以看出cache_t 结构大小为 8 + 8 = 16字节

    lldb调试查看cache_t结构

    (lldb) p/x [LGTeacher class]
    (Class) $1 = 0x00000001000084e0 LGTeacher
    (lldb) x/4gx 0x00000001000084e0
    0x1000084e0: 0x0000000100008508 0x0000000100008530
    0x1000084f0: 0x00000001007bb360 0x0001804000000003
    
    (lldb) p (cache_t *)0x1000084f0                 //转换成cache_t 赋值给$3
    (cache_t *) $3 = 0x00000001000084f0
    (lldb) p *$3                                                        //*$3 查看$3内容
    (cache_t) $4 = {                                    
      _bucketsAndMaybeMask = {
        std::__1::atomic<unsigned long> = {
          Value = 4303074144
        }
      }
       = {
         = {
          _maybeMask = {
            std::__1::atomic<unsigned int> = {
              Value = 3
            }
          }
          _flags = 32832
          _occupied = 1
        }
        _originalPreoptCache = {
          std::__1::atomic<preopt_cache_t *> = {
            Value = 0x0001804000000003
          }
        }
      }
    }
    
    (lldb) p $4.buckets()                                               //深看cache_t结构,发现了buckets方法,可以返回buckets指针
    (bucket_t *) $7 = 0x00000001007bb360
    
    (lldb) p *$7
    (bucket_t) $8 = {
      _sel = {
        std::__1::atomic<objc_selector *> = (null) {
          Value = (null)
        }
      }
      _imp = {
        std::__1::atomic<unsigned long> = {
          Value = 0
        }
      }
    }
    (lldb) p *($7+1)
    (bucket_t) $9 = {
      _sel = {
        std::__1::atomic<objc_selector *> = "" {
          Value = ""
        }
      }
      _imp = {
        std::__1::atomic<unsigned long> = {
          Value = 49040
        }
      }
    }
    
    (lldb) p *($7+1)
    (bucket_t) $12 = {
      _sel = {
        std::__1::atomic<objc_selector *> = "" {
          Value = ""
        }
      }
      _imp = {
        std::__1::atomic<unsigned long> = {
          Value = 49040
        }
      }
    }
    (lldb) p $12.sel()
    (SEL) $13 = "saySomething"
    (lldb) po $12.imp(nil, $1)
    (KCObjcBuild`-[LGPerson saySomething])
    

    可以看到cache_t中缓存了父类LGPerson的saySomething方法的sel和imp

    结构代码转换调试方法

    通过上面源码和探索,可以声明一个类似的结构来探索,代码如下

    #import <Foundation/Foundation.h>
    #import "LGPerson.h"
    #import <objc/runtime.h>
    
    typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
    
    struct kc_bucket_t {
        SEL _sel;
        IMP _imp;
    };
    struct kc_cache_t {
        struct kc_bucket_t *_bukets; // 8
        mask_t    _maybeMask; // 4
        uint16_t  _flags;  // 2
        uint16_t  _occupied; // 2
    };
    
    struct kc_class_data_bits_t {
        uintptr_t bits;
    };
    
    // cache class
    struct kc_objc_class {
        Class isa;
        Class superclass;
        struct kc_cache_t cache;             // formerly cache pointer and vtable
        struct kc_class_data_bits_t bits;
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGPerson *p  = [LGPerson alloc];
            Class pClass = p.class;  // objc_clas
            [p say1];
            [p say2];
            [p say3];
            [p say4];
            [p say1];
            [p say2];
    //        [p say3];
    
            [pClass sayHappy];
            struct kc_objc_class *kc_class = (__bridge struct kc_objc_class *)(pClass);
            NSLog(@"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
            // 0 - 8136976 count
            // 1 - 3
            // 1: 源码无法调试
            // 2: LLDB
            // 3: 小规模取样
            
            // 底层原理
            // a: 1-3 -> 1 - 7
            // b: (null) - 0x0 方法去哪???
            // c: 2 - 7 + say4 - 0xb850 + 没有类方法
            // d: NSObject 父类
            
            for (mask_t i = 0; i<kc_class->cache._maybeMask; i++) {
                struct kc_bucket_t bucket = kc_class->cache._bukets[i];
                NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
            }
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    

    cache_t 源码探究

    首先找到插入方法,因为如果需要缓存的话,就需要先插入

    void insert(SEL sel, IMP imp, id receiver);
    
        if (slowpath(isConstantEmptyCache())) {
            // Cache is read-only. Replace it.
            if (!capacity) capacity = INIT_CACHE_SIZE;
            reallocate(oldCapacity, capacity, /* freeOld */false);
        }
    

    初始化的缓存大小4

        else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
            // Cache is less than 3/4 or 7/8 full. Use it as-is.
        }
    

    否则使用的内存加1小于等于开辟空间的3/4或者7/8时,正常插入(和架构有关)

    #if CACHE_ALLOW_FULL_UTILIZATION                            //缓存允许100%使用时
        else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
            // Allow 100% cache utilization for small buckets. Use it as-is.
        }
    #endif
    

    仔细看变量,CACHE_ALLOW_FULL_UTILIZATION = 1,FULL_UTILIZATION_CACHE_SIZE = 8,就是说否则在缓存小于8的时候是允许存放满的

    else {
            capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
            if (capacity > MAX_CACHE_SIZE) {
                capacity = MAX_CACHE_SIZE;
            }
            reallocate(oldCapacity, capacity, true);
        }
    

    这里扩大内存,重新开启一块双倍内存,原来存的内容不再拷贝过来,最大缓存1 << 16

    void cache_t::incrementOccupied() 
    {
        _occupied++;
    }
    // 每次添加_occupied + 1
    
    void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
    {
        // objc_msgSend uses mask and buckets with no locks.
        // It is safe for objc_msgSend to see new buckets but old mask.
        // (It will get a cache miss but not overrun the buckets' bounds).
        // It is unsafe for objc_msgSend to see old buckets and new mask.
        // Therefore we write new buckets, wait a lot, then write new mask.
        // objc_msgSend reads mask first, then buckets.
    
    #ifdef __arm__
        // ensure other threads see buckets contents before buckets pointer
        mega_barrier();
    
        _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_relaxed);
    
        // ensure other threads see new buckets before new mask
        mega_barrier();
    
        _maybeMask.store(newMask, memory_order_relaxed);
        _occupied = 0;
    #elif __x86_64__ || i386
        // ensure other threads see buckets contents before buckets pointer
        _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release);
    
        // ensure other threads see new buckets before new mask
        _maybeMask.store(newMask, memory_order_release);
        _occupied = 0;                  //归零
    #else
    #error Don't know how to do setBucketsAndMask on this architecture.
    #endif
    }
    

    从上面代码看出,扩大空间的时候_occupied 归0. _maybeMask赋值newCapacity - 1

    • cache_t 缓存初始化的缓存大小4
    • 开辟空间的3/4时正常插入
    • CACHE_ALLOW_FULL_UTILIZATION = 1时,缓存小于8的时候是允许存放满的
    • 在不满足上面条件的时候,重新开启一块双倍内存,原来存的内容不再拷贝过来,最大缓存1 << 16
    • 扩大空间的时候_occupied 归0. _maybeMask赋值newCapacity - 1
    • 每次存储新的bucket, _occupied + 1

    得知cache_t的含义:

    _occupied : 缓存的个数

    _maybeMask:开辟的空间

    _bucketsAndMaybeMask: buckets的首地址,所以也可以通过bucketsAndMaybeMask直接得到

        // Sign newImp, with &_imp, newSel, and cls as modifiers.
        uintptr_t encodeImp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, IMP newImp, UNUSED_WITHOUT_PTRAUTH SEL newSel, Class cls) const {
            if (!newImp) return 0;
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
            return (uintptr_t)
                ptrauth_auth_and_resign(newImp,
                                        ptrauth_key_function_pointer, 0,
                                        ptrauth_key_process_dependent_code,
                                        modifierForSEL(base, newSel, cls));
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
            return (uintptr_t)newImp ^ (uintptr_t)cls;
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
            return (uintptr_t)newImp;
    #else
    #error Unknown method cache IMP encoding.
    #endif
        }
    

    imp的存储方式

    探索方法调用到cache缓存的过程

    断点可知


    image.png

    由此有调用链:_objc_msgSend_uncached -> lookUpImpOrForward -> log_and_fill_cache -> cache::insert

    image.png

    汇编正向可以看出调用了objc_msgSend,搜索objc_msgSend


    image.png

    找到调用_objc_msgSend_uncached,补全调用链
    objc_msgSend -> _objc_msgSend_uncached -> lookUpImpOrForward -> log_and_fill_cache -> cache::insert

    相关文章

      网友评论

          本文标题:IOS类分析(一)

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