0%

RunTime - 自己实现KVO(isa-swizzling)

NSObject+KVO.h

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>

typedef void(^GCObserverBlock)(id observedKey, NSString *keyPath, id oldValue, id newValue);

@interface NSObject (KVO)

- (void)GC_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(GCObserverBlock)observerBlock;

- (void)GC_removeObserver:(NSObject *)observer forKey:(NSString *)key;

@end

NSObject+KVO.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//
// NSObject+KVO.m
// Runtime-运行时
//
// Created by GhostClock on 2018/5/17.
// Copyright © 2018年 GhostClock. All rights reserved.
//

#import "NSObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>

@interface GCObservationInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) GCObserverBlock observerBlock;

@end

@implementation GCObservationInfo

- (instancetype)initWithObserver:(NSObject *)observer keyPath:(NSString *)key block:(GCObserverBlock)observerBlock {
self = [super init];
if (self) {
_observer = observer;
_keyPath = key;
_observerBlock = [observerBlock copy];
}
return self;
}

@end


#pragma mark - ================


static NSString *const KVONotifying = @"GCKVONotifying_";
static NSString *const AssociatedObjectKey = @"AssociatedObjectKey";

@implementation NSObject (KVO)

// 获取方法类型
const char* methodTypeEncoding(Method originalMethod) {
return method_getTypeEncoding(originalMethod);
}

// 观察的属性前面加上set 例如setAge:
NSString * setterForGetter(NSString *key) {
if (key.length <= 0) {
return nil;
}
//把第一个字母变成大写
NSString *firstLetter = [[key substringToIndex:1] uppercaseString];
//其余的全部变成小写
NSString *remainingLetter = [key substringFromIndex:1];
// 拼接成set方法
NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, remainingLetter];
return setter;
}

// 观察的属性前面要是有 set 前缀和 : 后缀,就将其去掉 setAge: -> age
NSString * getterForSetter(NSString *setter) {
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
return setter;
}

// 删除set开头和:结尾
NSRange range = NSMakeRange(3, setter.length - 4);
NSString *getter = [setter substringWithRange:range];

// 把第一个字母变成小写
NSString *firstLetter = [[getter substringToIndex:1] lowercaseString];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstLetter];

return getter;
}

#pragma mark - 判断该kvo类有没有这个setter方法
- (BOOL)haveSelector:(SEL)setter {
Class class = object_getClass(self);
unsigned int outCount = 0;
Method *methods = class_copyMethodList(class, &outCount);
for (int i = 0 ; i < outCount; i ++) {
SEL setSelector = method_getName(methods[i]);
if (setSelector == setter) {
free(methods);
return YES;
}
}
free(methods);
return NO;
}

#pragma mark - 重写setter方法
// 新的setter方法在调用原setter方法后,通知每个观察者
static void kvo_setter(id self, SEL _cmd, id newValue) {
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterForSetter(setterName);
if (!getterName) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"没有相应的属性" userInfo:nil];
return;
}

id oldValue = [self valueForKey:getterName];

struct objc_super superClass = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};

void (*objc_msgSendSuperCasted)(void*, SEL, id) = (void *)objc_msgSendSuper; // 转换objc_msgSendSuper
objc_msgSendSuperCasted(&superClass, _cmd, newValue);

NSMutableArray *observer = objc_getAssociatedObject(self, &AssociatedObjectKey);
for (GCObservationInfo *info in observer) {
if ([info.keyPath isEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
info.observerBlock(self, getterName, oldValue, newValue);
});
}
}
}

Class kvo_class(id self, SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}

#pragma mark - 动态创建这个kvo类
- (Class)createKVONotifyingClassWithOriginalClassName:(NSString *)originClassName {

NSString *kvoClassName = [KVONotifying stringByAppendingString:originClassName];
// 如果这个kvo类存在,就直接返回
Class kvoClass = NSClassFromString(kvoClassName);
if (kvoClass) {
return kvoClass;
}

// 这个kvo类不存在, 就用runtime创建一个, 父类是该观察者
Class originalClass = object_getClass(self);
kvoClass = objc_allocateClassPair(originalClass, [kvoClassName UTF8String], 0);

// 重写掉系统的class方法
Method originMethod = class_getInstanceMethod(originalClass, @selector(class));
class_addMethod(kvoClass, @selector(class), (IMP)kvo_class, methodTypeEncoding(originMethod));
// 向runtime注册这个类
objc_registerClassPair(kvoClass);

return kvoClass;
}

#pragma mark - 外部方法
- (void)GC_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(GCObserverBlock)observerBlock {
// 用setterForGetter获得相应的setter方法,得到型如 setAge: 的选择器
SEL setterSelector = NSSelectorFromString(setterForGetter(key));

Method setterMethood = class_getInstanceMethod([self class], setterSelector);
// 1. 检查对象的类有没有相应的setter方法,如果没有则抛出异常
if (!setterMethood) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"没有相应的属性" userInfo:nil];
return;
}

// 2. 检查对象 isa 指针指向的是否是一个KVO类,如果不是,用runtime新建一个继承原来类的子类,并把 isa 指向这个新建的子类
Class kvoClass = object_getClass(self);
NSString *className = NSStringFromClass(kvoClass);
if (![className hasPrefix:KVONotifying]) {
kvoClass = [self createKVONotifyingClassWithOriginalClassName:className];
// 把isa指向新建的类
object_setClass(self, kvoClass);
}

// 3. 检查对象的KVO类有没有重写过这个类的setter方法,如果没有,添加重写的setter方法
if (![self haveSelector:setterSelector]) {
// 添加setter方法
// class_addMethod(kvoClass, setterSelector, (IMP)kvo_setter, methodTypeEncoding(setterMethood));
class_addMethod([kvoClass class], setterSelector, class_getMethodImplementation(kvoClass, @selector(kvoSetter:)), methodTypeEncoding(setterMethood));
}

// 4.添加这个观察者
GCObservationInfo *observerInfo = [[GCObservationInfo alloc] initWithObserver:observer keyPath:key block:observerBlock];
NSMutableArray *observers = objc_getAssociatedObject(self, &AssociatedObjectKey);
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, &AssociatedObjectKey, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:observerInfo];
}

- (void)kvoSetter:(id)newValue {
unsigned int outCount = 0;
Method *methods = class_copyMethodList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Method method = methods[i];
NSString *setterName = NSStringFromSelector(method_getName(method));
if ([setterName hasPrefix:@"set"] && [setterName hasSuffix:@":"]) {
NSString *getterName = getterForSetter(setterName);

// 用kvo获取旧值
id oldValue = [self valueForKey:getterName];

struct objc_super superClass;
superClass.receiver = self;
superClass.super_class = class_getSuperclass(object_getClass(self));

// 转换 objc_msgSendSuper
void(* myMsgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;

myMsgSendSuper(&superClass, method_getName(method), newValue);

NSMutableArray *observers = objc_getAssociatedObject(self, &AssociatedObjectKey);
for (GCObservationInfo *info in observers) {
if ([info.keyPath isEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
info.observerBlock(self, getterName, oldValue, newValue);
});
}
}

break;
}
}
}



- (void)GC_removeObserver:(NSObject *)observer forKey:(NSString *)key {
NSMutableArray *observers = objc_getAssociatedObject(self, &AssociatedObjectKey);
GCObservationInfo *removeInfo = nil;
for (GCObservationInfo *info in observers) {
if ([info.keyPath isEqualToString:key] && info.observer == observer) {
removeInfo = info;
break;
}
}
[observers removeObject:removeInfo];
}

@end

Demo 地址

https://github.com/GhostClock/Runtime-Demo