Runtime

Runtime

文章目录

Runtime 概念

  • OC是基于C的(底层是C和汇编编写的),它为 C 添加了面向对象的特征。将很多静态语言在编译和链接时期做的事放到了 Runtime 运行时来处理(消息机制),可以说 Runtime 是 OC 的幕后工作者。
  • C 语言编译的时候就就决定调用哪个函数。OC 运行的时才根据函数的名称找到对应的函数来调用。编译阶段,OC可以调用任何函数,即使并未实现,只要声明就不会报错,只有当运行的时候才会报错,这是因为OC是运行时动态调用的。而C语言调用未实现的函数就会报错。
  • Objective-C runtime目前有两个版本:
    • Modern runtime (Modern Runtime 覆盖了64位的Mac OS X Apps,还有 iOS Apps)
    • Legacy runtime (Legacy Runtime 是早期用来给32位 Mac OS X Apps 用的)

底层数据结构

// An opaque type that represents an Objective-C class.
// Objective-C 类是由 Class 类型来表示的,它实际上是一个指向 objc_class 结构体的指针
typedef struct objc_class *Class;

// Represents an instance of a class.
//objc_object 是表示一个类的实例的结构体
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

// A pointer to an instance of a class.
typedef struct objc_object *id;

Class 的结构

//部分代码
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;          // 当前类的父类
    cache_t cache;             // 方法缓存
    // 用于获取具体类的信息(属性、方法、协议等)
    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);
    }
};
// 部分代码
struct objc_object {
private:
    isa_t isa; // 可以看到 isa 指针实际上是一个 isa_t 的共用体
};
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
//定义一个结构体,结构体内定义的变量后面的值代表该变量占用多少二进制位(位域技术)
//在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体
    struct {
        uintptr_t nonpointer        : 1;                                         \
        uintptr_t has_assoc         : 1;                                         \
        uintptr_t has_cxx_dtor      : 1;                                         \
        uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
        uintptr_t magic             : 6;                                         \
        uintptr_t weakly_referenced : 1;                                         \
        uintptr_t deallocating      : 1;                                         \
        uintptr_t has_sidetable_rc  : 1;                                         \
        uintptr_t extra_rc          : 8;
    };
#endif
};

共用体概念

[外链图片转存失败(img-RxyT7TE8-1568884592268)(media/15398503045242/15476213762813.jpg)]
[外链图片转存失败(img-PATV9JAG-1568884592271)(media/15398503045242/15597226584149.jpg)]

class_data_bits
  • class_data_bits_t 主要是对 class_rw_t 的封装
  • class_rw_t 代表了类相关的读写信息、对 class_ro_t 的封装、方法、属性、协议列表等
  • class_ro_t 代表类相关的只读信息
//部分源码
struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;  // 仅有一个属性 通过 FAST_xxx 获取对应的信息
    .......
    
public:
    //用于返回  class_rw_t 
    //#define FAST_DATA_MASK          0x00007ffffffffff8UL
    class_rw_t* data() { //  FAST_DATA_MASK & bits 获取 class_rw_t 
        return (class_rw_t *)(bits & FAST_DATA_MASK);  
    }
    void setData(class_rw_t *newData)
    {
        assert(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
......
}
class_rw_t
  • class_rw_t 里面的 methods、properties、protocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容
  • 在运行时为该类添加的分类信息(属性、方法、协议等)合并在类对应的二维数组中为类提供了很好的扩展性,也验证了 OC 语言的动态性
  • 最终在程序运行的时候会把 分类的相关信息 和类 class_ro_t 内部的相关信息都合并在 class_rw_t 中(class_rw_t 一开始是没有的,是在合并后才创建赋值的)
    [外链图片转存失败(img-ryXoyx6J-1568884592275)(media/15398503045242/15476228345583.jpg)]
//部分源码
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;
    ......    
}
class_ro_t
  • class_ro_t 里面的baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,且是只读的,包含了类的初始内容(不包含分类的信息)。
  • 在编译期完成类的原始信息存储,并用const 修饰常量,不可再进行写入修改(只读
    [外链图片转存失败(img-VRRB459Q-1568884592276)(media/15398503045242/15597234285156.jpg)]
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;
    }
};
method_t
  • method_t 是对方法、函数的封装
    [外链图片转存失败(img-H44glJ9P-1568884592278)(media/15398503045242/15476231201747.jpg)]
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;
  • types 包含了函数返回值、参数编码的字符串
    [外链图片转存失败(img-FYLneNMF-1568884592280)(media/15398503045242/15573051892976.jpg)]

Type Encoding

官方文档

  • iOS 提供了一个叫做 @encode() 的指令,可以将具体的类型表示成字符串编码
    [外链图片转存失败(img-m9RSYNDz-1568884592282)(media/15398503045242/15686018234288.jpg)]
cache_t
  • 每次方法调用都从方法列表内部去查很消耗性能(类存在继承等情况,方法的查找链会很长),所以才有了cache_t 来解决性能问题
  • Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度

[外链图片转存失败(img-XRs0kUdm-1568884592283)(media/15398503045242/15686022301569.jpg)]
[外链图片转存失败(img-SwP815T4-1568884592286)(media/15398503045242/15476219455441.jpg)]

// mask一开始给一个初始值,如果长度不够用就再次申请,然后清空之前的缓存,缓存最新的(因为mask发生了变化)
struct cache_t {
    struct bucket_t *_buckets;  //散列表  (是一个数组 内部有很多的 bucket_t )
    mask_t _mask;               //散列表的长度-1 (散列表的长度减去1 & key = 索引)
    mask_t _occupied;           //已经缓存的方法数量
    ......    
}
struct bucket_t {
private:
#if __arm64__
    MethodCacheIMP _imp;  // 函数的内存地址 @selector
    cache_key_t _key;  // SEL 作为 key   函数地址
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif

public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }

    void set(cache_key_t newKey, IMP newImp);
};

#####cache_t的存入

  • 先看缓存中是否已经存在了该方法,如果已经存在,直接return掉,不用再缓存,
  • 从class中拿到cache列表,将sel转换为内存地址,如果当前cache还没被初始化,则分配一个大小为4的数组,并设置_mask为3。
  • 如果存入缓存后的大小小于当前大小的3/4,则当前缓存大小还可以使用,无需扩容
  • 否则缓存太满,需要扩容,扩容为原来大小的2倍。放弃旧的缓存,新扩容的缓存为空。

将_key与_mask相与,因为_mask是数组大小-1,所以得到的结果刚好小于数组的大小。
如果得到的位置已经被占用,则往后寻找,直到找到空的位置,把缓存设置到这个位置

void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
    // 因为cache_t内部用来储存的结构其实就是个数组
    // 所以操作的时候需要先加个锁,保证线程安全。
    mutex_locker_t lock(cacheUpdateLock);
    cache_fill_nolock(cls, sel, imp, receiver);
}

static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();

    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;

    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    // 如果缓存中已经缓存过了,不用再缓存,直接return
    if (cache_getImp(cls, sel)) return;

    cache_t *cache = getCache(cls);
    cache_key_t key = getKey(sel);

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1;
    mask_t capacity = cache->capacity();
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand();
    }

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the 
    // minimum size is 4 and we resized at 3/4 full.
    bucket_t *bucket = cache->find(key, receiver);
    if (bucket->key() == 0) cache->incrementOccupied();
    bucket->set(key, imp);
}
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);

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        cache_collect_free(oldBuckets, oldCapacity);
        cache_collect(false);
    }
}
//缓存中查找方法的流程 源码在 objc-cache.mm 中 
// k : SEL 
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);  // k : SEL   m:mask
    mask_t i = begin;
    do {  // 如果找到 就直接返回
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } 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);
}
// Class points to cache. SEL is key. Cache buckets store SEL+IMP.
// Caches are never built in the dyld shared cache.

static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
    return (mask_t)(key & mask);
}

对于 hash 表的碰撞问题(@selector(test) & _mask 和 @selector(test1) & _mask 值相同),苹果的解决方案是发生碰撞的时候,就索引减1 ,查找下一个,直到减到 0 就返回mask 。

static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;  // 如果减到 0 就直接返回 mask
}

关于hash 表的原理就是 : f(key) = index ,结构图如下:
[外链图片转存失败(img-xaNbY3yZ-1568884592287)(media/15398503045242/15686304099605.jpg)]

  • cache 查找过程:(以对象方法为例)
    • 通过isa查找到指定 class
    • 从 cache 中查找,若存在缓存,则直接调用
    • 若缓存中不存在方法,则在自己的 class 里 bits 的 rw 中查找方法
    • 若找到该方法则调用,并将方法缓存至cache中
    • 若没有找到,则通过 superclass 找到父类,继续从父类class里 bits 的 rw 中查找方法
    • 若在父类中找到,则直接调用,并将方法缓存至自己 class 中;若找不到,则一直向上查找
isa
  • 在 arm64 架构之前,isa 就是一个普通的指针,存储着Class 、Meta-Class 对象的内存地址。
  • 在 arm64 架构开始,对 isa 进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。
    [外链图片转存失败(img-h5zyytRp-1568884592290)(media/15398503045242/15573015446730.jpg)]
isa 位域
  //优化后的 isa 
   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          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
       uintptr_t magic             : 6;
       uintptr_t weakly_referenced : 1;
       uintptr_t deallocating      : 1;
       uintptr_t has_sidetable_rc  : 1;
       uintptr_t extra_rc          : 8;
       };
};
  • nonpointer
    • 0, 代表普通的指针,存储着 Class 和 Meta—Class 对象的内存地址
    • 1, 代表优化过(不是普通指针),使用位域存储更多的信息
  • has_assoc
    • 是否有设置关联对象,如果有,释放时会更快
  • has_cxx_dtor
    • 是否有C++的析构函数(.cxx_destruct),如果有,释放时会更快,类似delloc
  • shiftcls
    • 存储着Class、Meta-Class对象的内存地址信息
  • magic
    • 用于在调试时分辨对象是否未完成初始化
  • weakly_referenced
    • 是否有被弱引用指向过,如果没有,释放时会更快
  • deallocating
    • 对象是否正在释放
  • has_sidetable_rc
    • 里面存储的值是引用计数减 1
  • extra_rc (retain count 引用计数)
    • 引用计数器是否过大无法存储在isa中
    • 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

Class、Mate-Class 对象的地址的后三位始终是0

[外链图片转存失败(img-h61jFe6K-1568884592291)(media/15398503045242/15476214977618.jpg)]
isa 指针 有两种 一种是 指针型的isa 另一种是 非指针型的isa。

  • isa 指向
    • 关于对象,其指向类对象
      [外链图片转存失败(img-jqXld4K4-1568884592295)(media/15398503045242/15476216272498.jpg)]

    • 关于类对象,其指向元类对象
      [外链图片转存失败(img-oEhK39wl-1568884592295)(media/15398503045242/15476216450733.jpg)]

整体数据结构关系

[外链图片转存失败(img-XK7SgxGs-1568884592297)(media/15398503045242/15476235317120.jpg)]

类与对象

对象、类对象、元类对象

  • 类对象存储实例方法列表等信息

  • 元类对象存储类方法列表等信息
    [外链图片转存失败(img-LgwAuWXM-1568884592299)(media/15398503045242/15476239352566.png)]

  • 实例的isa 指向类对象,类对象的isa 指向元类对象。

类与对象基础数据结构

Class

Objective-C 类是由Class 类型来表示的,它实际上是一个指向objc_class结构体的指针;

typedef struct objc_class *Class;
objc_class
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    const char *name                        OBJC2_UNAVAILABLE;  // 类名
    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
    long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
    long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
    #endif
    
} OBJC2_UNAVAILABLE;
isa

需要注意的是在Objective-C 中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)。

super_class

指向该类的父类,如果父类已经是最顶层的根类(NSObject或者NSProxy),则super_classNULL

version

可以使用这个字段提供类的版本信息。对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。

cache

用于缓存最近使用的方法。一个接收者接收到一个消息的时候,会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,都是methodLists中遍历一遍,性能会很差。使用cache在每次调用一个方法后,这个方法会被缓存到cache 列表中,下次调用的时候runtime就会优先去cache中找,如果cache没有,才去methodLists中查找方法,这样对于经常用到的方法的调用,提高了调用的效率。

objc_object & id
objc_object

是表示一个类的实例的结构体,定义如下:

 struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
  • 可以看到,这个结构体只有一个isa指针,即指向其类的isa指针。当我们向一个Objective-C 对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime 库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后运行这个方法。
  • 当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的allocallocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。
id

它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。

  • Meta Calss(元类)
    • 所有的类自身也是一个对象,可以向这个对象发送消息(即调用类方法),比如NSArray *array = [NSArray array]; +array 消息发送给了NSArray 类,这个NSArray也是一个对象。既然是对象,那么它也是一个objc_object 指针,它包含一个指向其类的isa指针。那么这个isa指针指向什么呢?为了调用 +array 方法,这个类的isa 指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class(类对象的类)的概念。
    • 当我们向一个对象发送消息的时候Runtime 会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息的时候,会在这个类的meta-class的方法列表中查找。
    • meta-class 之所以重要,因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
    • meta-class 也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C 设计者让所有的meta-classisa指向基类的meta-class,以此作为他们的所属类,即任何NSObject继承体系下的meta-class都使用NSObjectmeta-class作为自己的所属类,而基类的meta-classisa指针是指向它自己,这样就形成了一个完美的闭环。
    • 以上描述的体系结构可以从下图看出:
      [外链图片转存失败(img-6N2AGRTI-1568884592302)(media/15398503045242/meta-class.png)]

Runtime 类 与 对象 API

Runtime 提供了大量的函数来操作类与对象。

  • 类的操作方法大部分是以class为前缀
  • 对象的操作方法大部分是以objcobject_为前缀。

类 相关操作函数

objc_allocateClassPair

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

  • objc_allocateClassPair函数: 如果想要创建一个根类,则superclass指定为Nil。extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
    • 为了创建一个新类,需要调用objc_allocateClassPair。然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。
    • 实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
//在运行时动态生成一个类
Class newClass = objc_allocateClassPair([NSObject class], "RuntimeClass", 0);
    
// 为类添加成员变量
// 给指定的类添加成员变量 ,只能在objc_allocateClassPair() 和 objc_registerClassPair() 之间调用,并且不能为一个已经存在的类添加成员变量
class_addIvar(newClass, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
class_addIvar(newClass, "_age", sizeof(int), sizeof(int), @encode(int));
    
// 为类添加方法
SEL sel = sel_registerName("RuntimeTestMethod");
class_addMethod(newClass, sel, (IMP)RuntimeTestMethodIMP, "i@:@");
    
// 注册类到Runtime (必须要注册)
objc_registerClassPair(newClass);
    
// 使用已经创建的类
id person = [[newClass alloc] init];
    
// 可以这样设置值
//        [person setValue:@"小花" forKey:@"_name"];
//        NSLog(@" name :%@",[person valueForKey:@"_name"]);
    
// 设置成员变量的值
Ivar nameIvar = class_getInstanceVariable(newClass, "_name");
object_setIvar(person, nameIvar, @"xiaohua");
    
Ivar ageIvar = class_getInstanceVariable(newClass, "_age");
object_setIvar(person, ageIvar, @18);
    
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"xiaoli",@"name",@"25",@"age", nil];
[person performSelector:@selector(RuntimeTestMethod) withObject:dic];
    
// 获取成员变量的值
NSLog(@"实例变量Person的值%@  %@",object_getIvar(person, nameIvar),object_getIvar(person, ageIvar));
// 实例变量Person的值xiaohua  18
NSLog(@"实例所属的类: %@ ,实例所属的父类: %@",object_getClass(person),class_getSuperclass(object_getClass(person)));
//实例所属的类: RuntimeClass ,实例所属的父类: NSObject
    
// 在不需要这个类的时候释放
//objc_disposeClassPair(newClass);

//动态添加的方法必须是已经实现的
void RuntimeTestMethodIMP(id self,SEL _cmd,NSDictionary *dict) {
    NSLog(@"传递进来的 dict%@",dict);
    NSLog(@"打印成员变量name: %@",object_getIvar(self, class_getInstanceVariable([self class], "_name")));
}
//传递进来的 dict{ age = 25; name = xiaoli; }
objc_registerClassPair

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

void objc_registerClassPair(Class cls) 
objc_disposeClassPair

销毁一个类

void objc_disposeClassPair(Class cls)

objc_disposeClassPair函数用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或子类的实例,则不能针对类调用该方法。

object_getClass

获取isa指向的Class

Class object_getClass(id obj)
object_setClass

设置isa指向的Class

Class object_setClass(id obj, Class cls)
Person *person = [[Person alloc] init];
[person run]; // [Person run]

//object_setClass  设置isa指向的Class
object_setClass(person, [Son class]);   // 可以动态修改 isa 指向
[person run]; // [Son run]
object_isClass

判断一个OC对象是否为Class

BOOL object_isClass(id obj)
class_isMetaClass

判断一个Class是否为元类

BOOL class_isMetaClass(Class cls)
class_getSuperclass

获取类的父类

Class class_getSuperclass(Class cls)

当 cls 为 Nil 或者 cls 为根类时,返回 Nil。

成员变量 相关操作函数

class_getInstanceVariable

获取一个实例成员变量的信息

Ivar class_getInstanceVariable(Class cls, const char *name)

Ivar ivar = class_getInstanceVariable([Person class],"_height");
NSLog(@"%s == %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));  //  _height == i
class_getClassVariable

获取类成员变量的信息

Ivar class_getClassVariable ( Class cls, const char *name );

目前没有找到关于Objective-C中类变量的信息,**一般认为Objective-C不支持类变量。**注意,返回的列表不包含父类的成员变量和属性。

class_copyIvarList

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

Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
unsigned int count = 0;
//获取类中的所有成员变量
Ivar *ivarList = class_copyIvarList([Person class], &count);
//遍历所有成员变量
for (int i = 0; i < count; i ++) {
    //根据角标,从数组中取出对应的成员变量
    Ivar ivar = ivarList[i];
    //获取成员变量的名字
    NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
    NSLog(@"%@",ivarName);
}
free(ivarList);
object_setIvar object_getIvar

设置和获取成员变量的值

void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
// 设置成员变量的值 
Ivar nameIvar = class_getInstanceVariable(newClass, "_name");
object_setIvar(person, nameIvar, @"xiaohua");
    
Ivar ageIvar = class_getInstanceVariable(newClass, "_age");
object_setIvar(person, ageIvar, @18);

// 获取成员变量的值
object_getIvar(person, nameIvar)
object_getIvar(person, ageIvar)
class_addIvar

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

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

获取成员变量的相关信息

const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)

属性 相关操作函数

class_getProperty

获取一个属性

objc_property_t class_getProperty(Class cls, const char *name)
class_copyPropertyList

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

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

动态为类添加属性

BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)
Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
    
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
    
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);
    
id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];
    
输出结果:
run sub method 1
run sub method 1
class_replaceProperty

动态替换属性

void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)
property_getName property_getAttributes

获取属性的一些信息

const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

方法 相关操作函数

class_getInstanceMethod class_getClassMethod

获取 一个实例方法 、类方法,class_getInstanceMethodclass_getClassMethod函数,与 class_copyMethodList不同的是,这两个函数都会去搜索父类的实现

Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
class_getMethodImplementation method_setImplementation method_exchangeImplementations

方法实现相关操作

IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分.

class_copyMethodList

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

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

class_copyMethodList函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount 参数返回方法的个数。在获取到列表后,需要使用free()方法来释放。

class_addMethod

动态添加方法

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

class_addMethod的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation。一个OC方法是一个简单的C函数,至少包含两个参数self_cmd。所以,实现函数(IMP参数指向的函数)至少需要两个参数void myMethodIMP(id self, SEL _cmd){ // implementation ....}。与成员变量不同的是,可以为类动态添加方法,不管这个类是否已存在;另外,参数types是一个描述传递给方法的参数类型的字符数组。

class_replaceMethod

动态替换方法

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

class_replaceMethod函数,该函数的行为可以分为两种:如果类中不存在name 指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原来方法的实现。

imp_implementationWithBlock

用block作为方法实现

// 创建一个指针函数的指针,该函数调用时会调用特定的block 
IMP imp_implementationWithBlock(id block)
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock(IMP anImp)
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock(IMP anImp)
@interface MyRuntimeBlock : NSObject
@end
@implementation MyRuntimeBlock
@end
// 测试代码
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
    NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
// 输出结果 : hello world!
获取方法的相关信息

(带有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)

协议 相关操作函数

class_addProtocol

添加协议

BOOL class_addProtocol ( Class cls, Protocol *protocol );
class_conformsToProtocol

返回类是否实现指定的协议

BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
class_copyProtocolList
	返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

综合实例

#import <Foundation/Foundation.h>

@interface Person : NSObject<NSCopying,NSCoding>
	
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *address;
	
@property (nonatomic,strong) NSArray *array;
@property (nonatomic,copy) NSString *string;
	
- (void)method1;
- (void)method2;
	
+ (void)classMethod;
	
@end
	
#import "Person.h"

@interface Person (){
    NSInteger _instance1;
    NSString *_instance2;
}
	
@property (nonatomic,assign)  NSUInteger age;
	
@end
	
@implementation Person
	
- (void) method1 {
    NSLog(@" == method1 == ");
}
	
- (void)method2 {
    NSLog(@" == method2 == ");
}
	
+ (void)classMethod {
    NSLog(@" == classMethod == ");
}
	
@end
	
- (void)viewDidLoad {
[super viewDidLoad];

unsigned int count = 0;

Class cls = Person.class;

//类名
NSLog(@"class name : %s",class_getName(cls));
//父类
NSLog(@"super class name : %s",class_getName(class_getSuperclass(cls)));
//是否是元类
NSLog(@"Person is %@ a meta-class",class_isMetaClass(cls) ? @"" : @"not");
//获取元类
Class meta_class = objc_getMetaClass(class_getName(cls));
NSLog(@"%s meta-class is %s",class_getName(cls),class_getName(meta_class));
//实例变量大小
NSLog(@"instance size: %zu",class_getInstanceSize(cls));
    
//成员变量
Ivar *ivars = class_copyIvarList(cls, &count);
for (int i = 0; i < count; i ++) {
    Ivar ivar = ivars[i];
    NSLog(@"instance variable's name:%s at index %d",ivar_getName(ivar),i);
}
free(ivars);
    
Ivar string = class_getInstanceVariable(cls, "_string");
if (string != NULL) {
    NSLog(@"instance variable %s",ivar_getName(string));
}
    
//属性操作
objc_property_t *properties = class_copyPropertyList(cls, &count);
for (int i = 0; i < count; i++) {
    objc_property_t property = properties[i];
    NSLog(@"propertie's name : %s",property_getName(property));
}
free(properties);
    
objc_property_t array = class_getProperty(cls, "array");
if (array != NULL) {
    NSLog(@"property %s",property_getName(array));
}
    
//方法操作
Method *methods = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i ++) {
    Method method = methods[i];
    NSLog(@"method's signature: %s",method_getName(method));
}
    
Method method1 = class_getInstanceMethod(cls, @selector(method1));
if (method1 != NULL) {
    NSLog(@"method %s",method_getName(method1));
}
    
Method classMethod = class_getClassMethod(cls, @selector(classMethod));
if (classMethod != NULL) {
    NSLog(@"class method : %s",method_getName(classMethod));
}
    
IMP imp = class_getMethodImplementation(cls, @selector(method1));
imp();
    
//协议
Protocol *__unsafe_unretained *protocals = class_copyProtocolList(cls, &count);
Protocol *protocol;
for (int i = 0; i < count; i ++) {
    protocol = protocals[i];
    NSLog(@"protocol name : %s",protocol_getName(protocol));
}
NSLog(@"Person is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
}

[外链图片转存失败(img-ftKP9LWY-1568884592303)(media/15398503045242/runtime_shili.png)]

实例操作函数

实例操作函数主要针对我们创建的实例对象的一系列操作函数,可以使用这组函数从实例对象中获取到想要的信息,比如实例对象中变量的值。

  • 1、针对整个对象进行操作的函数,这类函数包含:
// 返回指定对象的一份拷贝
   id object_copy ( id obj, size_t size );
   // 释放指定对象占用的内存
   id object_dispose ( id obj );
  • 有这样一种场景,假如有类A和类B,且类B是类A的子类。类B通过添加一些额外的属性来扩展类A。 创建一个A类的实例对象,并希望在运行时将这个对象转换为B类的实例对象,这样可以添加数据到B类的属性中。这种情况下,没有办法直接转换,因为B类的实例会比A类的实例更大,没有足够的空间来放置对象。此时就可以使用以上几个函数来出来这种情况。
 NSObject *a = [[NSObject alloc] init];
  id newB = object_copy(a, class_getInstanceSize(MyClass.class));
  object_setClass(newB, MyClass.class);
  object_dispose(a);
  • 2、针对对象实例变量进行操作的函数,这类函数包含:

// 修改类实例的实例变量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );

// 获取对象实例变量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );

// 返回指向给定对象分配的任何额外字节的指针
void * object_getIndexedIvars ( id obj );

// 返回对象中实例变量的值
id object_getIvar ( id obj, Ivar ivar );

// 设置对象中实例变量的值
void object_setIvar ( id obj, Ivar ivar, id value );

```

实例变量的Ivar已知的情况下,调用`object_getIvar`会比`object_getInstanceVariable`函数快,相同情况下,`object_setIvar`也比`object_setInstanceVariable`快。
  • 3、针对对象的类进行操作的函数,这类函数包含:

    // 返回给定对象的类名
    const char * object_getClassName ( id obj );
    
    // 返回对象的类
    Class object_getClass ( id obj );
    
    // 设置对象的类
    Class object_setClass ( id obj, Class cls );
    
  • objc_getClassList函数: 获取已注册的类定义的列表。不能假设从该函数中获取的类对象是继承自NSObject 体系的,所以在这些类上调用方法时,都应该检测一下这个方法是否在这个类中实现。

int numClasses;
Class * classes = NULL;

numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
    classes = malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);
    
    NSLog(@"number of classes: %d", numClasses);
    
    for (int i = 0; i < numClasses; i++) {
        
        Class cls = classes[i];
        NSLog(@"class name: %s", class_getName(cls));
    }
    
    free(classes);
}
```
  • 获取类定义的方法由三个:objc_lookUpClass objc_getClassobjc_getRequiredClass: 如果类在运行时未注册,则objc_lookUpClass 会返回nil,而objc_getClass 会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。而objc_getRequiredClass 函数的操作与 objc_getClass 相同,只不过如果没有找到类,则会杀死进程。

  • objc_getMetaClass函数: 如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。

方法和消息

  • SEL: 又叫选择器,是表示一个方法的selector的指针。
    typedef struct objc_selector *SEL;

    • 方法的selector用于表示运行时方法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL

      SEL sel1 = @selector(method1);
      NSLog(@"sel : %p", sel1);
      输出:sel : 0x100002d72
      
    • 两个类之间,不管它们是不是父类与子类的关系,只要方法名相同,那么方法的SEL就是一样的。每个方法都对应一个SEL。在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致Objective-C在处理相同方法名且参数个数相同但类型不同的方法方面的能力很差。如在某个类中定义以下两个方法:

      - (void)setWidth:(int)width;
      - (void)setWidth:(double)width;
      
      
    • 当然,不同的类可以拥有相同的selector,这个没有问题。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP

    • 所有的SEL组成一个Set集合,Set的特点就是唯一,因此SEL是唯一的。因此,如果到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,可以说速度上无语伦比!!但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用的是perfect hash)。但是不管使用什么样的方法加速,如果能够将总量减少(多个方法可能对应同一个SEL),那将是最犀利的方法。那么,我们就不难理解,为什么 SEL仅仅是函数名了。

    • 本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。

    • 可以在运行时添加新的selector,也可以在运行时获取已存在的selector,我们可以通过下面三种方法来获取SEL:

      • sel_registerName函数

      • Objective-C编译器提供的@selector()

      • NSSelectorFromString()方法

  • IMP: 实际上是一个函数指针,指向方法实现的首地址:
    id (*IMP)(id, SEL, ...)

    • 这个函数使用当前CPU架构实现的标准的C调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),接下来是方法的实际参数列表。
    • 前面介绍过的SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的 IMP,查找过程将在下面讨论。取得IMP后,就获得了执行这个方法代码的入口点,此时,就可以像调用普通的C语言函数一样来使用这个函数指针 了。
    • 通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。
  • Method: 用于表示类定义中的方法:

    typedef struct objc_method *Method;
    
    struct objc_method {
        SEL method_name                 OBJC2_UNAVAILABLE;  // 方法名
        char *method_types                  OBJC2_UNAVAILABLE;
        IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法实现
    }
    
    • 该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。

Runtime 消息机制

OC 中的方法调用,其实就是转换为objc_msgSend函数的调用。
objc_msgSend的执行流程可以分为3个阶段

  • 消息发送
  • 动态方法解析
  • 消息转发

消息发送(传递)

[外链图片转存失败(img-ZCO8qYmc-1568884592306)(media/15398503045242/15663558819004.jpg)]

面试题:runtime 如何通过Selector 找到对应的IMP 地址的?(其实就是上面的寻找过程)

缓存查找

[外链图片转存失败(img-7YqvGb6Q-1568884592307)(media/15398503045242/15476252402496.jpg)]

在当前类中查找
  • 对于已排序好的列表,采用二分查找算法查找方法对应执行函数。
  • 对于没有排序的列表,采用一般遍历查找方法对应执行函数。
父类逐级查找

[外链图片转存失败(img-vxN8t7e6-1568884592310)(media/15398503045242/15476255489105.jpg)]

动态方法解析

[外链图片转存失败(img-eJifQAVr-1568884592311)(media/15398503045242/15663740925549.jpg)]

@implementation Person
- (void)otherTest {
    NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        
        //获取需要动态添加的方法
        Method method = class_getInstanceMethod(self, @selector(otherTest));
        
        //动态添加方法实现
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
       
        // 返回 YES 代表有动态添加的方法
        return YES;
    }
  return [super resolveInstanceMethod:sel];
}
@end
@implementation PersonC
// 任何方法都默认的两个参数 self , _cmd (当前方法的方法编号)
void otherRun(id self,SEL _cmd,NSNumber *meter){
    NSLog(@"跑了%@米",meter);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == NSSelectorFromString(@"run:")) {

        // class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
        // cls : 给哪个类动态添加方法
        // name : 添加方法的方法名
        // imp : 方法实现
        // types :  方法类型 (返回值 + 参数类型)
        // v: void
        // @ : 对象 -> self
        // :  表示SEL->_cmd
        class_addMethod(self, sel, (IMP)otherRun, "v@:@");

        return YES;
    }

    // 返回父类的默认调用
    return [super resolveInstanceMethod:sel];
}

//+ (BOOL)resolveClassMethod:(SEL)sel {
//    if (sel == NSSelectorFromString(@"test1")) {
//        
//        class_addMethod(object_getClass(self), sel, (IMP)otherRun, "v@:@");
//
//        return YES;
//    }
//    return [super resolveClassMethod:sel];
//}

@end

消息转发

[外链图片转存失败(img-1Vyto9L8-1568884592312)(media/15398503045242/15663951198817.jpg)]

[外链图片转存失败(img-pvB51Z0j-1568884592315)(media/15398503045242/15476259960974.jpg)]

OC 中方法调用的本质就是消息传递,消息有名称(name)或选择子(selector),可以接受参数,而且可能还有返回值。
消息机制的原理:对象根据方法编号SEL去映射表中查找对应的方法的实现。

通常会使用 clang -rewrite-objc来窥探OC的底层实现:
例如 main.m 文件代码如下:

```
 #import <UIKit/UIKit.h>
 #import "AppDelegate.h"
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
```

打开终端,到main.m目录下,执行如下指令命令,最终会生成main.cpp文件:

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

相关的代码转化为如下:

 int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}
xcrun -sdk iphonesimulator11.4  clang -rewrite-objc -F  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.4.sdk/System/Library/Frameworks

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

Runtime 常见应用

动态创建类和对象

动态创建对象:
// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
  • class_createInstance 函数:创建实例时,会在默认的内存区域为类分配内存。extraBytes 参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量。该函数在ARC 环境下无法使用。
  • 调用class_createInstance 的效果与+alloc方法类似。不过在使用class_createInstance 时,需要确切的知道用它来做什么。
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
    
NSLog(@"%@", [str1 class]);
    
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);
    
打印结果:
 NSString
 __NSCFConstantString
  • 可以看到,使用 class_createInstance 函数获取的是NSString实例,而不是类簇中的默认占位符类__NSCFConstantString
  • objc_constructInstance 函数:在指定的位置(bytes)创建类实例。
  • objc_destructInstance 函数:销毁一个类的实例,但不会释放并移除任何与其相关的引用。

Runtime 字典转模型

字典转模型的方式:

  1. 一个一个的给模型属性赋值

  2. 字典转模型KVC实现

    • KVC 字典转模型弊端:必须保证,模型中的属性和字典中的key一一对应。
    • 如果不一致就会[<Status 0x7fa74b545d60> setValue:forUndefinedKey:] 报找不到key的错。
    • 分析:模型中的属性和字典的key 不一一对应,系统就会调用 setValue:forUndefinedKey: 报错。
    • 解决:重写对象的setValue:forUndefinedKey: 把系统的方法覆盖掉,就能继续使用KVC字典转模型了。
  3. 字典转模型Runtime 实现

    • 思路:利用Runtime 遍历模型中的所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值(从字典中取值,不一定要全部取出来)。
    • 考虑情况:
      • 1、当字典的key 和模型的属性匹配不上。
        • 字典键值对数量大于模型属性数量,不处理
        • 模型属性数量大于字典键值对(不能赋值nil,需要判断处理)
      • 2、模型中嵌套模型(模型属性是另外一个模型对象)。
      • 3、数组中装着模型(模型的属性是一个数组,数组中是一个个模型对象)。
  • MJExtension 字典转模型实现 (写一个 NSObject 的分类,字典转模型用分类来处理)

    • 1、字典转模型Runtime方式实现 (把一个模型中所有属性遍历出来,然后赋值):
    //Runtime 根据模型中属性,去字典中取出对应的value给模型属性赋值
    //思路: 遍历模型中的所有属性(使用运行时)
    + (instancetype)modelWithDict:(NSDictionary *)dict {
        
        //1、创建对应的对象
        id objc = [[self alloc] init];
        
        //2、利用Runtime 给对象中的属性赋值
        //class_copyIvarList : 获取类中的所有成员变量
        //Ivar : 成员变量
        //第一个参数 : 表示获取哪个类中的成员变量
        //第二个参数 : 表示这个类有多少成员变量,传入一个Int 变量地址,会自动给这个变量赋值
        //返回 Ivar * : 指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到
        //count : 成员变量个数
        
        unsigned int count = 0;
        //获取类中的所有成员变量
        Ivar *ivarList = class_copyIvarList(self, &count);
        
        //遍历所有成员变量
        for (int i = 0; i < count; i ++) {
            //根据角标,从数组中取出对应的成员变量
            Ivar ivar = ivarList[i];
            //获取成员变量的名字
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            //处理成员变量名 - >字典中的key (去掉 _ ,从第一个角标开始截取)
            NSString *key = [ivarName substringFromIndex:1];
            //根据成员属性名去字典中查找对应的value
            id value = dict[key];
            
            //如果模型属性数量大于字典键值对数,模型属性会被赋值nil,报错 could not set nil as the value for the key age
            if (value) {
                [objc setValue:value forKey:key];
            }
        }
        free(ivarList); //释放
        return objc;
    }
    

    注:获取模型中的类的所有属性名,是采取 class_copyIvarList 先获取成员变量(以下划线开头) ,然后再处理成员变量名->字典中的key(去掉 _ ,从第一个角标开始截取) 得到属性名。
    Ivar : 成员变量,以下划线开头。
    Property 属性 获取类里面的属性 class_copyPropertyList ,获取类中的所有成员变量 class_copyIvarList

    {
    int _a; //成员变量
    }
    @property (nonatomic,assign) NSInteger attitudes_count; //属性
    这里有成员变量,就不会漏掉属性;如果有属性,可能会漏掉成员变量。

    >使用Runtime 字典转模型获取模型属性名的时候,最好获取成员属性名,`Ivar`因为可能会有个属性是没有setter 和 getter 方法的。  
    
    
  • 2、Runtime 字典转模型 -> 模型中嵌套模型 (模型属性是另外一个模型对象),可以做如下处理:

    // 字典转模型-->模型中嵌套模型「模型属性是另外一个模型对象」
    + (instancetype)modelWithDict2:(NSDictionary *)dict {
        //1、创建对应的对象
        id objc = [[self alloc] init];
        //2、利用runtime给对象中的属性赋值
        unsigned int count = 0;
        //获取类中的所有成员变量
        Ivar *ivarList = class_copyIvarList(self, &count);
        
        //遍历所有成员变量
        for (int i = 0; i < count; i ++) {
            //根据角标,从数组中取出对应的成员变量
            Ivar ivar = ivarList[i];
            //获取成员变量名字
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            //获取成员变量类型
            NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            
            //替换 @\“User\”  - > User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            
            //处理成员属性名 - > 字典中的key (去掉 _,从第一个角标开始截取)
            NSString *key = [ivarName substringFromIndex:1];
            
            //根据成员变量属性名去字典中查找对应的value
            id value = dict[key];
            
            // ====== 二级转换 ===== //
            //二级转换: 如果字典中还有字典,也需要把对应的字典转换模型
            //判断下value 是否是字典,并且是自定义对象才需要转换
            if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
                
                //字典转化成模型 userDict => User 模型,转换成哪个模型
                //根据字符串类名生成类对象
                Class modelClass = NSClassFromString(ivarType);
                if (modelClass) {  //有对应的模型才需要转
                    //把字典转模型
                    value = [modelClass modelWithDict2:value];
                }
            }
            //给模型中属性赋值
            if (value) {
                [objc setValue:value forKey:key];
            }
        }
        free(ivarList); //释放
        return objc;
    }
    
  • 3 **Runtime 字典转模型 --> 数组中装着模型 **(模型的属性是一个数组,数组中是字典模型对象),这种情况做如下处理:

     //Runtime : 根据模型中的属性,去字典中取出对应的value给模型属性赋值
    //思路: 遍历模型中所有属性 -> 使用运行时
    + (instancetype)modelWithDict3:(NSDictionary *)dict {
        // 1、创建对应的对象
        id objc = [[self alloc] init];
        
        //2、利用Runtime 给对象中的属性赋值
        unsigned int count = 0 ;
        //获取类中的所有成员变量
        Ivar *ivarList = class_copyIvarList(self, &count);
        
        // 遍历所有成员变量
        for (int i = 0; i < count; i ++) {
            // 根据角标,从数组取出对应的成员变量
            Ivar ivar = ivarList[i];
            
            //获取成员变量名
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            //处理成员属性名 -> 字典中的key (去掉 _,从第一个角标开始截取)
            NSString *key = [ivarName substringFromIndex:1];
            // 根据成员属性名去字典中查找对应的value
            id value = dict[key];
            
            //  ====== 三级转换 =======   //
            //三级转换:NSArray 中也是字典,把数组中的字典转换成模型
            //判断值是否是数组
            if ([value isKindOfClass:[NSArray class]]) {
                //判断对应类有没有实现字典数组转模型数组的协议
                // arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
                if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                    //转换成 id 类型,就能调用任何对象的方法
                    id idSelf = self;
                    
                    //获取数组中字典对应的模型
                    NSString *type = [idSelf arrayContainModelClass][key];
                    
                    //生成模型
                    Class classModel = NSClassFromString(type);
                    NSMutableArray *arrM = [NSMutableArray array];
                    //遍历字典数组,生成模型数组
                    for (NSDictionary *dict in value) {
                        //字典转模型
                        id model = [classModel modelWithDict3:dict];
                        [arrM addObject:model];
                    }
                    
                    // 把模型数组赋值给value
                    value = arrM;
                }
            }
            
            //如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
            if (value) {
                //给模型中属性赋值
                [objc setValue:value forKey:key];
            }
        }
        free(ivarList); // 释放
        return objc;
    }
    

总结:既然能获取到属性类型,就可以拦截到模型的那个数组属性,进而对数组中每个模型遍历并字典转模型,但是不知道数组中的模型都是什么类型,可以声明一个方法,改方法的目的不是让其调用,而是让其实现并返回模型的类型。

Runtime 动态交换两个方法的实现 运用汇总

  • 在OC 中调用一个方法,其实就是向一个对象发送消息,查找消息的唯一依据就是selector的名字。利用运行时可以偷换selector对应的方法实现,达到给方法挂钩的目的。
  • 每个类都有一个方法列表,存放着方法的名字和方法的实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP
  • 交换方法的几种实现方式
    * method_exchangeImplementations 交换两个方法的实现
    * class_replaceMethod 替换方法的实现
    * method_setImplementation 来设置某个方法的IMP

应用场景: 当第三方框架或者系统原生方法不能满足需求的时候,可以在保持系统方法的基础上,添加额外的功能。

需求: 加载一张图片直接用[UIImage imageNamed:@"icon"]; 是无法知道到底有没有加载成功。给系统的 imageNamed 添加额外的功能(是否加载图片成功)。

  • 方案一 : 继承系统的类,重写方法(弊端:每次使用都需要导入头文件)。
  • 方法二 : 使用Runtime 交换方法。

实现步骤
1、给系统的方法添加分类
2、自己实现一个带有扩展功能的方法
3、交换方法,只需交换一次

案例代码 方法+调用+打印输出

 - (void)viewDidLoad {
    [super viewDidLoad];

    //Runtime 交换方法
    UIImage *img = [UIImage imageNamed:@"icon"];    
    //方案一:先搞个分类,定义一个能加载图片并且能打印的方法 + (instancetype)imageWithName:(NSString *)name;
    //方案二:交换imageNamed 和 tw_imageNamed 的实现,就能调用 imageNamed 间接调用 tw_imageNamed 的实现。
}
 #import "UIImage+Image.h"
 #import <objc/message.h>

@implementation UIImage (Image)

/**
 load方法: 把类加载进内存时候调用,只会调用一次
 方法应先交换,再去调用
 */
+ (void)load {
    //1、获取 imageNamed 方法地址
    // class_getClassMethod 获取某个类的方法
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    //2、获取 tw_iamgeNamed 方法地址
    Method tw_imageNamedMethod = class_getClassMethod(self, @selector(tw_imageNamed:));
    //3、交换方法地址,相当于交换实现方式   method_exchangeImplementations 交换两个方法的实现
    method_exchangeImplementations(imageNamedMethod, tw_imageNamedMethod);
}

/**
 以下代码是不会有死循环的
 调用 imageNamed => tw_imageNamed
 调用 tw_imageNamed => imageNamed
 */
+ (UIImage *)tw_imageNamed:(NSString *)name {
    UIImage *image = [UIImage tw_imageNamed:name];
    if (image) {
        NSLog(@"加载成功");
    }else {
        NSLog(@"加载失败");
    }
    return image;
}

//不能在分类中重写系统方法 imageNamed ,因为会把系统的功能给覆盖掉,而且分类中不能调用super。
//所以第二步,我们要自己实现一个带有扩展功能的方法。

@end

总结: 在方法调用流程第三步的时候,交换两个方法地址指向,而且改变指向要在系统的 imageNamed: 方法调用前,所以将代码写在了分类的load 方法中。最后运行的时候系统的方法就会去找我们的方法的实现。

Runtime 利用关联对象给分类动态添加属性

**原理:**给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类内存空间。

应用场景: 给系统的类(或者分类)添加属性的时候,可以使用runtime 动态添加属性方法。
注:在分类中国是不能够添加成员属性的,虽然可以用@property ,但是仅仅会自动生成getset方法的声明,并没有带下划线的属性和方法实现生成,这就需要通过runtime来实现。

需求: 给系统 NSObject 类动态添加属性 name字符串。

#import <Foundation/Foundation.h>
@interface NSObject (Property)
//只会生成get 和 sey 方法声明,不会生成实现,也不会生成下划线成员属性
@property (nonatomic,copy) NSString *name;
@end

#import "NSObject+Property.h"
#import <objc/runtime.h>
@implementation NSObject (Property)
-(void)setName:(NSString *)name {
    // objc_setAssociatedObject (将某个值跟某个对象关联起来,将某个值存储到某个对象中)
    // object: 给哪个对象添加属性
    // key: 属性名
    // value: 属性值
    // policy: 保存策略
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name {
    return objc_getAssociatedObject(self, @"name");
}
@end

NSObject *obj = [[NSObject alloc] init];
obj.name = @"呵呵呵";
NSLog(@"%@",obj.name);

总结: 给属性赋值的本质,就是让属性与一个对象产生关联,所以要给NSObject 的分类的name 属性赋值就是让 name 和 NSObject 产生关联。

Runtime 动态添加方法

  • 应用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类添加方法解决。
  • OC 中常用懒加载,当用到的时候才去加载它,但是实际上只要一个类实现了某个方法,就会被加载进内存。当我们不想加载这么多方法的时候,就会使用到Runtime 动态的添加方法。
  • 需求:runtime 动态添加方法处理调用某一个未实现的方法和去除报错。
- (void)viewDidLoad {
    [super viewDidLoad];
    //动态添加方法
    Person *p = [[Person alloc] init];
    //默认person 没有实现run:方法,可以通过performSelector 调用,但是会报错
    //动态添加方法就不会报错
    [p performSelector:@selector(run:) withObject:@10];
}

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

@implementation Person

//没有返回值,1个参数
//void,(id,SEL)
void aaa(id self,SEL _cmd,NSNumber *meter) {
    NSLog(@"跑了%@米",meter);
}
//任何方法默认都有两个隐式参数,self,_cmd (当前方法的方法编号)
//什么时候调用: 只要一个对象调用了一个未实现的方法就会调用这个方法进行处理
//作用:动态添加方法,处理未实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == NSSelectorFromString(@"run:")) {
        //动态添加run方法
        //class : 给哪个类添加方法
        //SEL : 添加哪个方法,即添加方法的方法编号
        //IMP : 方法实现 => 函数 => 函数入口 => 函数名 (添加方法的函数实现(函数地址))
        //type : 方法类型(返回值 + 参数类型) v: void   @ : 对象-> self   : 表示SEL->_cmd
        class_addMethod(self, sel, (IMP)aaa, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

动态方法解析

@dynamic : 当把一个属性标记为 @dynamic 时,表示不需要编译器在编译时生成 getter 和 setter 方法的实现,而是当调用的时候才去为其生成 getter 和 setter 方法的实现。

  • 动态运行时语言 将函数决议推迟到运行时。
  • 编译时语言 在编译期进行函数决议。

@property(iOS 6以后出来的关键词)有两个对应的词,@synthsize @dynamic如果都没写,那么默认就是@synthsize var = _var(Xcode 4.5 以后);

  • @synthesize 语义是如果没有手动实现 getter 和 setter 方法,那么编译器会自动添加这两个方法(一般情况下不用)。
  • @dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。

动态变量控制

  • 动态创建一个类,并为其添加成员变量和方法
//动态添加的方法必须是已经实现的
void RuntimeTestMethodIMP(id self,SEL _cmd,NSDictionary *dict) {
    NSLog(@"传递进来的 dict%@",dict);
    NSLog(@"打印成员变量name: %@",object_getIvar(self, class_getInstanceVariable([self class], "name")));
} 
 
    //1、在运行时动态生成一个类
    Class cls = objc_allocateClassPair([NSObject class], "RuntimeClass", 0);
    
    //2、为类添加方法和成员变量
    //给指定的类添加成员变量 ,只能在objc_allocateClassPair() 和 objc_registerClassPair() 之间调用,并且不能为一个已经存在的类添加成员变量
    class_addIvar(cls, "name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
    class_addIvar(cls, "age", sizeof(int), sizeof(int), @encode(int));
    
    SEL sel = sel_registerName("RuntimeTestMethod");
    class_addMethod(cls, sel, (IMP)RuntimeTestMethodIMP, "i@:@");
    
    //3、注册类到Runtime
    objc_registerClassPair(cls);
    
    //使用创建的类
    //4、创建该类的实例,为新增的成员变量赋值
    id person = [[cls alloc] init];
    NSLog(@"实例所属的类: %@ ,实例所属的父类: %@",object_getClass(person),class_getSuperclass(object_getClass(person)));
    
    Ivar nameIvar = class_getInstanceVariable(cls, "name");
    object_setIvar(person, nameIvar, @"xiaohua");
    
    Ivar ageIvar = class_getInstanceVariable(cls, "age");
    object_setIvar(person, ageIvar, @18);
    NSLog(@"实例变量Person的值%@  %@",object_getIvar(person, nameIvar),object_getIvar(person, ageIvar));
    
    NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"xiaoli",@"name",@"25",@"age", nil];
    [person performSelector:@selector(RuntimeTestMethod) withObject:dic];
 
结果:
实例所属的类: RuntimeClass ,实例所属的父类: NSObject
实例变量Person的值xiaohua  18
传递进来的 dict{
    age = 25;
    name = xiaoli;
}
打印成员变量name: xiaohua

实现 NSCoding 的自动归档和解档

在做数据持久化的时候,如果一个模型有许多个属性,那么我们需要对每个属性都实现一遍 encodeObject 和 decodeObjectForKey 方法,如果这样的模型又有很多个,这还真的是一个十分麻烦的事情。

@interface Movie : NSObject<NSCoding>
@property (nonatomic,copy) NSString *movieId;
@property (nonatomic,copy) NSString *movieName;
@property (nonatomic,copy) NSString *pic_url;
@end

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:_movieId forKey:@"id"];
    [aCoder encodeObject:_movieName forKey:@"name"];
    [aCoder encodeObject:_pic_url forKey:@"url"];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        self.movieId = [aDecoder decodeObjectForKey:@"id"];
        self.movieName = [aDecoder decodeObjectForKey:@"name"];
        self.pic_url = [aDecoder decodeObjectForKey:@"url"];
    }
    return self;
}

@end

像上面的代码,如果有100个属性,那就太麻烦了,可以使用Runtime 来处理:

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Movie class], &count);
    for (int i = 0; i < count; i ++) {
        Ivar ivar = ivars[i];
        //查看成员变量
        const char *name = ivar_getName(ivar);
        //归档
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        for (int i = 0; i <count; i ++) {
            Ivar ivar = ivars[i];
            const char *name = ivar_getName(ivar);
            //归档
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [aDecoder decodeObjectForKey:key];
            //设置到成员变量身上
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}

这样不管有多少属性,几行代码就能搞定,下面对代码进行精简(两句话搞定):

#import "Movie.h"
#import <objc/runtime.h>

#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [self valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\

#define initCoderRuntime(A) \
\
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [decoder decodeObjectForKey:key];\
[self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self;\
\

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
    encodeRuntime(Movie)
}

- (id)initWithCoder:(NSCoder *)decoder
{
    initCoderRuntime(Movie)
}
@end

上面就是抽成了两个宏,这样使用起来就很方便了。

  • Runtime下Class的各项操作:

    • class_copyPropertyList (获取类中所有属性):
    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i = 0; i < count; i ++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"property %@",[NSString stringWithUTF8String:propertyName]);
    }
    
    • class_copyMethodList (获取类的所有方法):
    unsigned int count = 0;
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i = 0; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"method = %@",NSStringFromSelector(method_getName(method)));
    }
    
    • class_copyIvarList (获取类中所有成员变量)
     unsigned int count = 0;
      Ivar *ivarList = class_copyIvarList([self class], &count);
      for (unsigned int i = 0; i < count; i ++) {
          Ivar myIvar = ivarList[i];
          const char *ivarName = ivar_getName(myIvar);
          NSLog(@"Ivar  %@", [NSString stringWithUTF8String:ivarName]);
      }
    
    • class_copyProtocolList (获取协议列表)
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol  %@", [NSString stringWithUTF8String:protocolName]);
    }
    
    • class_getMethod (获取类方法)
      Class PersonClass = object_getClass([父类 class]);
      SEL oriSEL = @selector(xxx);
      Method oriMethod = class_getMethod(子类, oriSEL);
    
    • class_getInstanceMethod (获取实例方法)
    Class PersonClass = object_getClass([xiaoming class]);
      SEL oriSEL = @selector(test2);
      Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
    
    • class_addMethod (动态添加方法)
    BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
    
    • class_replaceMethod (动态替换方法实现)
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

  • method_exchangeImplementations (交换两个方法的实现)
method_exchangeImplementations(method1, method2);
// 根据名字得到实例变量的Ivar指针
   Ivar oneIVIvar = class_getInstanceVariable([Person class], name);
   
   //找到后可以直接对私有成员变量赋值(强制修改name属性)
   object_setIvar(_per, oneIVIvar, @"age");
   
   
   //动态添加方法
   class_addMethod([person class]:Class cls 类型, @selector(eat):待调用的方法名称, (IMP)myAddingFunction:(IMP)myAddingFunction,IMP是一个函数指针,这里表示指定具体实现方法myAddingFunction, 0:0代表没有参数);
   
   //获得某个类的类方法
   Method class_getClassMethod(Class cls , SEL name)
   
   //获得成员变量的名字
   const char *ivar_getName(Ivar v);
   
   //将某个值跟某个对象关联起来,将某个值存储到某个对象中
   void objc_setAssociatedObject(id object:表示关联者,是一个对象,变量名理所当然也是object , const void *key:获取被关联者的索引key ,id value :被关联者 ,objc_AssociationPolicy policy:关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC)
   
   //利用参数key 将对象object中存储的对应值取出来
   id objc_getAssociatedObject(id object , const void *key)

面试题

1、以下代码输出什么?

@interface Son : Person
@end
@implementation Son
- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
答案:都是Son
  • 消息传递
    [外链图片转存失败(img-J35obs1b-1568884592318)(media/15398503045242/15476245373296.jpg)]
    [外链图片转存失败(img-Q6eeDv5q-1568884592320)(media/15398503045242/15476246447058.jpg)]
  • class 获取当前方法的调用者的类
  • superClass获取当前方法的调用者的父类
  • super 仅仅是一个编译器指示器,就是给编译器看的,不是一个指针
  • 只要编译器看到super 这个标志,就会让当前对象去调用父类方法,本质还是当前对象在调用。
  • self是类的隐藏参数,指向当前调用方法的这个类的实例。而super本质是一个编译器标识符,和self是指向的同一个消息接受者。
  • 当使用self调用方法时,会从当前类的方法列表中开始查找,如果没有就从父类中再找。
  • 而当使用super时,则从父类的方法列表中开始找。然后调用父类的这个方法。
  • 调用 [self class]时,会转化成objc_msgSend函数。

    id objc_msgSend(id self, SEL op, ...)
  • 调用 [super class]时,会转化成 objc_msgSendSuper 函数。

    id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一个参数是 objc_super 这样一个结构体,其定义如下
 struct objc_super {
     __unsafe_unretained id receiver;
     __unsafe_unretained Class super_class;
 };
第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self
第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son
objc Runtime 开源代码对- (Class)class方法的实现
-(Class)class { 
    return object_getClass(self);
}
  • 能否向编译后的类中增加实例变量?不能增加实例变量。
  • 能够向动态添加的类中增加实例变量? 可以的,因为动态添加的类的过程中,只要在调用注册类之前就是可以添加实例变量的。

2、讲一下 OC 的消息机制

  • OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
  • objc_msgSend底层有3大阶段
    • 消息发送(当前类、父类中查找)、动态方法解析、消息转发

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

  • OC 是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
  • OC 的动态性就是 Runtime 来支撑和实现的, Runtime 是一套 C 语言的API,封装了很多动态性相关的函数
  • 平时写的很多代,底层都是转换成 Runtime API 进行实现的

4、Runtime 平时的具体应用

  • 利用关联对象(AssociatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 交换方法实现(交换系统的方法)
  • 利用消息转发机制解决方法找不到的异常问题

5、以下代码能不能执行成功?如果可以,打印结果是什么?

@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
- (void)print;
@end

@implementation Person
- (void)print{
    NSLog(@"my name's %@",self.name);
}
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
   
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj print];  // my name's <ViewController: 0x7fb9ebe04470>

	Person *person = [[Person alloc] init];
    [person print];   //my name's (null)
}
@end

可以看出是能执行成功的,打印结果如下:

 Runtime-面试题[34340:1470326] my name's <ViewController: 0x7f810c50d7b0>
 Runtime-面试题[34340:1470326] my name's (null)
  • 1、 print 为什么能够调用成功 ?
    在这里插入图片描述
  • 2、 为什么 self.name 变成了 ViewController等其他内容 ?

比较好的总结:
西木 完整总结
天口三水羊 objc_msgSend
夜千寻墨
袁峥
消息机制
Method Swizzling开发实例汇总
OC最实用的runtime总结
Runtime在实际开发中的应用

https://www.jianshu.com/p/dbd184b0ba4d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值