Runtime
Runtime
Runtime
即运行时,让Objective-C拥有了动态特性,对于实现这些特性的数据结构分析,一堆大神都已经分析得很透彻,本文就不多叙述了。
objc_class
、objc_object
、objc_selector
、objc_method
、objc_ivar
、objc_property
的一些在Runtime
中的一些声明如下:
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_property *objc_property_t;
备注:在runtime.h
中,鉴于__OBJC2__
这个宏,使得编译器clang
编译代码后所有OBJC2_UNAVAILABLE
的代码都不再存在,也就是废弃掉了,所以现在运行中的runtime的代码与开源出来的代码是有出入的,不过不妨碍对Runtime
的理解
本文内容不少,按照主题分为以下内容:
- 变量与属性
- 消息及其转发
- Method Swizzling
- Runtime在KVO中的实现
- 总结
1. 变量与属性
在Objective-C中,变量与属性由两个结构题定义,分别是objc_ivar
和objc_property
,他们既有关联,也不相同。
1.1 objc_ivar
objc_ivar
在runtime.h
中声明如下:
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
可以看到,Ivar
就是结构体objc_ivar
,而objc_ivar
内根据提供的API能证实有ivar_name
ivar_type
ivar_offset
三个变量。
另外,可以发现,Ivar
就是变量的描述,包含名称、类型和内存偏移地址。
- (void)testIvar {
Ivar publicIvar = class_getInstanceVariable([TestObject class], "_publicString");
NSLog(@"public Ivar name: %s, type encoding: %s, offset: %ti", ivar_getName(publicIvar), ivar_getTypeEncoding(publicIvar), ivar_getOffset(publicIvar));
unsigned int outCount = 0;
Ivar *ivarList = class_copyIvarList([TestObject class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivarList[i];
const char *name = ivar_getName(ivar);
const char *typeEncoding = ivar_getTypeEncoding(ivar);
long offset = ivar_getOffset(ivar); // 可以注意到,这里并非强转,用的是long,而非int,也就是说objc_ivar结构体并非如所看到的一样。
NSLog(@"ivar name: %s, type encoding: %s, offset: %ti", name, typeEncoding, offset);
}
free(ivarList);
/*
public Ivar name: _publicString, type encoding: @"NSString", offset: 8
ivar name: _publicString, type encoding: @"NSString", offset: 8
ivar name: _privateString, type encoding: @"NSString", offset: 16
*/
}
1.2 objc_property
objc_property
在runtime.h
中,声明如下:
typedef struct objc_property *objc_property_t;
objc_property_t
就是objc_property
结构体,根据提供的API,可以猜测其内有name
和attribute
,name
记录的是属性名,attribute
记录的是按照一定格式排列的属性中已声明的特性的字符串(很拗口,其实就是Declared Properties).
attribute
显示的字符串格式如下:
T(以逗号分隔的@encode类型编码)V[属性名]
@encode
的类型编码有两种,分别为Objective-C中的类型编码和声明特性编码,分别如下:
Objective-C中的类型编码
Code | Meaning |
---|---|
c | A char |
i | An int |
s | A short |
l | A long l is treated as a 32-bit quantity on 64-bit programs. |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
[array type] | An array |
{name=type…} | A structure |
name=type…) | A union |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
声明特性编码
Code | Meaning |
---|---|
R | The property is read-only (readonly). |
C | The property is a copy of the value last assigned (copy). |
& | The property is a reference to the value last assigned (retain). |
N | The property is non-atomic (nonatomic). |
G | The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,). |
S | The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,). |
D | The property is dynamic (@dynamic). |
W | The property is a weak reference (__weak). |
P | The property is eligible for garbage collection. |
t | Specifies the type using old-style encoding. |
示例代码如下:
- (void)testProperty {
unsigned int outCount = 0;
objc_property_t *propertyList = class_copyPropertyList([TestObject class], &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = propertyList[i];
const char *propertyName = property_getName(property);
const char *propertyAttribute = property_getAttributes(property);
NSLog(@"property name: %s, attribute: %s", propertyName, propertyAttribute);
unsigned int attributeOutCount = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &attributeOutCount);
for (int j = 0; j < attributeOutCount; j++) {
objc_property_attribute_t attribute = attributeList[j];
NSLog(@"attribute name: %s, value: %s", attribute.name, attribute.value);
}
free(attributeList);
}
free(propertyList);
/*
property name: privateString, attribute: T@"NSString",&,V_privateString
attribute name: T, value: @"NSString"
attribute name: &, value:
attribute name: V, value: _privateString
property name: publicString, attribute: T@"NSString",&,V_publicString
attribute name: T, value: @"NSString"
attribute name: &, value:
attribute name: V, value: _publicString
*/
}
1.3 关联
objc_ivar
和objc_property
两者,有着不同的分工。
在Objective-C中,定义一个属性用到了@property
编译器指令。
@property
在默认情况下就是创建ivar + getter + setter
,即成员变量(也就是1.1中的Ivar
)及其访问器。
而@property
在此之外还需要标示出属性是“只读”还是“可写可读”,是“强引用”还是“弱引用”等特性,objc_property
就记录了这方面的信息。
2. 消息及其分发
Objective-C中的消息是在运行时才与方法实现进行绑定的,,编译器通过转换方法的实现为一个发送信息函数的调用。
即:[receiver message]
-> objc_msgSend(receiver, selector, ...)
在运行过程中,一个消息的发送会经历如下过程:
1) 找到方法的实现
2) 传递参数并调用
3) 传递实现方法的返回值
2.1 消息的发送
2.1.1 消息发送机制
消息发送的关键在于编译器为每个类和对象构建的结构。每个类包含2种基础元素:
- 一个指向父类的指针
- 一个类分发表:表中记录了方法选择器(SEL)及其实现地址(IMP)的入口
当一个对象(在objc/objc.h
中定义的struct objc_object
)被创建(即分配了内存,初始化成员变量)后,对象通过isa
(读伊萨)指针指向它的类,并且通过类,可以访问类中实现的方法及集成的类的方法。
在项目中,一般不允许直接调用objc_msgSend
等函数(在objc/message.h
中声明),可以通过clang
把Objective-C代码编译成C++代码进行观察,也可以在Build Settings
->Enable Strict Checking of objc_msgSend Calls
中设置为No
,再在代码中调用。
在消息发送中的消息接收者,有两个需要注意的参数,一个是消息接收对象(id),一个是方法选择器(SEL)。它们是隐式的,在编译时,会被插入到objc_msgSend
函数中,充当参数。对于这里的理解,有一个很特别的代码例子,如下
//
// MessagingObject.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "MessagingObject.h"
@implementation MessagingObject
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"self class: %@, super class: %@", [self class], [super class]);
// self class: MessagingObject, super class: MessagingObject
}
return self;
}
@end
可以看到打印的结果无论是self
还是super
,都是同一个类,理解了消息发送的原理就能明白,其实消息发送的接收对象都是self
,只是super
调用的是objc_msgSendSuper
函数而已。
继承了NSObject
或NSProxy
的类因具有运行时特性,其内的所有方法都是动态绑定的,在某些情况下,如果想直接调用,而不是通过运行时的消息发送来执行,那么可以通过使用methodForSelector:
方法获得消息接收者某方法的SEL
(方法选择器)对应的IMP
(方法实现地址),直接执行函数,绕过动态绑定。示例代码如下:
//
// MessagingObject.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "MessagingObject.h"
@implementation MessagingObject
// 省略部分代码
- (void)methodWithParameter:(NSString *)parameter {
NSLog(@"Parameter: %@", parameter);
}
@end
//
// ViewController.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "ViewController.h"
#import <objc/message.h>
#import "MessagingObject.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
MessagingObject *messagingObject = [[MessagingObject alloc] init];
[self messagingTest];
}
- (void)messagingTest {
void (*directMethod)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))[messagingObject methodForSelector:@selector(methodWithParameter:)];
directMethod(messagingObject, @selector(methodWithParameter:), @"directly invoke");
// Parameter: directly invoke
}
@end
通过clang
编译成C++后的相关代码如下:
static void _I_ViewController_messagingTest(ViewController * self, SEL _cmd) {
MessagingObject *messagingObject = ((MessagingObject *(*)(id, SEL))(void *)objc_msgSend)((id)((MessagingObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MessagingObject"), sel_registerName("alloc")), sel_registerName("init"));
void (*directMethod)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))((IMP (*)(id, SEL, SEL))(void *)objc_msgSend)((id)messagingObject, sel_registerName("methodForSelector:"), sel_registerName("methodWithParameter:"));
directMethod(messagingObject, sel_registerName("methodWithParameter:"), (NSString *)&__NSConstantStringImpl__var_folders_xr_qkw3kpr13vlb7w3ht11gp_4c0000gn_T_ViewController_796ded_mi_0);
}
可以看到directMethod
是直接调用的,没有通过objc_msgSend
来调用。这种调用方式也适用于开发柯理化函数(function currying)。
2.1.2 动态添加方法
既然方法是动态绑定的,那么就肯定可以动态添加方法。代码如下:
//
// ViewController.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "ViewController.h"
#import <objc/message.h>
#import "MessagingObject.h"
@interface ViewController ()
@property MessagingObject *messagingObject;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.messagingObject = [[MessagingObject alloc] init];
[self dynamicMethodTest];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
/*
因为MessagingObject.h中,没有dynamicMethodIMP的声明,所以会warnings。看着烦,可以这么去掉。
*/
- (void)dynamicMethodTest {
objc_msgSend(self.messagingObject, @selector(dynamicMethodIMP));
// dynamicMethodIMP called
}
#pragma clang diagnostic pop
@end
//
// MessagingObject.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "MessagingObject.h"
#import <objc/message.h>
@implementation MessagingObject
// - MARK: dynamic method
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"dynamicMethodIMP called");
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethodIMP)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
#pragma clang diagnostic pop
@end
首先看ViewController.m
中的- (void)dynamicMethodTest
实现,是直接用objc_msgSend
的,当然也可以用performSelector:
,原因是在MessagingObject.h
中并没有对将要调用的方法进行声明。
然后看MessagingObject.m
中的+ (BOOL)resolveInstanceMethod:(SEL)sel
实现,它是用来查找SEL
对应的实现的,所以可以在找的时候,通过class_addMethod
函数添加一个想要与SEL
对应的函数,这个函数如所有的Objective-C方法的底层实现一样,是个自带消息接收者和方法选择器参数的函数。
class_addMethod
函数的前三个参数都很好理解,最后一个参数,是用来指定函数的相关类型的,是一个C字符串,字符串内按照如下格式填写1.2中提到过的类型编码:
[返回值类型编码][第一个参数类型编码][第二个参数类型编码][...]
由于Objective-C中的方法都必须有id
和SEL
参数,所以基本上所有的方法的类型编码中都包含@:
这两个类型编码。
最后需要了解的是,class_addMethod
这是个添加方法的函数,但不会替换掉已经存在的方法,并且+ (BOOL)resolveInstanceMethod:(SEL)sel
只会在找不到方法时才调用,所以在第一次动态添加方法后,下一次的向方法发消息时,已经能找到方法了,就不会再进入+ (BOOL)resolveInstanceMethod:(SEL)sel
。
2.2 消息的转发
正常情况下,往方法发送消息时,如果方法不存在,那么就会崩溃报错,这种情况下消息的转发机制,就提供了规避错误的机会。消息转发的实现代码如下:
//
// ViewController.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "ViewController.h"
#import <objc/message.h>
#import "MessagingObject.h"
@interface ViewController ()
@property MessagingObject *messagingObject;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.messagingObject = [[MessagingObject alloc] init];
[self messageForwardingTest];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
/*
因为MessagingObject.h中,没有surrogateMethod的声明,所以会warnings。看着烦,可以这么去掉。
*/
- (void)messageForwardingTest {
[self.messagingObject performSelector:@selector(surrogateMethod)];
// surrogateMethod called
}
#pragma clang diagnostic pop
@end
//
// MessagingObject.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "MessagingObject.h"
#import <objc/message.h>
#import "SurrogateObject.h"
@interface MessagingObject ()
@property SurrogateObject *surrogateObject;
@end
@implementation MessagingObject
- (instancetype)init
{
self = [super init];
if (self) {
_surrogateObject = [SurrogateObject new];
}
return self;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel { // 1 & 4
return [super resolveInstanceMethod:sel];
}
#pragma clang diagnostic pop
// - MARK: massaging forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector { // 2
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { // 3
NSMethodSignature *methodSignature = [super methodSignatureForSelector: aSelector];
if (!methodSignature) {
methodSignature = [self.surrogateObject methodSignatureForSelector:aSelector];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation { // 5
if ([self respondsToSelector: [anInvocation selector]]) {
[anInvocation invokeWithTarget:self];
} else if ([self.surrogateObject respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:self.surrogateObject];
} else {
[super forwardInvocation:anInvocation];
}
}
@end
//
// SurrogateObject.h
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface SurrogateObject : NSObject
- (void)surrogateMethod;
@end
//
// SurrogateObject.m
// Message
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "SurrogateObject.h"
@implementation SurrogateObject
- (void)surrogateMethod {
NSLog(@"surrogateMethod called");
}
@end
消息转发的实现为了方便理解,多增加了一个类,使消息转发让类在一种在单继承规则下拥有多继承功能的用途(注意,并不是真正意义上的多继承,所以反射API的结果不会被影响)。
消息的转发过程:objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
->+ (BOOL)resolveInstanceMethod:(SEL)sel
->- (id)forwardingTargetForSelector:(SEL)aSelector
->- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
->+ (BOOL)resolveInstanceMethod:(SEL)sel
->- (void)forwardInvocation:(NSInvocation *)anInvocation
->- (void)invokeWithTarget:(id)target
1) 调用消息发送函数objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
2) 因找不到方法,所以第一次触发+ (BOOL)resolveInstanceMethod:(SEL)sel
3) 在父类中+ (BOOL)resolveInstanceMethod:(SEL)sel
还找不到方法,就触发- (id)forwardingTargetForSelector:(SEL)aSelector
4) 若父类的消息转发还没找到方法,触发- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
,希望获得方法签名NSMethodSignature
对象
5) 当方法签名非nil时,第二次触发+ (BOOL)resolveInstanceMethod:(SEL)sel
6) 这次有了NSMethodSignature
对象,就可以构成NSInvocation
的对象,并触发- (void)forwardInvocation:(NSInvocation *)anInvocation
7) 在- (void)forwardInvocation:(NSInvocation *)anInvocation
中通过- (void)invokeWithTarget:(id)target
调用被转发的方法,至此完成整个转发流程。
3. Method Swizzling
Method Swizzling通常被用于需要Hook(钩子)的场景,又或者是以AOP
(Aspect Oriented Programming,面向切面编程)思想进行开发。
前面提及过,类结构体中的其中一个元素是一个类分发列表,列表中的一个结构体objc_method
内记录着方法选择器(SEL)及其实现地址(IMP)的数据。而运行时使方法的实现与方法选择器在运行中才进行绑定,所以在进行动态绑定前,就有机会改变SEL和IMP的对应关系,而这种改变,就是Method Swizzling。虽然已经被OBJC2_UNAVAILABLE;
标记,但也可以做个参考,如下:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
使用Method Swizzling的例子如下:
//
// AppDelegate.m
// MethodSwizzling
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "AppDelegate.h"
#import <objc/runtime.h>
#import "SwizzlingMethodObject.h"
#import "ViewController.h"
#import "UIViewController+MethodSwizzling.h"
@interface AppDelegate ()
@property SwizzlingMethodObject *smObject;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self initMethodSwizzling];
return YES;
}
- (void)initMethodSwizzling {
// 原方法
Method original_viewDidLoad = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
IMP original_viewDidLoad_imp = method_getImplementation(original_viewDidLoad);
// 目标方法
Method method_swizzling_viewDidLoad = class_getInstanceMethod([UIViewController class], @selector(ms_viewDidLoad));
IMP ms_viewDidLoad_imp = method_getImplementation(method_swizzling_viewDidLoad);
self.smObject = [[SwizzlingMethodObject alloc] initWithOriginalClass:[UIViewController class]
OriginalSelector:@selector(viewDidLoad)
OriginalIMP:original_viewDidLoad_imp
SMClass:[UIViewController class]
SMSelector:@selector(ms_viewDidLoad)
SMIMP:ms_viewDidLoad_imp];
[self.smObject swizzle];
}
@end
//
// ViewController.m
// MethodSwizzling
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"original viewDidLoad");
}
@end
//
// UIViewController+MethodSwizzling.m
// MethodSwizzling
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "UIViewController+MethodSwizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (MethodSwizzling)
- (void)ms_viewDidLoad {
NSLog(@"Method Swizzling viewDidLoad");
}
@end
//
// SwizzlingMethodObject.m
// MethodSwizzling
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "SwizzlingMethodObject.h"
#import <objc/objc.h>
#import <objc/runtime.h>
@interface SwizzlingMethodObject ()
@property (nonatomic, assign) Class original_class;
@property (nonatomic, assign) SEL original_selector;
@property (nonatomic, assign) IMP original_imp;
@property (nonatomic, assign) Class sm_class;
@property (nonatomic, assign) SEL sm_selector;
@property (nonatomic, assign) IMP sm_imp;
@end
@implementation SwizzlingMethodObject
- (instancetype)initWithOriginalClass:(Class)original_class
OriginalSelector:(SEL)original_selector
OriginalIMP:(IMP)original_imp
SMClass:(Class)sm_class
SMSelector:(SEL)sm_selector
SMIMP:(IMP)sm_imp
{
self = [super init];
if (self) {
_original_class = original_class;
_original_selector = original_selector;
_original_imp = original_imp;
_sm_class = sm_class;
_sm_selector = sm_selector;
_sm_imp = sm_imp;
}
return self;
}
- (void)swizzle {
Method originalMethod = class_getInstanceMethod(self.original_class, self.original_selector);
/*
Method smMethod = class_getInstanceMethod(self.sm_class, self.sm_selector);
method_exchangeImplementations(originalMethod, smMethod);
// method_exchangeImplementations这个函数就等于下边两个函数的结合,也就是两个方法互换IMP
method_setImplementation(originalMethod, self.sm_imp);
method_setImplementation(smMethod, self.original_imp);
*/
method_setImplementation(originalMethod, self.sm_imp);
}
@end
打印结果如下:
Method Swizzling viewDidLoad
original viewDidLoad
可以看到,Method Swizzling后的方法打印也出现了,证明IMP
替换成功了。
至于为什么连原方法的打印也出现了呢?那是因为viewDidLoad
是UIViewController
中生命周期的其中一个方法,在ViewController
中被重写了,重写内容中又往super
发消息,所以会同时出现两个打印。如果是对自定义的方法进行Method Swizzling那么肯定就只是被替换后的方法被执行了。当然,可以直接注释掉viewDidLoad
方法,因子类找不到这个方法,所以isa
往父类找这个方法,然后找到了已被替换掉的IMP并执行,那么这种情况下看到的打印就只有第一条了。
4. Runtime在KVO中的实现
KVO(Key-Value Observing)其实就是利用Runtime来实现的,实现代码如下:
//
// ViewController.m
// RuntimeKVO
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "ViewController.h"
#import "NSObject+KVOImplementation.h"
@interface ViewController ()
@property int i;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.i = 0;
// [self addObserver:self forKeyPath:@"i" options:NSKeyValueObservingOptionNew context:nil];
[self rt_addObserver:self forKeyPath:@"i" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"Object: %@", object);
NSLog(@"keyPath: %@", keyPath);
NSLog(@"change: %@", change);
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%i touched", self.i);
self.i++;
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:@"i"];
}
@end
//
// NSObject+KVOImplementation.m
// RuntimeKVO
//
// Created by lzackx on 2018/3/15.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "NSObject+KVOImplementation.h"
#import <objc/message.h>
#import <objc/runtime.h>
@implementation NSObject (KVOImplementation)
static const void *kvo_observer = "kvo_observer";
static const void *kvo_key_path = "kvo_key_path";
static const void *kvo_options = "kvo_options";
- (void)rt_addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context {
NSString *originalClassName = NSStringFromClass([self class]);
NSString *kvoClassName = [NSString stringWithFormat:@"kvo_%@", originalClassName];
Class class = objc_allocateClassPair([self class], kvoClassName.UTF8String, 0);
id value = [observer valueForKeyPath:keyPath];
/*
按照例子 只列出了int类型,其他基础类型就不列出来了,原理相同
需要注意class_addMethod函数中的第一个参数该传递什么值,因为在原类中,是往setter方法发消息,所以为了能被调用,需要正确名称的方法选择器,
又因为是把self对象设置为了其子类,所以这里是直接添加Setter方法(也就是重写了),而不是利用Method Swizzling把原类的Setter的IMP替换掉。
*/
char type[4] = "v@:";
if (strcmp([(NSNumber *)value objCType], @encode(int)) == 0) {
type[3] = *[(NSNumber *)value objCType];
class_addMethod(class, NSSelectorFromString([NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]]), (IMP)setInt, type);
} else {
type[3] = *[value objCType];
class_addMethod(class, NSSelectorFromString([NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]]), (IMP)setObject, type);
}
objc_registerClassPair(class);
object_setClass(self, class);
// 由于category不能新增存储属性,所以用runtime的方式保存好相关信息,便于在observeValueForKeyPath:ofObject:change:context:用上。(实际的实现当然是不需要这样的)
objc_setAssociatedObject(self, kvo_observer, observer, OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(self, kvo_key_path, keyPath, OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(self, kvo_options, [NSNumber numberWithUnsignedLong:options], OBJC_ASSOCIATION_ASSIGN);
}
void setInt(id self, SEL _cmd, int i) {
struct objc_super super_struct = {self,class_getSuperclass([self class])};
objc_msgSendSuper(&super_struct, _cmd, i);
// 拿回需要用上的信息
id observer = objc_getAssociatedObject(self, kvo_observer);
NSString *keyPath = objc_getAssociatedObject(self, kvo_key_path);
NSUInteger options = [objc_getAssociatedObject(self, kvo_options) unsignedLongValue];
NSMutableDictionary *change = [NSMutableDictionary dictionary];
// NSKeyValueChangeKindKey的值因为这里的示例只有set,所以也就只把set的类别写死,自定义KVO时,可以根据情况自定义添加更多的信息
[change addEntriesFromDictionary:@{NSKeyValueChangeKindKey: [NSNumber numberWithInt:NSKeyValueChangeSetting]}];
// options是以位来记录值的,只列出新值的,原理相同,其他的就不列出来了
if (options & NSKeyValueObservingOptionNew) {
[change addEntriesFromDictionary:@{NSKeyValueChangeNewKey: [NSNumber numberWithInt:i]}];
}
if (change.count > 0) {
objc_msgSend(observer,
@selector(observeValueForKeyPath:ofObject:change:context:),
keyPath,
self,
change,
nil);
}
}
void setObject(id self, SEL _cmd, id object) {
struct objc_super super_struct = {self,class_getSuperclass([self class])};
objc_msgSendSuper(&super_struct, _cmd, object);
// 拿回需要用上的信息
id observer = objc_getAssociatedObject(self, kvo_observer);
NSString *keyPath = objc_getAssociatedObject(self, kvo_key_path);
NSUInteger options = [objc_getAssociatedObject(self, kvo_options) unsignedLongValue];
NSMutableDictionary *change = [NSMutableDictionary dictionary];
// NSKeyValueChangeKindKey的值因为这里的示例只有set,所以也就只把set的类别写死,自定义KVO时,可以根据情况自定义添加更多的信息
[change addEntriesFromDictionary:@{NSKeyValueChangeKindKey: [NSNumber numberWithInt:NSKeyValueChangeSetting]}];
// options是以位来记录值的,只列出新值的,原理相同,其他的就不列出来了
if (options & NSKeyValueObservingOptionNew) {
[change addEntriesFromDictionary:@{NSKeyValueChangeNewKey: object}];
}
if (change.count > 0) {
objc_msgSend(observer,
@selector(observeValueForKeyPath:ofObject:change:context:),
keyPath,
self,
change,
nil);
}
}
@end
KVO的实现,其实就是在添加观察者的时候,把消息接收者的类设置为其子类,并重写set方法,基于子类可以调用父类的方法以及KVC的实现机制,最后将消息发送回一个特定的方法中,从而达到观察的效果。代码中尽量做到了还原,尽量做到代码复用,但一些较为相同的写法,列了例子后就不再一一列出了,比较关键的地方也用注释进行了解释。