runtime浅析

参阅文章‘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)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容