要想全面了解 Runtime 机制,我们必须先了解 Runtime 的一些术语,他们都对应着数据结构。
id
id 是一个参数类型,它是指向某个类的实例的指针。定义如下:
typedef struct objc_object *id;
struct objc_object {
Class isa;
};
以上定义,看到 objc_object
结构体包含一个 isa
指针,根据 isa 指针就可以找到对象所属的类。
注意:
isa 指针在代码运行时并不总指向实例对象所属的类型,所以不能依靠它来确定类型,要想确定类型还是需要用对象的- class
方法。KVO
的实现原理就是将被观察对象的 >isa
指针指向一个中间类而不是真实类型。
Property
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//这个更常用
可以通过class_copyPropertyList 和 protocol_copyPropertyList 方法获取类和协议中的属性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
注意:返回的是属性列表,列表中每个元素都是一个 objc_property_t 指针
#import <Foundation/Foundation.h>
@interface Person : NSObject
/** 姓名 */
@property (strong, nonatomic) NSString *name;
/** age */
@property (assign, nonatomic) int age;
/** weight */
@property (assign, nonatomic) double weight;
@end
以上是一个 Person 类,有3个属性。让我们用上述方法获取类的运行时属性。
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([Person class], &outCount);
NSLog(@"%d", outCount);
for (NSInteger i = 0; i < outCount; i++) {
NSString *name = @(property_getName(properties[i]));
NSString *attributes = @(property_getAttributes(properties[i]));
NSLog(@"%@--------%@", name, attributes);
}
打印结果如下:
test[2321:451525] 3
test[2321:451525] name--------T@"NSString",&,N,V_name
test[2321:451525] age--------Ti,N,V_age
test[2321:451525] weight--------Td,N,V_weight
property_getName
用来查找属性的名称,返回c
字符串。property_getAttributes
函数可以获得属性的名字和@encode编码
。
class_getProperty
和 protocol_getProperty
通过给出属性名在类和协议中获得属性的引用。
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
Method Swizzling(黑魔法)
- 在没有一个类的实现源码时,想改变其中一个方法的实现,除了继承重写和借助类别重命名方法之外,还有更灵活的方法
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的过程,直接触发消息转发,进入如下流程:
具体可查看:这篇文章