OC 学习记录随笔 之 Runtime

总资料
全是随笔 笔记。没有规律

使用Objc版本为:objc4-824

前言

Runtime 是什么

OC 是一门动态性比较强的编程语言, 运行很多操作推迟到程序运行时进行(包括动态添加方法添加类添加属性)。
OC的动态性是由runtime 支撑和实现的,runtime 是一套C的API,封装了很多动态性相关的函数
平时写的OC代码,底层都是转换成了runtime的API进行调用的。

具体使用

  • 利用关联对象(AssocicatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfiled的占位颜色,字典模型转换, 归档解档)配合kvc完成。
  • 交换方法实现(交换系统自带方法,如NSLog,按钮点击,)
  • 搜集未实现的方法,避免崩溃
  • 重写 description 方法,打印model变量的所有成员变量

OTHER

runtime 的方法 前缀,代表着是获取的是什么属性:
比如 method 开头的方法,都是获取或设置 method 的某一个特定的属性的方法。
在这里插入图片描述

一、 isa

  • arm64 之前isa就只是简单存储 地址
  • arm64 以后进行了优化,isa存储的数据比较大,只有一部分是存储的 对象地址。

1.1 基本源码构成

union 加 位域的结合。

union isa_t {
    
    
uintptr_t bits;
struct {
    
    
        ISA_BITFIELD;  // defined in isa.h
    };
}

#isa.h  __arm64__ 真机
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t has_cxx_dtor      : 1;                                       \
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19

1.2 位域

  • 位域 是 C语言的结构。
  • 源码中union里面只使用了 bits成员, struct只为了增加可读性,因为没有往struct里面赋值
  • OC代码中基本上名字里面含有 MASK 字段的都是位域的处理方式。
  • x86 # define ISA_MASK 0x00007ffffffffff8ULL 转换成 二进制位后,可以看见
    4-47位都是包含范围 与 isa地址 & 后就是isa的类对象或元类对象的地址。
  • arm64 # define ISA_MASK 0x0000000ffffffff8ULL 转换成 二进制位后,可以看见
    4-43位都是包含范围。
    在这里插入图片描述

1.3 各参数描述

  • nonpointer:
    0:代表普通指针,直接存储Class、Meta Class的内存地址
    1:代表被优化过,使用位域技术来存储更多的信息,后面的位的数据才有效
  • has_assoc: 是否有设置过关联对象(应该是category动态添加的属性,待验证),没有的话,释放更快
  • has_cxx_dtor: 是否有C++的析构函数,没有的话,释放更快
  • shiftcls: 真正存储的Class、Meta Class 的地址指针
  • magic: 用于调试时判断对象是否已经完成了初始化
  • weakly_referenced: 是否被弱引用过,没有的话能够更快的释放
  • unused: TODO待了解
  • has_sidetable_rc: 引用计数是否过大导致无法存储在isa中,如果为1,则引用计数会存储在SideTable的类的属性中
  • extra_rc:存储引用计数的数据,值=引用计数器-1

释放更快。可以通过以下代码证明

//dealloc() -> _objc_rootDealloc(self) -> obj->rootDealloc();
//objc-object.h
inline void objc_object::rootDealloc()
{
    
    
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer                     &&
                 !isa.weakly_referenced             &&
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  &&
#else
                 !isa.getClass(false)->hasCxxDtor() &&
#endif
                 !isa.has_sidetable_rc))
    {
    
    
        assert(!sidetable_present());
        free(this); //直接释放
    } 
    else {
    
    
        object_dispose((id)this);
    }
}

1.4 对象的存储路径:

objc-runtimme-new.h -> struct objc_class : objc_object -> class_rw_t *-> class_rw_ext_t(class_ro_t)

objc_class 结构体主要成员如下:

	Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

获取路径如下
在这里插入图片描述

  • class_rw_ext_t 里面 method_array_t property_array_t等是二维数组,存放的是可读可写的,是包含分类等动态加载进来的信息。其中method_array_t -> method_list_t -> method_t 为二维一维和具体的方法struct,struct method_t 里面存储着具体的方法信息,如下 . 总结:新版的rumtime利用了懒加载的机制,在类的methods,properties等需要修改时,才初始化class_rw_ext_t这块“dirty+memory”存储这些列表,这样就减少了在旧版rumtime中90%的类在rw中直接复制ro中数据浪费的内存。更多详细信息可以参考这个

  • class_ro_t 里面的数组protocol_list_t * property_list_t* 等存储的是一维数组,存放的是可读的,类的初始信息,即不包括分类和runtime等动态加载进来的信息。 在realizeClassWithoutSwift 方法里面由class_ro_t为基础来创建class_rw_ext_t

    扫描二维码关注公众号,回复: 17247052 查看本文章

1.5 Method_t

		SEL name;  //函数名字,可以当做string
        const char *types; //编码。包括返回值类型,参数类型 和字节数量
        MethodListIMP imp; //函数的地址指针
  • SEL 表示函数的名字,又叫做方法选择器,底层类似于char *
    所以不同类有相同名字的方法,对应的SEL是相同的,地址相同
    @selector(<#selector#>) sel_registerName(<#const char * _Nonnull str#>) sel_getName(<#SEL _Nonnull sel#>) NSStringFromSelector(<#SEL _Nonnull aSelector#>) 可以用上面的函数进行char * method SEL转换
typedef struct objc_selector *SEL; 
  • IMP 代表了函数的具体实现
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
  • Type Encoding: 官方文档
    i24@0:8i16f20 -> 返回值+参数1+参数2+…+参数n
    i:代表返回值
    第一个数字24指所有参数占用的字节总数
    后面的数字代表当前的参数在地址内的字节的偏移量,比如i16代表这个ini参数是第16个字节开始的。

    char *buf2 = @encode(struct key);在这里插入图片描述

二、Cache_t 方法缓存

cache是散列表,空间换时间
第一次调用方法的时候,会将方法缓存到类对象的cache里面。

2.1 struct 结构


//散列表里面的结构,存储了SEL 和 IMP;
struct bucket_t {
    
    
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
        ......省略其他函数
}

/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_entry_t {
    
    
    uint32_t sel_offs;
    uint32_t imp_offs;
};

/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_t {
    
    
    int32_t  fallback_class_offset;
    union {
    
    
        struct {
    
    
            uint16_t shift       :  5;
            uint16_t mask        : 11;
        };
        uint16_t hash_params;
    };
    uint16_t occupied    : 14;   //表示已经缓存的方法的数量 (不包含空的)
    uint16_t has_inlines :  1;
    uint16_t bit_one     :  1;
    preopt_cache_entry_t entries[];

    inline int capacity() const {
    
     //桶的容量
        return mask + 1;  //msak的数量是总容量 - 1
    }
};

struct cache_t {
    
    
private:
// explicit_atomic 代表原子性,能够保证增删查改的线程安全
// _bucketsAndMaybeMask 包含 bucket_t 的地址。或者可能存有mask的值(根据不同平台),下面有讲解
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8字节
    union {
    
    
        struct {
    
    
            explicit_atomic<mask_t>    _maybeMask; //4字节  mask可能存在这里	
#if __LP64__
            uint16_t                   _flags; //2
#endif
            uint16_t                   _occupied; //2 //表示已经缓存的方法的数量 (不包含空的)
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
        ......省略其他函数
    };
}
  • occupied //表示已经缓存的方法的数量 (不包含空的)
  • capacity 当前的总容量、桶容量(包含了空的内容)
  • mask 总容量-1,用来和方法地址进行与运算获取缓存的下标
  • bucket_t 实际存储的缓存的方法的结构体。

2.2 cache_t 各结构的平台差异

在代码中_bucketsAndMaybeMask 里面的存储方式在不同的 类型设备上是不一样的。
节选自源码,并有以下注释。

#if defined(__arm64__) && __LP64__ //armCPU 64 位
    #if TARGET_OS_OSX || TARGET_OS_SIMULATOR // pc 或模拟器(应该不存在?)arm64的PC或模拟器?
        #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    #else
        #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 //iphone真机
    #endif
#elif defined(__arm64__) && !__LP64__
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 //32 位 arm(已经淘汰)
#else
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED //其他(MAC 或 模拟器)
#endif

通过上面代码可以看见不同架构的 CACHE_MASK_STORAGE的值不一样,也就可以在下面代码看出cache存储的逻辑也不一样。
(_originalPreoptCache 同样根据不同架构的存储逻辑不一样,可以看源码,这里未列出)

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
	//(MAC 或 模拟器)
    // _bucketsAndMaybeMask 是 buckets_t 的首指针
    // _maybeMask 是 buckets mask
	 static constexpr uintptr_t bucketsMask = ~0ul; //0xffff
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
// (应该不存在?)arm64的PC或模拟器?
    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
   
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//iphone真机 _bucketsAndMaybeMask  和 _maybeMask 是在一起的,节省内存
    // _bucketsAndMaybeMask 的低 48位是 buckets_t 的首指针, 高16位存储buckets mask
    // _maybeMask 未使用

    // mask 被移动的位数
    static constexpr uintptr_t maskShift = 48;

    // mask 后面4位 必须为零,也就是说buckets_t 的位数只有 48 - 4 = 44
    static constexpr uintptr_t maskZeroBits = 4;

    // mask 的最大数量 (1 << 16) - 1
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // 实际的buckets 地址的掩码 的值 (1 << 44) - 1
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
//32 位 arm(已经淘汰)
    // _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
    // _maybeMask is unused, the mask length is stored in the low 4 bits

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif

2.3 insert cache 流程

先结论后看代码:

  • 容量的大小扩容顺序为: 当前容量的2倍: 0->4->8->16->32
  • bucket_t的初始下标为:SEL地址 & MASK(总容量-1)。 但是可能被占用,占用后会存在其他地址
  • 每次扩容时,会释放掉以前缓存的内容
  • 一般情况是超过3/4容量的时候,就会扩容
  • insert() 不是线程安全的,可能会被其他线程同时写入
首先看下 初始化容量大小的数量
enum {
    
    
#if CACHE_END_MARKER || (__arm64__ && !__LP64__)
    // When we have a cache end marker it fills a bucket slot, so having a
    // initial cache size of 2 buckets would not be efficient when one of the
    // slots is always filled with the end marker. So start with a cache size
    // 4 buckets.
    INIT_CACHE_SIZE_LOG2 = 2,
#else
    // Allow an initial bucket size of 2 buckets, since a large number of
    // classes, especially metaclasses, have very few imps, and we support
    // the ability to fill 100% of the cache before resizing.
    INIT_CACHE_SIZE_LOG2 = 1,
#endif
    INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2),
    MAX_CACHE_SIZE_LOG2  = 16,
    MAX_CACHE_SIZE       = (1 << MAX_CACHE_SIZE_LOG2),
    FULL_UTILIZATION_CACHE_SIZE_LOG2 = 3,
    FULL_UTILIZATION_CACHE_SIZE = (1 << FULL_UTILIZATION_CACHE_SIZE_LOG2),
};

真正方法

// 真正方法
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
    
    
   ......省略了无关内容
    // Use the cache as-is if until we exceed our expected fill ratio.
    //新的数量为使用数量+1, 第一位 0+1=1
    mask_t newOccupied = occupied() + 1; 
    unsigned oldCapacity = capacity(),
    //总容量进行缓存,以防扩容
    unsigned capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
    
    
        // Cache is read-only. Replace it.
        //初始容量为 1<<2 = 4
        if (!capacity) capacity = INIT_CACHE_SIZE;
        //开辟内存
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    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.
        // CACHE_END_MARKER = 1
        // cache_fill_ratio(capacity) = capacity * 3 / 4
        //如果  新容量 1 <= 总容量的3/4的话 不需要做处理
    }
#if CACHE_ALLOW_FULL_UTILIZATION
    else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
    
    
        // Allow 100% cache utilization for small buckets. Use it as-is.
        // 运行 100% 容量也不处理
    }
#endif
    else {
    
    
    //其他情况,超过 3/4 或者 达到100%了, 进行扩容
    //初始为:INIT_CACHE_SIZE = 4
    //后续扩容为 当前容量的2倍: 0->4->8->16->32
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
    
    
            capacity = MAX_CACHE_SIZE;
        }
        //扩容,并且不保存原始缓存,并释放
        reallocate(oldCapacity, capacity, true);
    }
    /*
    memory_order_relaxed: 只保证当前操作的原子性,不考虑线程间的同步,其他线程可能读到新值,也可能读到旧值。
    bucketsMask 可以在上面代码看具体的值
    struct bucket_t *cache_t::buckets() const {
    	uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
    	return (bucket_t *)(addr & bucketsMask); //获取bucket的地址,如ios就是在1<<44
	}
	*/
    bucket_t *b = buckets();
    // m 为 struct preopt_cache_t 里面的 mask
    mask_t m = capacity - 1;
    //通过哈希计算 实际bucket_t的下标的值,具体实现 见下一个 代码片段
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    //do 代表,先用当前下标无条件,查找一次
    do {
    
    
        if (fastpath(b[i].sel() == 0)) {
    
    
        //如果计算出的下标,是未使用的位置,则插入这个位置,并返回
        //实际缓存的数量 加一
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
    
    
        //如果当前sel 已经被其他线程缓存进入其中,则返回
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
        //如果当前位置被占用,则每次重新循环计算下标,直到找到合适的位置,或者重新找到第一次尝试的位置 才结束。
    } while (fastpath((i = cache_next(i, m)) != begin));
	
	//缓存失败 。。。。
	    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

具体的哈希计算的方法:

// Class points to cache. SEL is key. Cache buckets store SEL+IMP.
// Caches are never built in the dyld shared cache.

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    
    
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
//mask  = 总容量 - 1
    return (mask_t)(value & mask);
}

2.4 realloc()

TODO:

三、objc_msgSend()

OC中所有的OC基本都会走下面的调用流程(+load 不会,它是直接地址调用).

调用的顺序总共经历了两个文件

  • objc-msg-arm64.s 汇编, 频繁调用,所有 查找第一次缓存的任务放到了汇编代码里面.
  • objc-runtime-new.mm 普通文件

大概流程如下:

  1. 调用方法时,会转换成objc_msgSend()
  2. 在汇编文件中,先查找cache是否有当前的方法. 有就直接调用
  3. 找不到的时候,会直接调用 objc-runtime-new.mm的方法
  4. 直接跳过缓存,查找当前class的方法列表. 有就直接返回
  5. 没有会在for 循环中 转到superClass中,
  6. 然后查到superClass的cache. 没有的话重复4-6步
  7. 当superClass == nil时,如果所有的都没有找到. 则会将imp = _objc_msgForward_impcache , 最终调用的时候,会进入到异常处理的流程 即+ (BOOL)resolveInstanceMethod:(SEL)sel
  8. 找到imp后,没有在缓冲中的话,会inset到cache中去.
  9. 然后return 到汇编中进行调用

具体调用流程图:图片有点大, 点击查看大图
一个蓝色的入口
三个绿色的出口。其他蓝色的都是关键节点。
消息查找
大流程:
1). 查找方法,找到 返回
2). 动态方法解析
3).备用接收者
4).完整转发

  • _objc_forward_handler 的实现函数没有开源,不知道当前异常处理的时候,被赋予了哪个函数地址来进行调用。

  • 没有处理的异常,最终的crash堆栈中会有___forwarding___ 这个函数,已有人通过反汇编模拟了 异常消息时的转发流程,也就是挽救的函数调用。
    注意:
    添加缓存的方法: log_and_fill_cache(cls, imp, sel, inst, curClass); 是直接添加到cls的cache中,也就是说是缓存到调用类的缓存里面

  • (+/-) (BOOL)resolveClassMethod:(SEL)sel

  • (+/-) (id)forwardingTargetForSelector:(SEL)aSelector

  • (+/-) NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

  • (+/-) (void)forwardInvocation:(NSInvocation *)anInvocation

  • (void)doesNotRecognizeSelector:(SEL)aSelector;

  • TODO

简单使用


+ (BOOL)resolveClassMethod:(SEL)sel {
    
    
    class_addMethod(object_getClass(self), sel, (IMP)(dynamicMethodIMP), "@@:");
    [super resolveClassMethod:sel];
    return YES;
}
---------
- (id)forwardingTargetForSelector:(SEL)aSelector{
    
    
    return  self.target; //将原本消息全部转发
}
--------
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    
    
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    
    
    [invocation invokeWithTarget:self.target];
}

四、@Synthesize VS @dynamic

  • ARC 默认行为
    //默认会自动生成成员变量 和 getset方法
@property(nonatomic, copy) NSString *str;

  • @sythiesize
    //手动设置 _str 为成员变量的名字,_str 可改变成任何名字
@sythiesize str = _str; 

  • @dynamic
    //提醒编译器不自动生成settergetter的实现,也不自动生成成员变量。 但是set和get的声明是自动生成的了。
@dynamic str;

可以在 +resolveInstanceMethod:sel 里面动态添加方法的实现。 基本上没有这么使用的。

五、 Super

继承关系

继承
继承
Student
Person
NSObjcet .

探究的问题

#import "Student.h"

@implementation Student
- (instancetype)init {
    
    
    if (self = [super init]) {
    
    
        NSLog(@"%@", [self class]);
        NSLog(@"%@", [self superclass]);
        
        NSLog(@"---------");
        
        NSLog(@"%@", [super class]);
        NSLog(@"%@", [super superclass]);
    }
    return self;
}

@end


2021-12-04 16:50:22.084478+0800 107-objc-send[15563:2249138] Student
2021-12-04 16:50:22.085260+0800 107-objc-send[15563:2249138] Person
2021-12-04 16:50:22.085438+0800 107-objc-send[15563:2249138] ---------
2021-12-04 16:50:22.085497+0800 107-objc-send[15563:2249138] Student
2021-12-04 16:50:22.085548+0800 107-objc-send[15563:2249138] Person

解析过程:

将当前文件转换成 cpp文件后,可以找出这几个源代码

static instancetype _I_Student_init(Student * self, SEL _cmd) {
    
    
    if (self = ((Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
    
    (id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))) {
    
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass")));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_2);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_3, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
    
    (id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_4, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
    
    (id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("superclass")));
    }
    return self;
}

其中进行提取后

[super class]
被转换成了下面语句
objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"));


提取出
方法: objc_msgSendSuper()
参数1: (__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}__rw_objc_super的结构体,本质是objc_super结构体
参数2: sel_registerName("class")


objc源码探析

结构体的定义可在头文件中

objc/message.h 的头文件中定义
struct objc_super {
    
    
    __unsafe_unretained _Nonnull id receiver;  //消息接受者
    __unsafe_unretained _Nonnull Class super_class;  //消息接受者的superClass
};

方法的说明:

/** 
 * Sends a message with a simple return value to the superclass of an instance of a class.
 * 
 * @param super A pointer to an \c objc_super data structure. Pass values identifying the
 *  context the message was sent to, including the instance of the class that is to receive the
 *  message and the superclass at which to start searching for the method implementation.
 * @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
 * @param ...
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method identified by \e op.
 * 
 * @see objc_msgSend
 */
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

简单翻译就是说,

  • objc_super的结构体里面
    • receiver参数是消息的接受者。
    • super_class参数是从这里开始查找方法的实现。
  • op 就是需要查找的方法和处理的方法。

结论:

  • 所以[super class]的本质是 将self当做方法的接受者。super当做方法开始的查找者,但是-class()方法是定义在根类中的,所以这里无论是selfsuper开始查找都无所谓,都只会在NSObject类中找到。
  • [super class] == [self class]
  • selfsuper都没有定义和声明的方法,无论用selfsuper来调用都没有任何影响,因为接受者都只是self

六、 isKindOfClass 、isMemberOfClass

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

+ (BOOL)isMemberOfClass:(Class)cls {
    
    
    return self->ISA() == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    
    
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
    
    
        if (tcls == cls) return YES;
    }
    return NO;
}
  • isMemberOfClass 判断是否是当前类型

  • isKindOfClass 判断左边类是否是右边的子类

  • (-)调用是判断类对象

  • (+)是判断元类对象

  • [Person isKindofClass:[NSObject class]] 是成立的,调用的是+号,因为Person的元类的superClass最终指向的是 NSObject的类对象, 可参考指向图。

七、Runtime实际用处

搜集未实现的方法

在 自己实现的基类中添加 消息转发的方法,能有效避免 调用未实现的方法而导致崩溃的事情。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x1010583a0'

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    
    if ([self respondsToSelector:aSelector]) {
    
    
        //忽略OK的方法
        return  [super methodSignatureForSelector:aSelector];
    }
    
    //转发为实现的方法,默认两个参数
    return  [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    
    //打印收集有问题的方法名字
    NSLog(@"---%@", NSStringFromSelector(anInvocation.selector));
}

查找所有的成员变量

字典转model 或者 model转字典

unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; ++i) {
    
    
    const char *ivarName  = ivar_getName(ivars[i]);
    const char *ivarType = ivar_getTypeEncoding(ivars);
}
free(ivars);

这里同样可以获取到 未公开的 类的所有成员变量, 比如 UITextFieldUILabel 等的私有成员变量。
然后可以直接KVC访问或者修改。如:UITextFieldplacehoder_placeholderLabel 的这个私有成员变量的颜色,大小等

交换方法实现 HOOK:

 Person *per = [[Person alloc] init];
	//Method 取的是 class_rw_ext_t * 里面的指针
    Method testMethod = class_getInstanceMethod([Person class], @selector(test));
    Method testReplaceMethod = class_getInstanceMethod([Person class], @selector(testReplace));
    //这里其实交换的是Method 里面的 imp实现。 也意味着class_rw_ext_t *里面保存的实现被同时交换了,并且缓存也交换了。
    //Method.name 不变,Method.imp 交换
    method_exchangeImplementations(testMethod, testReplaceMethod);
    
    [per test];
    [per testReplace];

交换后的输出结果。

2021-12-09 21:51:19.127764+0800 objc-send[1517:44106] -[Person testReplace]
2021-12-09 21:51:19.128496+0800 objc-send[1517:44106] -[Person test]

Button 点击事件的 hook

@implementation UIControl (MyControl)
+ (void)load {
    
    
    Method m1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method m2 = class_getInstanceMethod(self, @selector(jb_sendAction:to:forEvent:));
    method_exchangeImplementations(m1,  m2);
}

- (void)jb_sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event {
    
    
    if ([self isKindOfClass:[UIButton class]]) {
    
    
        NSLog(@"%s", __FUNCTION__);
        [self jb_sendAction:action to:target forEvent:event];
    }
}

备注:部分笔记包含有MJ老师的学习资料。

猜你喜欢

转载自blog.csdn.net/goldWave01/article/details/121504224