美文网首页
runtime浅析

runtime浅析

作者: 星空梦想 | 来源:发表于2019-11-12 17:03 被阅读0次

    参阅文章‘https://www.jianshu.com/p/a3fb0919877c’

    第一章

    1.简介

    Object-C是一门动态性比较强的编程语言,跟C,C++有着很大的不

    编写->编译->运行

    Objective-C的动态性是由Runtime API来支撑的

    Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

    2.isa详解

    要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针

    在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址

    从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

    OC对象的isa指针并不是直接指向类对象或者元类对象,而是需要&ISA_MASK通过位运算才能获取到类对象或者元类对象的地址。为什么需要&ISA_MASK才能获取到类对象或者元类对象的地址,以及这样的好处。

         // 截取objc_object内部分代码

         struct objc_object {

            private:

                isa_t isa;

         }

    isa指针其实是一个isa_t类型的共用体,来到isa_t内部查看其结构

    isa_t是union类型,union表示共用体。可以看到共用体中有一个结构体,结构体内部分别定义了一些变量,变量后面的值代表的是该变量占用多少个二进制位,也就是位域技术。

          // 精简过的isa_t共用体

         union isa_t

         {

             isa_t() { }

             isa_t(uintptr_t value) : bits(value) { }

            Class cls;

            uintptr_t bits;

           #if SUPPORT_PACKED_ISA

           # if __arm64__      

           #   define ISA_MASK        0x0000000ffffffff8ULL

           #   define ISA_MAGIC_MASK  0x000003f000000001ULL

           #   define ISA_MAGIC_VALUE 0x000001a000000001ULL

                struct {

                    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 deallocating      : 1;

                    uintptr_t has_sidetable_rc  : 1;

                    uintptr_t extra_rc          : 19;

               #       define RC_ONE   (1ULL<<45)

               #       define RC_HALF  (1ULL<<18)

              };

        # elif __x86_64__     

        #   define ISA_MASK        0x00007ffffffffff8ULL

        #   define ISA_MAGIC_MASK  0x001f800000000001ULL

        #   define ISA_MAGIC_VALUE 0x001d800000000001ULL

                struct {

                   // 1代表优化后的使用位域存储更多的信息。0代表普通的指针,存储着Class,Meta-Class对象的内存地址。

                    uintptr_t nonpointer        : 1;/

                    uintptr_t has_assoc         : 1;//是否有设置过关联对象,如果没有,释放时会更快

                    uintptr_t has_cxx_dtor      : 1; //是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快

    uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS

    0x7fffffe00000  

    //存储着Class、Meta-Class对象的内存地址信息,由于要进行位运算,所以地址值末三位都为000(此情况为二进制)

                    uintptr_t magic             : 6; //用于在调试时分辨对象是否未完成初始化

                    uintptr_t weakly_referenced : 1; //是否有被弱引用指向过,如果没有,释放时会更快

                    uintptr_t deallocating      : 1; //对象是否正在释放

                    uintptr_t has_sidetable_rc  : 1;  //引用计数器是否过大无法存储在isa中 ,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

                    uintptr_t extra_rc          : 8;  //里面存储的值是引用计数器减1

            #       define RC_ONE   (1ULL<<56)

            #       define RC_HALF  (1ULL<<7)

                };

        # else

        #   error unknown architecture for packed isa

        # endif

        #endif

    第二章

    1.对class对象简单回顾

         struct objc_class : objc_object {

            // 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_t *data() {

                return bits.data();

            }

            void setData(class_rw_t *newData) {

                bits.setData(newData);

            }

        }

         class_rw_t* data() {

            return (class_rw_t *)(bits & FAST_DATA_MASK);

        }

    bits & FAST_DATA_MASK位运算之后,可以得到class_rw_t,而class_rw_t中存储着方法列表、属性列表以及协议列表

    2.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;

        };

    上述源码中,method_array_t、property_array_t、protocol_array_t其实都是二维数组,来到method_array_t、property_array_t、protocol_array_t内部看一下。这里以method_array_t为例,method_array_t本身就是一个数组,数组里面存放的是数组method_list_t,method_list_t里面最终存放的是method_t

        class method_array_t :

            public list_array_tt<method_t, method_list_t>

        {

            typedef list_array_tt<method_t, method_list_t> Super;

         public:

            method_list_t **beginCategoryMethodLists() {

                return beginLists();

            }

            method_list_t **endCategoryMethodLists(Class cls);

            method_array_t duplicate() {

                return Super::duplicate<method_array_t>();

            }

        };

        class property_array_t :

            public list_array_tt<property_t, property_list_t>

        {

            typedef list_array_tt<property_t, property_list_t> Super;

         public:

            property_array_t duplicate() {

                return Super::duplicate<property_array_t>();

            }

        };

        class protocol_array_t :

            public list_array_tt<protocol_ref_t, protocol_list_t>

        {

            typedef list_array_tt<protocol_ref_t, protocol_list_t> Super;

         public:

            protocol_array_t duplicate() {

                return Super::duplicate<protocol_array_t>();

            }

        };

    class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容

    3.class_ro_t部分代码

        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_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。数组里面分别存放的是类的初始信息,以method_list_t为例,method_list_t中直接存放的就是method_t,但是是只读的,不允许增加删除修改。

    一开始类的方法,属性,成员变量属性协议等等都是存放在class_ro_t中的,当程序运行的时候,需要将分类中的列表跟类初始的列表合并在一起的时,就会将class_ro_t中的列表和分类中的列表合并起来存放在class_rw_t中,也就是说class_rw_t中有部分列表是从class_ro_t里面拿出来的。并且最终和分类的方法合并。

    ###4.realizeClass部分源码

        static Class realizeClass(Class cls)

        {

            runtimeLock.assertWriting();

            const class_ro_t *ro;

            class_rw_t *rw;

            Class supercls;

            Class metacls;

            bool isMeta;

            if (!cls) return nil;

            if (cls->isRealized()) return cls;

            assert(cls == remapClass(cls));

            // 最开始cls->data是指向ro的

            ro = (const class_ro_t *)cls->data();

            if (ro->flags & RO_FUTURE) {

                // rw已经初始化并且分配内存空间

                rw = cls->data();  // cls->data指向rw

                ro = cls->data()->ro;  // cls->data()->ro指向ro

                cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);

            } else {

                // 如果rw并不存在,则为rw分配空间

                rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); // 分配空间

                rw->ro = ro;  // rw->ro重新指向ro

                rw->flags = RW_REALIZED|RW_REALIZING;

                // 将rw传入setData函数,等于cls->data()重新指向rw

                cls->setData(rw);

            }

        }

    从上述源码中就可以发现,类的初始信息本来其实是存储在class_ro_t中的,并且ro本来是指向cls->data()的,也就是说bits.data()得到的是ro,但是在运行过程中创建了class_rw_t,并将cls->data指向rw,同时将初始信息ro赋值给rw中的ro。最后在通过setData(rw)设置data。那么此时bits.data()得到的就是rw,之后再去检查是否有分类,同时将分类的方法,属性,协议列表整合存储在class_rw_t的方法,属性及协议列表中。

    ###5.method_t

    ######method_t是对方法\函数的封装

          struct method_t {

              SEL name;  // 函数名

              const char *types;  // 编码(返回值类型,参数类型)

              IMP imp; // 指向函数的指针(函数地址)

          };

    ######IMP代表函数的具体实现

                typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull,...);

    ######SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似

                可以通过@selector()和sel_registerName()获得

                可以通过sel_getName()和NSStringFromSelector()转成字符串

                不同类中相同名字的方法,所对应的方法选择器是相同的

              typedef struct objc_selector *SEL;        

    ######type包含了函数返回值,参数编码的字符串

    ######Type Encoding

    apple为了能够清晰的使用字符串表示方法及其返回值,制定了一系列对应规则

        将types的值同表中的一一对照查看types的值v16@0:8 代表什么

        - (void) test;

        v    16      @     0     :     8

        void         id          SEL

        // 16表示参数的占用空间大小,id后面跟的0表示从0位开始存储,id占8位空间。

        // SEL后面的8表示从第8位开始存储,SEL同样占8位空间

        - (int)testWithAge:(int)age Height:(float)height

        {

            return 0;

        }

          i    24    @    0    :    8    i    16    f    20

         int         id        SEL       int        float

        // 参数的总占用空间为 8 + 8 + 4 + 4 = 24

        // id 从第0位开始占据8位空间

        // SEL 从第8位开始占据8位空间

        // int 从第16位开始占据4位空间

        // float 从第20位开始占据4位空间

     iOS提供了@encode的指令,可以将具体的类型转化成字符串编码。

         NSLog(@"%s",@encode(int));

        NSLog(@"%s",@encode(float));

        NSLog(@"%s",@encode(id));

        NSLog(@"%s",@encode(SEL));

        // 打印内容

        Runtime-test[25275:9144176] i

        Runtime-test[25275:9144176] f

        Runtime-test[25275:9144176] @

        Runtime-test[25275:9144176] :   

    ###6.方法缓存

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

    ######cache_t的内部结构

        struct cache_t {

            struct bucket_t *_buckets; // 散列表 数组

            mask_t _mask; // 散列表的长度 -1

            mask_t _occupied; // 已经缓存的方法数量

        };

    ######bucket_t是以数组的方式存储方法列表的,看一下bucket_t内部结构

        struct bucket_t {

        private:

            cache_key_t _key; // SEL作为Key

            IMP _imp; // 函数的内存地址

        };

    ######从源码中可以看出bucket_t中存储着SEL和_imp,通过key->value的形式,以SEL为key,函数实现的内存地址 _imp为value来存储方法

    ————————————————

    #第二章补充 散列表算法

    ###散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

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

    struct   cache_t {

    struct bucket_t *_buckets;//散列表

    mask_t _mask;//散列表长度  - 1

    mask_t _occupied;//已经缓存的方法数量

    }

    struct bucket_t {

    cache_key_t _key;//SEL作为key

    IMP _imp;//函数的内存地址

    }

    ###散列函数及散列表原理

    ###1.cache_fill 及 cache_fill_nolock 函数

    void cache_fill(Class cls, SEL sel, IMP imp, id receiver)

    {

    #if !DEBUG_TASK_THREADS

    mutex_locker_t lock(cacheUpdateLock);

    cache_fill_nolock(cls, sel, imp, receiver);

    #else

    _collecting_in_critical();

    return;

    #endif

    }

    static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)

    {

    cacheUpdateLock.assertLocked();

    // 如果没有initialize直接return

    if (!cls->isInitialized()) return;

    // 确保线程安全,没有其他线程添加缓存

    if (cache_getImp(cls, sel)) return;

    // 通过类对象获取到cache

    cache_t *cache = getCache(cls);

    // 将SEL包装成Key

    cache_key_t key = getKey(sel);

    // 占用空间+1

    mask_t newOccupied = cache->occupied() + 1;

    // 获取缓存列表的缓存能力,能存储多少个键值对

    mask_t capacity = cache->capacity();

    if (cache->isConstantEmptyCache()) {

    // 如果为空的,则创建空间,这里创建的空间为4个。

    cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);

    }

    else if (newOccupied <= capacity / 4 * 3) {

    // 如果所占用的空间占总数的3/4一下,则继续使用现在的空间

    }

    else {

    // 如果占用空间超过3/4则扩展空间

    cache->expand();

    }

    // 通过key查找合适的存储空间。

    bucket_t *bucket = cache->find(key, receiver);

    // 如果key==0则说明之前未存储过这个key,占用空间+1

    if (bucket->key() == 0) cache->incrementOccupied();

    // 存储key,imp

    bucket->set(key, imp);

    }

    ###2.reallocate 函数

    ######reallocate函数负责分配散列表空间

    void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)

    {

    // 旧的散列表能否被释放

    bool freeOld = canBeFreed();

    // 获取旧的散列表

    bucket_t *oldBuckets = buckets();

    // 通过新的空间需求量创建新的散列表

    bucket_t *newBuckets = allocateBuckets(newCapacity);

    assert(newCapacity > 0);

    assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    // 设置Buckets和Mash,Mask的值为散列表长度-1

    setBucketsAndMask(newBuckets, newCapacity - 1);

    // 释放旧的散列表

    if (freeOld) {

    cache_collect_free(oldBuckets, oldCapacity);

    cache_collect(false);

    }

    }

    enum {

    INIT_CACHE_SIZE_LOG2 = 2,

    INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2)

    };

    ###3.expand ()函数

    ######当散列表的空间被占用超过3/4的时候,散列表会调用expand ()函数进行扩展

    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) {

    newCapacity = oldCapacity;

    }

    // 调用reallocate函数,重新创建存储空间

    reallocate(oldCapacity, newCapacity);

    }

    ###4.find 函数

    ######最后来看一下散列表中如何快速的通过key找到相应的bucket呢?我们来到find函数内部

    bucket_t * cache_t::find(cache_key_t k, id receiver)

    {

    assert(k != 0);

    // 获取散列表

    bucket_t *b = buckets();

    // 获取mask

    mask_t m = mask();

    // 通过key找到key在散列表中存储的下标

    mask_t begin = cache_hash(k, m);

    // 将下标赋值给i

    mask_t i = begin;

    // 如果下标i中存储的bucket的key==0说明当前没有存储相应的key,将b[i]返回出去进行存储

    // 如果下标i中存储的bucket的key==k,说明当前空间内已经存储了相应key,将b[i]返回出去进行存储

    do {

    if (b[i].key() == 0  ||  b[i].key() == k) {

    // 如果满足条件则直接reutrn出去

    return &b[i];

    }

    // 如果走到这里说明上面不满足,那么会往前移动一个空间重新进行判定,知道可以成功return为止

    } 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)用来通过key找到方法在散列表中存储的下标,来到cache_hash (k, m)函数内部

    static inline mask_t cache_hash(cache_key_t key, mask_t mask)

    {

    return (mask_t)(key & mask);

    }

    ######当第一次使用方法时,消息机制通过isa找到方法之后,会对方法以SEL为keyIMP为value的方式缓存在cache的_buckets中,当第一次存储的时候,会创建具有4个空间的散列表,并将_mask的值置为散列表的长度减一,之后通过SEL & mask计算出方法存储的下标值,并将方法存储在散列表中。举个例子,如果计算出下标值为3,那么就将方法直接存储在下标为3的空间中,前面的空间会留空。

    当散列表中存储的方法占据散列表长度超过3/4的时候,散列表会进行扩容操作,将创建一个新的散列表并且空间扩容至原来空间的两倍,并重置_mask的值,最后释放旧的散列表,此时再有方法要进行缓存的话,就需要重新通过SEL & mask计算出下标值之后在按照下标进行存储了。

    如果一个类中方法很多,其中很可能会出现多个方法的SEL & mask得到的值为同一个下标值,那么会调用cache_next函数往下标值-1位去进行存储,如果下标值-1位空间中有存储方法,并且key不与要存储的key相同,那么再到前面一位进行比较,直到找到一位空间没有存储方法或者key与要存储的key相同为止,如果到下标0的话就会到下标为_mask的空间也就是最大空间处进行比较。

    当要查找方法时,并不需要遍历散列表,同样通过SEL & mask计算出下标值,直接去下标值的空间取值即可,同上,如果下标值中存储的key与要查找的key不相同,就去前面一位查找。这样虽然占用了少量控件,但是大大节省了时间,也就是说其实apple是使用空间换取了存取的时间。

    #第三章objc_msgSend

    ####1.OC中的方法调用,其实都是转换为objc_msgSend函数的调用

    objc_msgSend的执行流程可以分为3大阶段

    消息发送:负责从类及父类的缓存列表及方法列表查找方法

    动态方法解析:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现。

    消息转发:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理

    ####2.objc_msgSend执行流程 – 源码跟读

    objc-msg-arm64.s文件中

    //ENTRY 入口

    ENTRY _objc_msgSend

    UNWIND _objc_msgSend, NoFrame

    MESSENGER_START

    //判断是否为0,x0为objcd的第一个参数,receiver

    //x0称之为寄存器:消息接受者,

    cmp x0, #0          // nil check and tagged pointer check

    //b指令为跳转  le为跳转的条件  LNilOrTagged内部会执行LReturnZero->return 0

    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)

    ldr x13, [x0]       // x13 = isa

    and x16, x13, #ISA_MASK // x16 = class

    //得到isa

    LGetIsaDone:

    //查找缓存

    CacheLookup NORMAL        // calls imp or objc_msgSend_uncached

    //b 指令为跳转

    b.le    LNilOrTagged

    CacheLookup NORMAL

    //macro 为枚举标示

    //CacheLookup 内部对方法缓存列表进行查找

    .macro CacheLookup

    //命中

    CacheHit $0            // call or return imp

    //或没有命中

    CheckMiss $0            // miss if bucket->sel == 0

    .macro CheckMiss //失败,执行下面方法

    STATIC_ENTRY __objc_msgSend_uncached

    //调用下面函数

    __objc_msgSend_uncached

    //查找

    MethodTableLookup  //方法列表查找

    __class_lookupMethodAndLoadCache3

    汇编方法名字 __class_lookupMethodAndLoadCache3

    c语言方法名字 _class_lookupMethodAndLoadCache3

    objc-runtime-new.mm

    _class_lookupMethodAndLoadCache3

    lookUpImpOrForward

    getMethodNoSuper_nolock、search_method_list、log_and_fill_cache

    cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache

    _class_resolveInstanceMethod

    _objc_msgForward_impcache

    objc-msg-arm64.s

    STATIC_ENTRY __objc_msgForward_impcache

    ENTRY __objc_msgForward

    Core Foundation

    __forwarding__(不开源)

    #####3._class_lookupMethodAndLoadCache3 函数

    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)

    {

    return lookUpImpOrForward(cls, sel, obj,

    YES/*initialize*/, NO/*cache*/, YES/*resolver*/);

    }

    #####4.lookUpImpOrForward 函数

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst,

    bool initialize, bool cache, bool resolver)

    {

    // initialize = YES , cache = NO , resolver = YES

    IMP imp = nil;

    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 缓存查找, 因为cache传入的为NO, 这里不会进行缓存查找, 因为在汇编语言中CacheLookup已经查找过

    if (cache) {

    imp = cache_getImp(cls, sel);

    if (imp) return imp;

    }

    runtimeLock.read();

    if (!cls->isRealized()) {

    runtimeLock.unlockRead();

    runtimeLock.write();

    realizeClass(cls);

    runtimeLock.unlockWrite();

    runtimeLock.read();

    }

    if (initialize  &&  !cls->isInitialized()) {

    runtimeLock.unlockRead();

    _class_initialize (_class_getNonMetaClass(cls, inst));

    runtimeLock.read();

    }

    retry:

    runtimeLock.assertReading();

    // 防止动态添加方法,缓存会变化,再次查找缓存。

    imp = cache_getImp(cls, sel);

    // 如果查找到imp, 直接调用done, 返回方法地址

    if (imp) goto done;

    // 查找方法列表, 传入类对象和方法名

    {

    // 根据sel去类对象里面查找方法

    Method meth = getMethodNoSuper_nolock(cls, sel);

    if (meth) {

    // 如果方法存在,则缓存方法,

    // 内部调用的就是 cache_fill 上文中已经详细讲解过这个方法,这里不在赘述了。

    log_and_fill_cache(cls, meth->imp, sel, inst, cls);

    // 方法缓存之后, 取出imp, 调用done返回imp

    imp = meth->imp;

    goto done;

    }

    }

    // 如果类方法列表中没有找到, 则去父类的缓存中或方法列表中查找方法

    {

    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.");

    }

    // 查找父类的缓存

    imp = cache_getImp(curClass, sel);

    if (imp) {

    if (imp != (IMP)_objc_msgForward_impcache) {

    // 在父类中找到方法, 在本类中缓存方法, 注意这里传入的是cls, 将方法缓存在本类缓存列表中, 而非父类中

    log_and_fill_cache(cls, imp, sel, inst, curClass);

    // 执行done, 返回imp

    goto done;

    }

    else {

    // 跳出循环, 停止搜索

    break;

    }

    }

    // 查找父类的方法列表

    Method meth = getMethodNoSuper_nolock(curClass, sel);

    if (meth) {

    // 同样拿到方法, 在本类进行缓存

    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);

    imp = meth->imp;

    // 执行done, 返回imp

    goto done;

    }

    }

    }

    // ---------------- 消息发送阶段完成 ---------------------

    // ---------------- 进入动态解析阶段 ---------------------

    // 上述列表中都没有找到方法实现, 则尝试解析方法

    if (resolver  &&  !triedResolver) {

    runtimeLock.unlockRead();

    _class_resolveMethod(cls, sel, inst);

    runtimeLock.read();

    triedResolver = YES;

    goto retry;

    }

    // ---------------- 动态解析阶段完成 ---------------------

    // ---------------- 进入消息转发阶段 ---------------------

    imp = (IMP)_objc_msgForward_impcache;

    cache_fill(cls, sel, imp, inst);

    done:

    runtimeLock.unlockRead();

    // 返回方法地址

    return imp;

    }

    #####5.getMethodNoSuper_nolock 函数

    getMethodNoSuper_nolock(Class cls, SEL sel)

    {

    runtimeLock.assertLocked();

    assert(cls->isRealized());

    // cls->data() 得到的是 class_rw_t

    // class_rw_t->methods 得到的是methods二维数组

    for (auto mlists = cls->data()->methods.beginLists(),

    end = cls->data()->methods.endLists();

    mlists != end;

    ++mlists)

    {

    // mlists 为 method_list_t

    method_t *m = search_method_list(*mlists, sel);

    if (m) return m;

    }

    return nil;

    }

    ######源码中getMethodNoSuper_nolock函数中通过遍历方法列表拿到method_list_t最终通过search_method_list函数查找方法

    #####6.search_method_list函数

    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 {

    // 否则则遍历列表查找

    for (auto& meth : *mlist) {

    if (meth.name == sel) return &meth;

    }

    }

    return nil;

    }

    #####7.findMethodInSortedMethodList函数内二分查找实现原理

    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;

    // >>1 表示将变量n的各个二进制位顺序右移1位,最高位补二进制0。

    // count >>= 1 如果count为偶数则值变为(count / 2)。如果count为奇数则值变为(count-1) / 二分查找

    for (count = list->count; count != 0; count >>= 1) {

    // probe 指向数组中间的值

    probe = base + (count >> 1);

    // 取出中间method_t的name,也就是SEL

    uintptr_t probeValue = (uintptr_t)probe->name;

    if (keyValue == probeValue) {

    // 取出 probe

    while (probe > first && keyValue == (uintptr_t)probe[-1].name) {

    probe--;

    }

    // 返回方法

    return (method_t *)probe;

    }

    // 如果keyValue > probeValue 则折半向后查询

    if (keyValue > probeValue) {

    base = probe + 1;

    count--;

    }

    }

    return nil;

    }

    ####3.objc_msgSend执行流程02-动态方法解析

    开发者可以实现以下方法,来动态添加方法实现

    +resolveInstanceMethod:

    +resolveClassMethod:

    动态解析过后,会重新走“消息发送”的流程

    “从receiverClass的cache中查找方法”这一步开始执行

    ######以实例对象为例通过代码来看一下动态解析的过程

    @implementation Person

    - (void) other {

    NSLog(@"%s", __func__);

    }

    + (BOOL)resolveInstanceMethod:(SEL)sel

    {

    // 动态的添加方法实现

    if (sel == @selector(test)) {

    // 获取其他方法 指向method_t的指针

    Method otherMethod = class_getInstanceMethod(self, @selector(other));

    // 动态添加test方法的实现

    //获取IMP  method_getImplementation(otherMethod)

    //获取type  method_getTypeEncoding(otherMethod)

    class_addMethod(self, sel,method_getImplementation(otherMethod),method_getTypeEncoding(otherMethod));

    // 返回YES表示有动态添加方法

    return YES;

    }

    NSLog(@"%s", __func__);

    return [superresolveInstanceMethod:sel];

    }

    @end

    ######本类和父类cache和class_rw_t中都找不到方法时,就会进行动态解析的方法,也就是说会自动调用类的resolveInstanceMethod:方法进行动态查找。因此我们可以在resolveInstanceMethod:方法内部使用class_addMethod动态的添加方法实现。

    /**

    第一个参数: cls:给哪个类添加方法

    第二个参数: SEL name:添加方法的名称

    第三个参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)

    第四个参数: types :方法类型,需要用特定符号,参考API

    */

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

    class_addMethod用来向具有给定名称和实现的类添加新方法,class_addMethod将添加一个方法实现的覆盖,但是不会替换已有的实现

    ######Method是objc_method类型结构体,可以理解为其内部结构同method_t结构体相同,上文中提到过method_t是代表方法的结构体,其内部包含SEL、type、IMP

    struct method_t {

    SEL sel;

    char *types;

    IMP imp;

    };

    - (void) other {

    NSLog(@"%s", __func__);

    }

    + (BOOL)resolveInstanceMethod:(SEL)sel

    {

    // 动态的添加方法实现

    if (sel == @selector(test)) {

    // Method强转为method_t

    struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));

    NSLog(@"%s,%p,%s",method->sel,method->imp,method->types);

    // 动态添加test方法的实现

    class_addMethod(self, sel, method->imp, method->types);

    // 返回YES表示有动态添加方法

    return YES;

    }

    NSLog(@"%s", __func__);

    return [superresolveInstanceMethod:sel];

    }

    ######动态解析的方法

    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;

    }

    ######_class_resolveMethod函数内部,根据类对象或元类对象做不同的操作

    void _class_resolveMethod(Class cls, SEL sel, id inst)

    {

    //不是元类对象

    if (! cls->isMetaClass()) {

    // try [clsresolveInstanceMethod:sel]

    _class_resolveInstanceMethod(cls, sel, inst);

    }

    else {

    // try [nonMetaClassresolveClassMethod:sel]

    // and [clsresolveInstanceMethod:sel]

    _class_resolveClassMethod(cls, sel, inst);

    if (!lookUpImpOrNil(cls, sel, inst,

    NO/*initialize*/, YES/*cache*/, NO/*resolver*/))

    {

    _class_resolveInstanceMethod(cls, sel, inst);

    }

    }

    }

    ######无论我们是否实现了动态解析的方法,系统内部都会执行retry对方法再次进行查找,那么如果我们实现了动态解析方法,此时就会顺利查找到方法,进而返回imp对方法进行调用。如果我们没有实现动态解析方法。就会进行消息转发。

    ####4.消息转发

    ######消息转发:将消息转发给别人

    _cache_addForwardEntry(cls, sel);

    methodPC = _objc_msgForward_impcache;

    STATIC_ENTRY    __objc_msgForward_impcache

    // Method cache version

    // THIS IS NOT A CALLABLE C FUNCTION

    // Out-of-band register %edx is nonzero for stret, zero otherwise

    // Check return type (stret or not)

    testl    %edx, %edx

    jnz    __objc_msgForward_stret

    jmp    __objc_msgForward

    END_ENTRY    _objc_msgForward_impcache

    ENTRY    __objc_msgForward

    // Non-struct return version

    // Get PIC base into %edx

    call    L__objc_msgForward$pic_base

    L__objc_msgForward$pic_base:

    popl    %edx

    // Call user handler, if any

    movl    L_forward_handler-L__objc_msgForward$pic_base(%edx),%ecx

    movl    (%ecx), %ecx

    testl    %ecx, %ecx        // if not NULL

    je    1f            //   skip to default handler

    jmp    *%ecx            // call __objc_forward_handler

    1:

    // No user handler

    // Push stack frame

    pushl   %ebp

    movl    %esp, %ebp

    // Die if forwarding "forward::"

    movl    (selector+4)(%ebp), %eax

    movl    _FwdSel-L__objc_msgForward$pic_base(%edx),%ecx

    cmpl    %ecx, %eax

    je    LMsgForwardError

    // Call [receiverforward:sel:margs]

    subl    $8, %esp        // 16-byte align the stack

    leal    (self+4)(%ebp), %ecx

    pushl    %ecx            // &margs

    pushl    %eax            // sel

    movl    _FwdSel-L__objc_msgForward$pic_base(%edx),%ecx

    pushl    %ecx            // forward::

    pushl   (self+4)(%ebp)        // receiver

    call    _objc_msgSend

    movl    %ebp, %esp

    popl    %ebp

    ret

    ######当本类没有实现方法,并且没有动态解析方法,就会调用forwardingTargetForSelector函数,进行消息转发,我们可以实现forwardingTargetForSelector函数,在其内部将消息转发给可以实现此方法的对象。

    ######如果forwardingTargetForSelector函数返回为nil或者没有实现的话,就会调用methodSignatureForSelector方法,用来返回一个方法签名,这也是我们正确跳转方法的最后机会。

    ######如果methodSignatureForSelector方法返回正确的方法签名就会调用forwardInvocation方法,forwardInvocation方法内提供一个NSInvocation类型的参数,NSInvocation封装了一个方法的调用,包括方法的调用者,方法名,以及方法的参数。在forwardInvocation函数内修改方法调用对象即可。

    ######如果methodSignatureForSelector返回的为nil,就会来到doseNotRecognizeSelector:方法内部,程序crash提示无法识别选择器unrecognized selector sent to instance。

    - (id)forwardingTargetForSelector:(SEL)aSelector

    {

    // 返回能够处理消息的对象

    if (aSelector == @selector(driving)) {

    // 返回nil则会调用methodSignatureForSelector方法

    return nil;

    // return [[Car alloc] init];

    }

    return [superforwardingTargetForSelector:aSelector];

    }

    // 方法签名:返回值类型、参数类型

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

    {

    if (aSelector == @selector(driving)) {

    // return [NSMethodSignature signatureWithObjCTypes: "v@:"];

    // return [NSMethodSignature signatureWithObjCTypes: "v16@0:8"];

    // 也可以通过调用Car的methodSignatureForSelector方法得到方法签名,这种方式需要car对象有aSelector方法

    return [[[Car alloc] init] methodSignatureForSelector: aSelector];

    }

    return [supermethodSignatureForSelector:aSelector];

    }

    ######NSInvocation

    ######methodSignatureForSelector方法中返回的方法签名,在forwardInvocation中被包装成NSInvocation对象,NSInvocation提供了获取和修改方法名、参数、返回值等方法,也就是说,在forwardInvocation函数中我们可以对方法进行最后的修改。

    同样上述代码,我们为driving方法添加返回值和参数,并在forwardInvocation方法中修改方法的返回值及参数。

    #import "Car.h"

    @implementation Car

    - (int) driving:(int)time

    {

    NSLog(@"car driving %d",time);

    return time * 2;

    }

    @end

    #import "Person.h"

    #import

    #import "Car.h"

    @implementation Person

    - (id)forwardingTargetForSelector:(SEL)aSelector

    {

    // 返回能够处理消息的对象

    if (aSelector == @selector(driving)) {

    return nil;

    }

    return [superforwardingTargetForSelector:aSelector];

    }

    // 方法签名:返回值类型、参数类型

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

    {

    if (aSelector == @selector(driving:)) {

    // 添加一个int参数及int返回值type为 i@:i

    return [NSMethodSignature signatureWithObjCTypes: "i@:i"];

    }

    return [supermethodSignatureForSelector:aSelector];

    }

    //NSInvocation 封装了一个方法调用,包括:方法调用者,方法名,方法的参数

    //anInvocation.target  方法调用者

    //anInvocation.selector  方法方法名

    //[anInvocation getArgument: NULL atIndex: 0]  方法的参数

    //方法参数顺序:receiver,selector,other arguments

    - (void)forwardInvocation:(NSInvocation *)anInvocation

    {

    int time;

    // 获取方法的参数,方法默认还有self和cmd两个参数,因此新添加的参数下标为2

    [anInvocation getArgument: &time atIndex: 2];

    NSLog(@"修改前参数的值 = %d",time);

    time = time + 10; // time = 110

    NSLog(@"修改前参数的值 = %d",time);

    // 设置方法的参数 此时将参数设置为110

    [anInvocation setArgument: &time atIndex:2];

    // 将tagert设置为Car实例对象

    [anInvocation invokeWithTarget: [[Car alloc] init]];

    // 获取方法的返回值

    int result;

    [anInvocation getReturnValue: &result];

    NSLog(@"获取方法的返回值 = %d",result); // result = 220,说明参数修改成功

    result = 99;

    // 设置方法的返回值 重新将返回值设置为99

    [anInvocation setReturnValue: &result];

    // 获取方法的返回值

    [anInvocation getReturnValue: &result];

    NSLog(@"修改方法的返回值为 = %d",result);    // result = 99

    }

    #import

    #import "Person.h"

    int main(int argc, const char * argv[]) {

    @autoreleasepool {

    Person *person = [[Person alloc] init];

    // 传入100,并打印返回值

    NSLog(@"[person driving: 100] = %d",[person driving: 100]);

    }

    return 0;

    }

    #######只要来到forwardInvocation方法中,我们便对方法调用有了绝对的掌控权,可以选择是否调用方法,以及修改方法的参数返回值等等。

    类方法的消息转发

    类方法消息转发同对象方法一样,同样需要经过消息发送,动态方法解析之后才会进行消息转发机制。我们知道类方法是存储在元类对象中的,元类对象本来也是一种特殊的类对象。需要注意的是,类方法的消息接受者变为类对象。

    当类对象进行消息转发时,对调用相应的+号的forwardingTargetForSelector、methodSignatureForSelector、forwardInvocation方法,需要注意的是+号方法仅仅没有提示,而不是系统不会对类方法进行消息转发。

    类方法的消息转发机制。

    int main(int argc, const char * argv[]) {

    @autoreleasepool {

    [Person driving];

    }

    return 0;

    }

    #import "Car.h"

    @implementation Car

    + (void) driving;

    {

    NSLog(@"car driving");

    }

    @end

    #import "Person.h"

    #import

    #import "Car.h"

    @implementation Person

    + (id)forwardingTargetForSelector:(SEL)aSelector

    {

    // 返回能够处理消息的对象

    if (aSelector == @selector(driving)) {

    // 这里需要返回类对象

    return [Car class];

    }

    return [superforwardingTargetForSelector:aSelector];

    }

    // 如果forwardInvocation函数中返回nil 则执行下列代码

    // 方法签名:返回值类型、参数类型

    + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

    {

    if (aSelector == @selector(driving)) {

    return [NSMethodSignature signatureWithObjCTypes: "v@:"];

    }

    return [supermethodSignatureForSelector:aSelector];

    }

    + (void)forwardInvocation:(NSInvocation *)anInvocation

    {

    [anInvocation invokeWithTarget: [Car class]];

    }

    OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)。方法调用过程中也就是objc_msgSend底层实现分为三个阶段:消息发送、动态方法解析、消息转发。本文主要对这三个阶段相互之间的关系以及流程进行的探索。

    第一章 super本质

    #######MJStudent->MJPerson->NSObject

            - (instancetype)init

            {

                if (self = [super init]) {

                    NSLog(@"[self class] = %@", [self class]); // MJStudent

                    NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson

                    NSLog(@"--------------------------------");

                    // objc_msgSendSuper({self, [MJPerson class]}, @selector(class));

                    //super的消息接受者让然是子类对象,只不过是从父类开始查找

                    NSLog(@"[super class] = %@", [super class]); // MJStudent

                    NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson

                }

                return self;

            }

      #######解析方法   

                 [super run];

                 //转化为底层源码内部其实调用的是objc_msgSendSuper函数

    ((void (*)(__rw_objc_super *, SEL))(void

    *)objc_msgSendSuper)((__rw_objc_super){(id)self,

    (id)class_getSuperclass(objc_getClass("MJStudent"))},

    sel_registerName("run"));

                简化后

                //objc_msgSendSuper函数内传递了两个参数

                //__rw_objc_super结构体和sel_registerName("run")方法名

                objc_msgSendSuper((__rw_objc_super){

                (id)self,

                (id)class_getSuperclass(objc_getClass("MJStudent"))}, sel_registerName("run"))

                进一步简化

                struct __rw_objc_super arg = {

                self,

                class_getSuperclass(objc_getClass("MJStudent"))}

                objc_msgSendSuper(arg, @selector(run));

                //objc_msgSendSuper中传入的结构体是objc_super

                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结构体查看其内部结构

                // 精简后的objc_super结构体

                struct objc_super {

                __unsafe_unretained _Nonnull id receiver;// 消息接受者

                __unsafe_unretained _Nonnull Class super_class;// 消息接受者的父类

                /* super_class is the first class to search */

                // 父类是第一个开始查找的类

                };

                //super_class的消息接受者让然是子类对象,只不过是从父类开始查找

                //super调用方法的消息接受者receiver仍然是self,只是从父类的类对象开始去查找方法

    ########class内部实现是根据消息接受者返回其对应的类对象,最终会找到基类的方法列表中,而self和super的区别仅仅是self从本类类对象开始查找方法,super从父类类对象开始查找方法,因此最终得到的结果都是相同的

    ######## class及superClass代码实现

                + (Class)class {

                return self;

                }

                - (Class)class {

                return object_getClass(self);

                }

                + (Class)superclass {

                return self->superclass;

                }

                - (Class)superclass {

                return [self class]->superclass;

                }

    ########其实super底层真正调用的函数时objc_msgSendSuper2函数我们可以通过查看super调用方法转化为汇编代码来验证这一说法

    ########super底层其实调用的是objc_msgSendSuper2函数,我们来到源码中查找一下objc_msgSendSuper2函数的底层实现,我们可以在汇编文件中找到其相关底层实现。

        ENTRY _objc_msgSendSuper2

        UNWIND _objc_msgSendSuper2, NoFrame

        MESSENGER_START

        ldp x0, x16, [x0] // x0 = real receiver, x16 = class

        ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass

        CacheLookup NORMAL

        END_ENTRY _objc_msgSendSuper2

    ########通过上面汇编代码我们可以发现,其实底层是在函数内部调用的class->superclass获取父类,并不是我们上面分析的直接传入的就是父类对象

    ########其实_objc_msgSendSuper2内传入的结构体为objc_super2

        struct objc_super2 {

            id receiver;

            Class current_class;

        };

    ###总结

                [super message]的底层实现

                1.消息接收者仍然是子类对象

                2.从父类开始查找方法的实现

    第二章. isKindOfClass 与 isMemberOfClass

        - (BOOL)isMemberOfClass:(Class)cls {

            // 直接获取实例类对象并判断是否等于传入的类对象

            return [self class] == cls;

        }

        - (BOOL)isKindOfClass:(Class)cls {

            // 向上查询,如果找到父类对象等于传入的类对象则返回YES

            // 直到基类还不相等则返回NO

            for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {

                if (tcls == cls) return YES;

            }

            return NO;

        }

        // 判断元类对象是否等于传入的元类元类对象

        // 此时self是类对象 object_getClass((id)self)就是元类

        + (BOOL)isMemberOfClass:(Class)cls {

            return object_getClass((id)self) == cls;

        }

        // 向上查找,判断元类对象是否等于传入的元类对象

        // 如果找到基类还不相等则返回NO

        // 注意:这里会找到基类

        + (BOOL)isKindOfClass:(Class)cls {

            for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {

            if (tcls == cls) return YES;

            }

            return NO;

        }

    第三章.奇怪问题

            // Person.h

            #import <Foundation/Foundation.h>

            @interface Person : NSObject

            @property (nonatomic, strong) NSString *name;

            - (void)test;

            @end

            // Person.m

            #import "Person.h"

            @implementation Person

            - (void)test

            {

            //栈中内存地址分配从高位到低位,则cls在高位,obj在最低位,

            //self.name== self->name

            //self则为isa指针地址,self->name则isa指针位置下移8位,地址后向高地址位取8个字节地址空间存储的值

                NSLog(@"test print name is : %@", self.name);

            }

            @end

            // ViewController.m

            @implementation ViewController

            - (void)viewDidLoad {

                //栈中内存地址分配从高位到低位,则UIVewController在高位,self在最低位

                [super viewDidLoad];//这句调用完后

                //cls在低位,self在高位

                //cls中存储person类对象地址值

                id cls = [Person class];

                //obj中存储cls的地址

                void *obj = &cls;

                //p/x obj (MJPerson *) $0 = 0x00007ffee1eee888 地址

                // x 0x00007ffee1eee888  //地址内容

                //前面8字节为cls(c8 0f d1 0d 01 00 00 00)

                0x7ffee1eee888: c8 0f d1 0d 01 00 00 00 c0 be 70 1f ef 7f 00 00  ..........p.....

                0x7ffee1eee898: 00 0f d1 0d 01 00 00 00 87 d6 32 12 01 00 00 00  ..........2.....

                //x/4g 0x00007ffee1eee888 //打印四个数据,每个数据八个字节 4:四个数据 g:八个字节

                //打印出了 cls八个字节  self八个字节  viewController八个字节

                0x7ffee1eee888: 0x000000010dd10fc8 0x00007fef1f70bec0

                0x7ffee1eee898: 0x000000010dd10f00 0x000000011232d687

                // p (Class)0x000000010dd10fc8

                (Class) $1 = MJPerson

                //po 0x00007fef1f70bec0

                <ViewController: 0x7fef1f70bec0>

                //p (Class)0x000000010dd10f00

                (Class) $4 = ViewController

                [(__bridge id)obj test];//test print name is : <ViewController: 0x7f95514077a0>

                //person中存储实例对象地址,实例对象的第一块地址是isa指针,因此person中存储的地址其实和isa指针地址相同

                //isa指针中存储Person类对象地址

                Person *person = [[Person alloc] init];

                [person test];//test print name is : (null)

            }

    ####1.obj为什么可以正常调用方法

    person调用方法时首先通过isa指针找到类对象进而查找方法并进行调用。而person实例对象内实际上是取最前面8个字节空间也就是isa并通过计算得出类对象地址。obj在调用test方法时,也会通过其内存地址找到cls,而cls中取出最前面8个字节空间其内部存储的刚好是Person类对象地址。因此obj是可以正常调用方法的

    ####2.为什么self.name打印内容为ViewController对象

            1. super的底层本质为调用objc_msgSendSuper2函数,传入objc_super2结构体,结构体内部存储消息接受者和当前类,用来告知系统方法查找从父类开始。

            2. 局部变量分配在栈空间,并且从高地址向低地址连续分配。先创建的局部变量分配在高地址,后续创建的局部变量连续分配在较低地址。

            3. 方法调用的消息机制,通过isa指针找到类对象进行消息发送。

            4. 指针存储的是实例变量的首字节地址,上述例子中person指针存储的其实就是实例变量内部的isa指针的地址。

            5. 访问成员变量的本质,找到成员变量的地址,按照成员变量所占的字节数,取出地址中存储的成员变量的值。

    什么是Runtime?平时项目中有用过么?

        OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行

        OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数

        平时编写的OC代码,底层都是转换成了Runtime API进行调用

        具体应用

        利用关联对象(AssociatedObject)给分类添加属性

        遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)

        交换方法实现(交换系统的方法)

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

    第四章.r运行时相关API  

    #######类相关API

            1. 动态创建一个类(参数:父类,类名,额外的内存空间)

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

            2. 注册一个类(要在类注册之前添加成员变量)

            void objc_registerClassPair(Class cls)

            3. 销毁一个类

            void objc_disposeClassPair(Class cls)

            示例:

            void run(id self , SEL _cmd) {

                NSLog(@"%@ - %@", self,NSStringFromSelector(_cmd));

            }

            int main(int argc, const char * argv[]) {

                @autoreleasepool {

                    // 创建类 superclass:继承自哪个类 name:类名 size_t:格外的大小,创建类是否需要扩充空间

                    // 返回一个类对象

                    Class newClass = objc_allocateClassPair([NSObject class], "Student", 0);

                    // 添加成员变量

                    // cls:添加成员变量的类 name:成员变量的名字 size:占据多少字节 alignment:内存对齐,最好写1 types:类型,int类型就是@encode(int) 也就是i

                    class_addIvar(newClass, "_age", 4, 1, @encode(int));

                    class_addIvar(newClass, "_height", 4, 1, @encode(float));

                    // 添加方法

                    class_addMethod(newClass, @selector(run), (IMP)run, "v@:");

                    // 注册类

                    objc_registerClassPair(newClass);

                    // 创建实例对象

                    id student = [[newClass alloc] init];

                    // 通过KVC访问

                    [student setValue:@10 forKey:@"_age"];

                    [student setValue:@180.5 forKey:@"_height"];

                    // 获取成员变量

                    NSLog(@"_age = %@ , _height = %@",[student valueForKey:@"_age"], [student valueForKey:@"_height"]);

                    // 获取类的占用空间

                    NSLog(@"类对象占用空间%zd", class_getInstanceSize(newClass));

                    // 调用动态添加的方法

                    [student run];

                }

                return 0;

            }

            // 打印内容

            // Runtime应用[25605:4723961] _age = 10 , _height = 180.5

            // Runtime应用[25605:4723961] 类对象占用空间16

            // Runtime应用[25605:4723961] <Student: 0x10072e420> - run

            注意

            类一旦注册完毕,就相当于类对象和元类对象里面的结构就已经创建好了。

            因此必须在注册类之前,添加成员变量。方法可以在注册之后再添加,因为方法是可以动态添加的。

            创建的类如果不需要使用了 ,需要释放类。

    4. 获取isa指向的Class,如果将类对象传入获取的就是元类对象,如果是实例对象则为类对象

    Class object_getClass(id obj)

    int main(int argc, const char * argv[]) {

    @autoreleasepool {

    Person *person = [[Person alloc] init];

    NSLog(@"%p,%p,%p",object_getClass(person), [Person class],

    object_getClass([Person class]));

    }

    return 0;

    }

    // 打印内容

    Runtime应用[21115:3807804] 0x100001298,0x100001298,0x100001270

    5. 设置isa指向的Class,可以动态的修改类型。例如修改了person对象的类型,也就是说修改了person对象的isa指针的指向,中途让对象去调用其他类的同名方法。

    Class object_setClass(id obj, Class cls)

    int main(int argc, const char * argv[]) {

    @autoreleasepool {

    Person *person = [[Person alloc] init];

    [person run];

    object_setClass(person, [Car class]);

    [person run];

    }

    return 0;

    }

    // 打印内容

    Runtime应用[21147:3815155] -[Person run]

    Runtime应用[21147:3815155] -[Car run]

    最终其实调用了car的run方法

    6. 用于判断一个OC对象是否为Class

    BOOL object_isClass(id obj)

    // 判断OC对象是实例对象还是类对象

    NSLog(@"%d",object_isClass(person)); // 0

    NSLog(@"%d",object_isClass([person class])); // 1

    NSLog(@"%d",object_isClass(object_getClass([person class]))); // 1

    // 元类对象也是特殊的类对象

    7. 判断一个Class是否为元类

    BOOL class_isMetaClass(Class cls)

    8. 获取类对象父类

    Class class_getSuperclass(Class cls)

    #######成员变量相关API

    1. 获取一个实例变量信息,描述信息变量的名字,占用多少字节等

    Ivar class_getInstanceVariable(Class cls, const char *name)

    2. 拷贝实例变量列表(最后需要调用free释放)

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

    3. 设置和获取成员变量的值

    void object_setIvar(id obj, Ivar ivar, id value)

    id object_getIvar(id obj, Ivar ivar)

    4. 动态添加成员变量(已经注册的类是不能动态添加成员变量的)

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

    5. 获取成员变量的相关信息,传入成员变量信息,返回C语言字符串

    const char *ivar_getName(Ivar v)

    6. 获取成员变量的编码,types

    const char *ivar_getTypeEncoding(Ivar v)

    示例:

    int main(int argc, const char * argv[]) {

    @autoreleasepool {

    // 获取成员变量的信息

    Ivar nameIvar = class_getInstanceVariable([Person class], "_name");

    // 获取成员变量的名字和编码

    NSLog(@"%s, %s", ivar_getName(nameIvar), ivar_getTypeEncoding(nameIvar));

    Person *person = [[Person alloc] init];

    // 设置和获取成员变量的值

    object_setIvar(person, nameIvar, @"xx_cc");

    // 获取成员变量的值

    object_getIvar(person, nameIvar);

    NSLog(@"%@", object_getIvar(person, nameIvar));

    NSLog(@"%@", person.name);

    // 拷贝实例变量列表

    unsigned int count ;

    Ivar *ivars = class_copyIvarList([Person class], &count);

    for (int i = 0; i < count; i ++) {

    // 取出成员变量

    Ivar ivar = ivars[i];

    NSLog(@"%s, %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));

    }

    free(ivars);

    }

    return 0;

    }

    // 打印内容

    // Runtime应用[25783:4778679] _name, @"NSString"

    // Runtime应用[25783:4778679] xx_cc

    // Runtime应用[25783:4778679] xx_cc

    // Runtime应用[25783:4778679] _name, @"NSString"

    #######属性相关AIP

    1. 获取一个属性

    objc_property_t class_getProperty(Class cls, const char *name)

    2. 拷贝属性列表(最后需要调用free释放)

    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

    3. 动态添加属性

    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,

    unsigned int attributeCount)

    4. 动态替换属性

    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,

    unsigned int attributeCount)

    5. 获取属性的一些信息

    const char *property_getName(objc_property_t property)

    const char *property_getAttributes(objc_property_t property)

    #######方法相关API

    1. 获得一个实例方法、类方法

    Method class_getInstanceMethod(Class cls, SEL name)

    Method class_getClassMethod(Class cls, SEL name)

    2. 方法实现相关操作

    IMP class_getMethodImplementation(Class cls, SEL name)

    IMP method_setImplementation(Method m, IMP imp)

    void method_exchangeImplementations(Method m1, Method m2)

    3. 拷贝方法列表(最后需要调用free释放)

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

    4. 动态添加方法

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

    5. 动态替换方法

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

    6. 获取方法的相关信息(带有copy的需要调用free去释放)

    SEL method_getName(Method m)

    IMP method_getImplementation(Method m)

    const char *method_getTypeEncoding(Method m)

    unsigned int method_getNumberOfArguments(Method m)

    char *method_copyReturnType(Method m)

    char *method_copyArgumentType(Method m, unsigned int index)

    7. 选择器相关

    const char *sel_getName(SEL sel)

    SEL sel_registerName(const char *str)

    8. 用block作为方法实现

    IMP imp_implementationWithBlock(id block)

    id imp_getBlock(IMP anImp)

    BOOL imp_removeBlock(IMP anImp)

    相关文章

      网友评论

          本文标题:runtime浅析

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