iOS Runtime

文章目录
  一、简介
  二、isa 指针
  二、Class 结构
     1、class_rw_t
     2、class_ro_t
     3、method_t
     4、cache_t
 三、消息机制
     1、简介
     2、消息发送
     3、动态方法解析
     4、消息转发
  四、关于 super 关键字 
     1、一道经典面试题
     2、objc_msgSendSuper
  五、isKindOfClass 和 isMemberOfClass  
     1、实现
     2[Person isKindOfClass:[NSObject class]]
  六、runtime 常用 API 
     1、类相关
     2、成员变量相关
     3、属性相关(参照成员变量)
     4、方法相关 
 七、具体应用
     1、遍历访问成员变量(修改 textfield的占位属性、字典转模型、解归档)
     2、关联对象
     3、交换方法实现(hook) 
     4、利用消息转发机制解决方法找不到的异常

一、简介

OC 是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时才进行,OC 动态性都是由 runtime的 API 来支撑的。runtime的一套 C 语言的 API,封装了很多动态性相关的函数。平常编写的 OC 代码,底层都是转化成 Runtime API 进行调用的。下载链接

二、isa 指针
要想学习 runtime,首先要了解他一些底层常用的数据结构,例如,isa指针。在arm64之前,isa就是一个普通的指针,存储这 class、meta-class对象的内存地址。从 arm64架构开始,对 isa进行优化,变成一个共用体(union)结构,用一个 64 位内存数据来存储(位域)更多的数据,其中使用 33 位来存储具体的地址值。

union isa_t 
{
    Class cls;
    uintptr_t bits;
    //此结构体仅为了增加可读性,纯属摆设
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; 
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };

nonpointer
为 0 ,代表普通指针,直接存储 class、meta-class对象的内存地址
为1,代表优化过,使用位域存储更多的信息

has_assoc
是否有设置过关联对象,如果未否,释放得会更快

has_cxx_dtor
是否有析构函数(类似 dealloc),如果没有,会释放得更快

shiftcls
类对象、元类对象的地址值

weakly_referenced
是否被弱引用指向过

deallocating
对象是否正在释放

extra_rc(rc是 retainCount的简写)
里面存储的值是引用计数减1

has_sidetable_rc
引用计数器是否过大无法存储在 isa中,如果为 1,会存储在 一个 叫 sidetable类的属性中。

对象在释放的时候,调用的是 objc_destructInstance这个方法,在 objc-runtime-new.mm

/***********************************************************************
* objc_destructInstance
  //销毁对象
* Destroys an instance without freeing memory.  
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        //如果有析构函数
        if (cxx) object_cxxDestruct(obj);
        //如果有关联对象
        if (assoc) _object_remove_assocations(obj);
        //弱引用
        obj->clearDeallocating();
    }

    return obj;
}

二、Class 结构

类对象和元类对象在内存中的结构是一样的,都遵循如下

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // 方法缓存
    class_data_bits_t bits;    // 获取具体的类信息
}

1、class_rw_t

通过调用 bits.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;
}

其中,只读信息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;
    }
};

class_rw_t里面的 methods、properties、protocols二维数组可读可写,包含类的初始内容、分类的内容
在这里插入图片描述注意: 设计成二维数组的好处是可以比较快捷的扩展,例如 methods,分类方法在运行时合并按照编译顺序插入二维数组最前面,指针指向分类的方法列表。

2、class_ro_t
而对于 class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties一维数组只读,包含了类的初始内容(注意: 这里不包含分类,简单理解成 在 interface里声明的东西)

3、method_t

method_t 是对方法/函数的封装,结构如下

struct method_t {
    SEL name;    //选择器,函数名
    const char *types; //编码(返回值类型、参数类型) 其实就是 @encode 这个方法
    IMP imp; //指向函数的指针(函数地址)其实,就是函数的实现和地址值
};

关于编码,其实调用的是 @encode()这个函数,具体参照
在这里插入图片描述例如:
编码为 :i24@0:8i16f20
函数的返回值为 int类型,所有参数所占字节数为 24 (8 + 8 + 4 + 4 = 24)
第 一 个参数是 id 类型(消息接受者),占 8 字节,从 0 开始
第 二 个参数是 SEL 类型(_cmd),占 8 字节,从 8 开始
第 三 个参数是int 类型, 占 4 字节, 从 16 开始
第 四 个参数是 float 类型,占 4 字节,从 20 开始

备注: 每个方法都默认携带上两个参数,一个是消息接受者,另一个是_cmd

4、cache_t 方法缓存

Class内部结构中有个方法缓存cache_t,用 散列表缓存曾经调用过的方法,可以提高方法的查找速度。

struct cache_t {
    struct bucket_t *_buckets; //散列表(是个数组)
    mask_t _mask; //散列表长度 -1
    mask_t _occupied; //已经缓存的方法数量
}

_buckets结构如下

struct bucket_t {
    cache_key_t _key;  // SEL为 key   例如 :  _key = @selector(test)
    IMP _imp;  // 函数的内存地址        例如 : _imp = &test
}

散列表(哈希表)查找流程简述
首先,一个方法被调用过,会放进类对象的的缓存数组里面_buckets,放的位置是根据@selector(test) & _mask = index,然后存进数组。当在缓存里面查找的时候,也是根据@selector(test) & _mask = index 计算得出的 index(: 如果 index 超出长度,就扩容(扩容后,已有的缓存会被清掉,因为_mask 发生了变化),如果 index 的位置已经存储了,那么直接 继续 index-1,找到未被存储的位置,直接存进去; @selector(test) & _mask = index ,比对 key,如果一样,完成调用。不一样 index -1 ,找到完成调用,到最后还是没找到,那么从末尾继续往前找。),找到 _bucket_t,进而找到 _imp完成调用流程。是以空间换时间来提高查找效率。
在这里插入图片描述源码展示,在 objc-cache.mm

bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    //查找 index
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        //找到了直接返回
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
        //如果找到了但是 key 不一样,那么 i-1,继续比对
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

看下 cache_hash(k, m)查找流程

static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
     // 就是 @selector() & _mask
    return (mask_t)(key & mask);
}

没找到继续循环的流程 cache_next(i, m)

static inline mask_t cache_next(mask_t i, mask_t mask) {
    //将其下标减 1 ,如果还是没找到,就从最末尾开始找
    return i ? i-1 : mask;
}

还有一个,扩容的过程补充下,每次扩容容量直接 double,更新_mask, 清除原有缓存

void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    
    uint32_t oldCapacity = capacity();
    //扩容,每次容量直接翻倍
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;

    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }
     //重新分配
    reallocate(oldCapacity, newCapacity);
}
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
    bool freeOld = canBeFreed();

    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this

    assert(newCapacity > 0);
    assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
    //更新 mask.这里也可以得到,_mask 的值为容量  -1
    setBucketsAndMask(newBuckets, newCapacity - 1);
    //释放原有的缓存
    if (freeOld) {
        cache_collect_free(oldBuckets, oldCapacity);
        cache_collect(false);
    }
}

综合上面的知识,完善下方法调用流程, 假设 Student :Person,在 Person的头文件声明- (void)test;方法,并在实现文件对所声明的方法进行实现。

int main(int argc, char * argv[]) {
    @autoreleasepool {
         Student *stu = [[Student alloc]init];  // cache_t->_occupied = 1
         //第一次调用
         [stu test];  // cache_t->_occupied = 2
         //第二次调用
         [stu test];
        return 0
    }
}

方法的调用流程如下:

0、转化成 objc_msgSend(stu,sel_registerName("test"))
     sel_registerName("test") == @selector(test)
1、根据 stu 的 isa 找到 Student 的类对象
2、在类对象的 cache 散列表( _buckets)里面查找
   2.1 @selector(test) & _mask = index
   2.2 根据 index 取出对应的 _bucket_t 不为空
       2.2.1 取出 _bucket_t._key == @selector(test)对比
             相等,如果找到,取出 _bucket_t._imp 完成调用
             不相等,将 index -1 ,继续 2.2.1 流程,直到 index = _mask
   2.3 根据 index 取出对应的 _bucket_t 为空,走 3 流程         
3、通过 `bits.data()` 拿到 类的具体信息 class_rw_t rw;
4、通过 rw 找到 method_array_t methods,即 rw->methods
5、遍历 methods 这个二维数组 (method_list_t),找到了(method_t)结束调用,走 9
6、未找到,通过 class-> superclass 指针找到 父类的类对象
7、继续走 2->3->4->5 的流程  
8、最终在 Person 这个类对象的方法列表中找到方法 method_t 
9、完成调用
   9.1 同时,将 method_t 写入 stu 实例对应类对象的 cache 中 

三、消息机制
1、简介
OC方法调用,也称消息机制,给方法调用者发送消息。方法调用者称之为消息接受者。
OC 中的方法调用,其实都是转化成 objc_msgSend()函数调用。

#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        Person *person = [[Person alloc]init];
        [person test];
        //objc_msgSend(person, sel_registerName("test"));
        //消息接受者: person
        //发送消息: test
        
        [Person initialize];
       //objc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));
        //消息接受者: [Person class]
        //发送消息: initialize
    }
    return 0;
}

objc_msgSend的执行流程可以分为 3 大阶段:消息发送动态方法解析消息转发如果经历如上步骤还找不到合适的方法进行调用,会报错
unrecognized selector sent to instance 或者 unrecognized selector sent to class

贴下核心代码,源码中有注释,结合下注释一起看

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this class's cache.
    // 1、消息发送
    /**
      先找缓存 cache,再找 methodlist
     */
    // 1.1 缓存查找  
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    // 1.2 methodlist 列表查找
    {  
        // 这里是具体的查找方法
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    // 1.3 父类缓存查找
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)  //注意这里的赋值
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            // 1.4 父类方法列表的查找 
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
    // 2、动态方法解析
    /**
       如果没有进行动态解析,那么符合条件,进行消息解析,并将标志位设置为 YES
       如果已经进行过动态解析,那么不满足条件,之后进行消息转发
       也就是说,当动态解析过一次,之后就不会在动态解析了
     */
    if (resolver  &&  !triedResolver) { 
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    // 3、消息转发
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

贴下 methodlist 如果查找的 ,getMethodNoSuper_nolock具体实现

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

     // 相当于 调用类对象的 bits.data 得到 class_rw_t 得到类的具体信息
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        //真正在查找的方法
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

方法查找的具体实现 search_method_list

/***********************************************************************
* getMethodNoSuper_nolock
* fixme
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    //方法是否排序,如果是已经排序好了,那么使用二分法查找
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        // 如果方法未排序好,那么使用线性查找,就是 一层 for 循环
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}

排序好的方法实现折半查找的具体实现

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

1、消息发送阶段

obj -> isa -> class -> cache -> methodlist -> superclass -> cache -> methodlist ->…->superclass = nil -> 动态方法解析

如果 objnil,结束调用;
如果查找过程,找到方法实现,调用结束,并将方法写入 obj 的类对象 的 cache中;
对于 methodlist 中方法查找,如果方法已经排序,则为 二分法查找,如果未排序,则为暴力遍历查找。

2、动态方法解析

如果方法接收者是实例对象, 调用 [cls resolveInstanceMethod:sel]
如果方法接受者是类对象,调用 [cls resolveClassMethod:sel]

/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

举个例子: Person:NSObject 在Person的实现文件里,重写 resolveInstanceMethod方法

#import "Person.h"
#import <objc/runtime.h>
@implementation Person

- (void)test
{
    NSLog(@"%s",__func__);
}

//实例方法
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(abc)) {
        //获取 method_t
        Method method = class_getInstanceMethod(self, @selector(test));
        //获取 (method_t->_imp)
        IMP imp = method_getImplementation(method);
        //获取参数编码(method_t->_types) v16@0:8 隐藏参数  (id)obj 和 _cmd (当前selector) 都是指针,占 8 字节
        const char *encode =  method_getTypeEncoding(method);
       //动态添加方法
        if (class_addMethod(self, sel, imp, encode)) {
           //注意: 添加成功后,并标记 triedResolver = YES ,重新走消息发送流程
        }
        return YES;
    }
    return [super resolveClassMethod:sel];
}

@end


//main方法
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc]init];
        // 调用成功,输出 -[Person test]
        [person performSelector:@selector(abc)];
    }
    return 0;
}

注意: class_addMethod 添加成功后,会添加到 类对象信息 class_rw_t->methods 中。

3、消息转发

顾名思义: 当前对象无法处理消息,将消息转发给别人。
核心就下面三个方法,这三个方法都有对象方法、类方法 2 个版本(前面可以是 +、也可以是 -)

/*
   返回可以处理该消息的对象
   如果返回为空 `nil`,调用下 methodSignatureForSelector
   如果不为空 ,相当于 [target aSelector];
*/
- (id)forwardingTargetForSelector:(SEL)aSelector 

/* 
 如果上面 forwardingTargetForSelector 返回值为 nil, 
 则会调用 methodSignatureForSelector 该方法。返回一个函数签名
 如果函数签名不为空,则调用 forwardInvocation 方法,将函数签名交给其他 target
 其中: anInvocation 包含方法签名(返回值、参数) ,调用  [anInvocation invokeWithTarget:<#(nonnull id)#>] 传入一个可以处理该消息的 target 即可或者任意处理也没问题
 如果函数签名为空,直接报  unrecognized selector sent to instance/class 错误
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation 

举个例子:

将 Person 实例对象接受的消息转发给 Student 实例

// 1、Person 实现文件

#import "Person.h"
#import "Student.h"
@implementation Person
//返回可以处理该消息的对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(abc)) {
        return [[Student alloc]init];
    }
    return  [super forwardingTargetForSelector:aSelector];
}

//返回一个函数签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
     //参数编码必须与 test 保持一致
    if (aSelector == @selector(test)) {
        //这里其实就是 @encode() 参数编码. v16@0:8 解释:返回值为 void,默认参数(self 和 _cmd 指针)占 16 字节。第一个是 receiver(消息接受者)id 类型,编码后为 @ 从0开始,第二个是 SEL ,编码后为 : ,从第 8 个字节开始
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// anInvocation 封装了一个方法调用,包括方法调用者,方法名,方法参数
// anInvocation.target  方法调用者
//anInvocation.selector; //方法名
//[anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>] //参数

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 将消息转发给 target 去处理,当然你这里也可以任意处理
     //NSLog(@"%s",anInvocation.selector); 只写这一行也没问题,不会 crash
    [anInvocation invokeWithTarget:[Student new]];
}

// 2、Student 实现文件

#import "Student.h"

@implementation Student

- (void)abc
{
    NSLog(@"%s",__func__);
}

- (void)test{
    NSLog(@"%s",__func__);
}

@end


// 3、main 方法
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
 
        Person *person = [[Person alloc]init];
        [person performSelector:@selector(abc)];
        [person performSelector:@selector(test)];

    }
    return 0;
}

输出结果如下:

 -[Student abc]
 -[Student test]

下面看下 类方法的消息转发,基于上面的修改下

// 1、Person 类实现文件
#import "Person.h"
#import "Student.h"
@implementation Person

//返回可以处理该消息的对象
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(abc)) {
        //这里返回的当然,你这里返回实例对象也是可以的。只要实例对象有对应的实现即可。
        return [Student class];
    }
    return  [super forwardingTargetForSelector:aSelector];
}

//返回一个函数签名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        //这里其实就是 @encode() 参数编码
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// anInvocation 封装了一个方法调用,包括方法调用者,方法名,方法参数
// anInvocation.target  方法调用者
//anInvocation.selector; //方法名
//[anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>] //参数

+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 将消息转发给 target 去处理
    [anInvocation invokeWithTarget:[Student class]];
}

// 2、Student 类实现文件

#import "Student.h"

@implementation Student

+ (void)abc
{
    NSLog(@"%s",__func__);
}

+ (void)test{
    NSLog(@"%s",__func__);
}

@end

// 3、main 方法
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        [Person performSelector:@selector(abc)];
        [Person performSelector:@selector(test)];

    }
    return 0;
}

输出结果如下:

+[Student abc]
+[Student test]

四、关于 super 关键字

1、一道经典面试题
Student继承Person,并在 Student实现类重写 init方法,重写如下,问输出结果

#import "Student.h"

@implementation Student

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

结果:

[self class] : Student
[self superclass] :Person
[super class]: Student
super superclass] :Person

2、 objc_msgSendSuper

其实,super 关键字底层调用的是 objc_msgSendSuper这个方法,这个 API 源码在objc_msgSend正下方。

/** 
 * 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_msgSendSuper第一个参数是个指向 struct objc_super类型的结构体指针,此结构体定义如下

struct objc_super {
    __unsafe_unretained _Nonnull id receiver;         //消息接受者
    __unsafe_unretained _Nonnull Class super_class;  //消息接受者的父类
};

可知, receiver 还是当前对象 self, 而 calss方法在 NSObject中。从 GNUStep源码中,可知class方法的实现就是

- (Class) class
{
  return object_getClass(self);
}

由此,可以得出 : super 只是将方法的查找由父类方法列表开始查找,消息接受者还是当前对象。

五、 isKindOfClassisMemberOfClass

1、实现
关于 isKindOfClassisMemberOfClassruntime中实现如下

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

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}


+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

从上面可以看出,isKindOfClassisMemberOfClass 同时存在实例方法 和 类方法。

实例方法 isKindOfClass
判断当前对象的类对象和参数是否一致;或者当前对象的父类的类对象与参数是否一致

实例方法 isMemberOfClass
判断当前对象的类对象和参数是否一致;

类方法 isKindOfClass
判断当前类对象的元类对象与参数是否一致;或者当前类对象的父类的元类对象与参数是否一致

类方法 isMemberOfClass
判断当前类对象的元类对象与参数是否一致

2、细节

这里需要注意一个非常小的细节:

NSLog("%d",[Person isKindOfClass:[NSObject class]]); 

输出结果:

1

这是因为: Person 的元类对象 的 superclass指针指向 NSobject的元类对象,而NSobject元类对象的superclass指针,指向了 NSObject的类对象!!!

六、 runtime 常用 API

1、类相关

动态创建一个类(父类、类名、额外的内存空间), 后缀 pair 是成对的意思,因为生成了 类 对象和元类对象

Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes)

注册一个类(注册最好添加好成员变量、方法、协议、属性),类一旦被注册,就相当于这个类的类对象和元类对象就已经创建好了

objc_registerClassPair(Class cls)

销毁一个类

void objc_disposeClassPair(Class cls)

获取 isa 指向的 Class (用于获取 类对象、元类对象)

Class object_getClass(id obj)

设置 isa 指向 Class (KVO 底层实现原理有用到)

object_setClass(id obj,Class cls)

判断是否为 Class 对象

BOOL object_isClass(id obj)

判断一个 Class是否为元类

BOOL class_isMetaClass(Class cls)

获取父类

Class class)getSuperClass(Class cls)

2、成员变量相关

动态添加成员变量(已注册的类不能添加成员变量,因为成员变量放在 类对象结构体 class_ro_t 中的)

BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) 

获取成员变量信息对象

Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)

根据成员变量信息对象获取变量名

const char ivar_getName(Ivar _Nonnull v) 

根据成员信息对象获取变量的类型编码(即:@encode(type) )

 const char * ivar_getTypeEncoding(Ivar _Nonnull v) 

拷贝成员变量列表(最后需要用 free()函数进行释放)

Ivar * class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 

3、属性相关(参照成员变量)

4、方法相关

获取一个实例方法

Method class_getInstanceMethod(Class cls, SEL name)

获取一个类方法

Method class_getClassMethod(Class cls, SEL name)

获取方法的实现

IMP class_getMethodImplementation(Class cls, SEL name) 

交换两个方法的实现

void method_exchangeImplementations(Method m1, Method m2) 

拷贝方法列表

Method *class_copyMethodList(Class cls, unsigned int *outCount)

动态添加方法

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

做个简单的demo

#import <Foundation/Foundation.h>
#import <objc/runtime.h>


void run(id obj,SEL _md){
    
    NSLog(@"%s",__func__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        //创建这个类(Pair 成对的意思: 类对象、元类对象)
        Class dogClass = objc_allocateClassPair([NSObject class], "Dog", 0);
        
        //添加成员变量(一定要在注册类之前,因为成员变量是放在类对象结构中 class_ro_t ,是只读的)
        class_addIvar(dogClass, "_age", 4, 1, @encode(int));
        class_addIvar(dogClass, "_weight", 4, 1, @encode(int));
        
        //注册这个类
        objc_registerClassPair(dogClass);
        
        //添加方法(方法可以放在注册类之后,因为方法是放在类对象结构体中 class_rw_t,可读可写)
        class_addMethod(dogClass, @selector(run), (IMP)run, "v16@0:8");
        
        id dog = [[dogClass alloc] init];
        [dog setValue:@10 forKey:@"_age"];
        [dog setValue:@15 forKey:@"_weight"];
        [dog run];
        //[dog performSelector:@selector(run)];
        
        
        NSLog(@"%@---%@",[dog valueForKey:@"_age"],[dog valueForKey:@"_weight"]);
        
        NSLog(@"%zd",class_getInstanceSize(dogClass));
       
        //获取成员变量信息
        Ivar ageIvar = class_getInstanceVariable(dogClass, "_age");
        //获取成员变量的名字和编码
        const char *name =  ivar_getName(ageIvar);
        //获取成员变量的编码
        const char *nameEndode = ivar_getTypeEncoding(ageIvar);
        NSLog(@"%s----%s",name,nameEndode);
        
        
        //获取成员变量信息
        unsigned int ivarCount;
        Ivar *ivarlist = class_copyIvarList(dogClass, &ivarCount);
        for (int i = 0; i < ivarCount; i++) {
            Ivar ivar = ivarlist[i];
            const char *ivarName = ivar_getName(ivar);
            NSLog(@"%s",ivarName);
        }
        free(ivarlist);
        
        //获取方法列表
        unsigned int methodCount;
        Method *methodList = class_copyMethodList(dogClass, &methodCount);
        for (int index = 0; index < methodCount; index++) {
            Method method = methodList[index];
            NSString *methodName = NSStringFromSelector(method_getName(method));
            NSLog(@"%@",methodName);
        }
        //释放
        free(methodList);
        
        //销毁类
        objc_disposeClassPair(dogClass);
        
    }
    return 0;
}

七、相关应用

1、遍历访问成员变量(修改 textfield 的占位属性、字典转模型、解归档 等)

修改 TextField placeholderText 文本颜色、字体大小
思路: 适应 class_copyIvarList遍历 UITextField 的 Ivar 得到成员变量,然后 利用 KVC 直接修改

//修改字体颜色
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placholderLable.textColor"]];

//修改字体大小
[self.textField setValue:[UIFont fontWithSize:18 ]forKeyPath:@"_placholderLable.textFont"]];

当然了,你也可以用 attribute去进行设置啦。这里有个小细节需要注意下: UIKit框架下的很多控件都是用懒加载实现的,所以必须现有placeholder 后上面的设置才能生效。

字典转模型

为 NSObject 添加分类,分类中实现字典转模型的方法(简配版)

#import "NSObject+Category.h"
#import <objc/runtime.h>
@implementation NSObject (Category)

+ (instancetype)yfl_objectWithJson:(NSDictionary*)json
{
    id obj = [[self alloc]init];
    
    //遍历
    unsigned int count;
    //得到成员变量列表
    Ivar *ivars = class_copyIvarList(self, &count);
    //变量成员变量列表
    for (int index = 0; index < count; index++) {
        //得到成员变量信息
        Ivar ivar = ivars[index];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        //去掉开头"_"
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        //赋值
        if ([json valueForKey:name]) {
            [obj setValue:[json valueForKey:name] forKey:name];
        }
    }
    return obj;
}

@end

自动解归解档
例子参见这里

2、关联对象

利用关联对象给 category 添加属性

//添加属性
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
//获取属性
id objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

3、交换方法实现(hook)

交换方法的底层实现为: 实质为交换 IMP,相当于 类对象中 bits.data()获取类具体信息 class_rw_t->methodsmethod_t中的imp,且当发生方法交换,类对象中的方法缓存 canhe会被擦除(flushCaches(nil))

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    rwlock_writer_t lock(runtimeLock);
    //采用中间值,交换两者的 imp 
    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;
    
    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?
    //清除 class 中的 cache
    flushCaches(nil);

    updateCustomRR_AWZ(nil, m1);
    updateCustomRR_AWZ(nil, m2);
}

缓存擦除(erase)的方法

static void flushCaches(Class cls)
{
    runtimeLock.assertWriting();

    mutex_locker_t lock(cacheUpdateLock);

    if (cls) {
        foreach_realized_class_and_subclass(cls, ^(Class c){
            cache_erase_nolock(c);
        });
    }
    else {
        foreach_realized_class_and_metaclass(^(Class c){
            cache_erase_nolock(c);
        });
    }
}

事件埋点,统计项目中所有按钮的点击动作。由于UIButton:UIControl,所以可以直接为UIControl创建分类,自定义方法与sendAction:to:forEvent:交换 IMP ,具体实现如下:

#import "UIControl+Category.h"
#import <objc/runtime.h>
@implementation UIControl (Category)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Method originMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method targetMethod = class_getInstanceMethod(self, @selector(yfl_sendAction:to:forEvent:));
       // cache 会被清空
        method_exchangeImplementations(originMethod, targetMethod);
    });
}


- (void)yfl_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    //注意: 这里不是递归,而是调用原来的实现
    [self yfl_sendAction:action to:target forEvent:event];
    
    if ([self isKindOfClass:[UIButton class]]) {
        //可以在这里埋点按钮事件点击
        NSLog(@"%@---%@--%ld",NSStringFromSelector(action),target,event.type);
    }
}
@end

进一步图解
在这里插入图片描述
其他应用,例如 App 仿闪退 JJException

4、利用消息转发机制解决方法找不到的异常

 思路:  重写消息转发的两个方法
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值