要想全面了解 Runtime 机制,我们必须先了解 Runtime 的一些术语,他们都对应着数据结构。

id

id 是一个参数类型,它是指向某个类的实例的指针。定义如下:

  1. typedef struct objc_object *id;
  2. struct objc_object {
  3. Class isa;
  4. };

以上定义,看到 objc_object 结构体包含一个 isa 指针,根据 isa 指针就可以找到对象所属的类。

注意:
isa 指针在代码运行时并不总指向实例对象所属的类型,所以不能依靠它来确定类型,要想确定类型还是需要用对象的 - class 方法。 KVO 的实现原理就是将被观察对象的 > isa 指针指向一个中间类而不是真实类型。

Property

  1. typedef struct objc_property *Property;
  2. typedef struct objc_property *objc_property_t;//这个更常用

可以通过class_copyPropertyList 和 protocol_copyPropertyList 方法获取类和协议中的属性:

  1. objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
  2. objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

注意:返回的是属性列表,列表中每个元素都是一个 objc_property_t 指针

  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. /** 姓名 */
  4. @property (strong, nonatomic) NSString *name;
  5. /** age */
  6. @property (assign, nonatomic) int age;
  7. /** weight */
  8. @property (assign, nonatomic) double weight;
  9. @end

以上是一个 Person 类,有3个属性。让我们用上述方法获取类的运行时属性。

  1. unsigned int outCount = 0;
  2. objc_property_t *properties = class_copyPropertyList([Person class], &outCount);
  3. NSLog(@"%d", outCount);
  4. for (NSInteger i = 0; i < outCount; i++) {
  5. NSString *name = @(property_getName(properties[i]));
  6. NSString *attributes = @(property_getAttributes(properties[i]));
  7. NSLog(@"%@--------%@", name, attributes);
  8. }

打印结果如下:

  1. test[2321:451525] 3
  2. test[2321:451525] name--------T@"NSString",&,N,V_name
  3. test[2321:451525] age--------Ti,N,V_age
  4. test[2321:451525] weight--------Td,N,V_weight
  • property_getName 用来查找属性的名称,返回 c 字符串。
  • property_getAttributes函数可以获得属性的名字和@encode编码

关于类型编码参考:类型编码属性的类型编码

class_getPropertyprotocol_getProperty 通过给出属性名在类和协议中获得属性的引用。

  1. objc_property_t class_getProperty(Class cls, const char *name)
  2. objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

Method Swizzling(黑魔法)

image.png

  • 在没有一个类的实现源码时,想改变其中一个方法的实现,除了继承重写和借助类别重命名方法之外,还有更灵活的方法Method Swizzling
  • Method Swizzling指的是改变一个已存在的选择器对应的实现过程,OC中方法的调用能够在运行时,通过改变类的调度表(dispach table)中选择器到最终函数间的映射关系,归根结底,就是偷换了selector的IMP
  • 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字,利用OC的动态特性,可以实现在运行时偷换selector对应的方法实现。
  • 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系,IMP有点类似函数指针,指向具体的Method实现。
  • 可以利用method_exchangeImplementations来交换两个方法的IMP
  • class_replaceMethod来修改类;
  • method_setImplementation来直接设置某个方法的IMP。

如何利用好这个技巧写出简洁、有效且维护性更好的代码。参考:Method Swizzling 和 AOP 实践

Aspect-Oriented Programming(AOP)

  • 类似记录日志、身份验证、缓存等事务非常琐碎,与业务逻辑无关,很多地方都有,又很难抽象出一个模块,这种程序设计问题,业界给它们起了一个名字叫横向关注点(Cross-cutting concern)
  • AOP作用就是分离横向关注点(Cross-cutting concern)来提高模块复用性,它可以在既有的代码添加一些额外的行为(记录日志、身份验证、缓存)而无需修改代码。

动态绑定

  • 运行时的消息分发机制为动态绑定提供支持
  • 当向一个动态类型确定了的对象发送消息是,运行环境系统会通过接受的isa指针定位对象的类,并以此为起点确定被调用的方法,方法和消息是动态绑定的。
  • 不必在OC代码做任何工作,就可自动获取动态绑定的好处

objc_msgForward

是IMP类型,用于消息转发,当向一个对象发送消息,但它并没有实现,_objc_msgForward会尝试做消息转发。
如果手动调用,将跳过查找IMP的过程,直接触发消息转发,进入如下流程:

具体可查看:这篇文章