IOS类分析(一)

2021-07-26  本文已影响0人  XingKongMap

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

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

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

苹果官方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的含义:

_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

上一篇 下一篇

猜你喜欢

热点阅读