摘录:网易iOS App运行时Crash自动防护实践
方法调用流程
让我们看一下方法调用在运行时的过程。
runtime中具体的方法调用流程大致如下:
首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
如果没找到,去父类指针所指向的对象中执行1,2.
以此类推,如果一直到根类还没找到,转向拦截调用,走消息转发机制。
如果没有重写拦截调用的方法,程序报错。
3.1.3 拦截调用
在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢?
拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理:
拦截调用的整个流程即Objective-C的消息转发机制。其具体流程如下图:
由上图可见,在一个函数找不到时,runtime提供了三种方式去补救:
调用resolveInstanceMethod给个机会让类添加这个实现这个函数
调用forwardingTargetForSelector让别的对象去执行这个函数
调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
如果都不中,调用doesNotRecognizeSelector
抛出异常。
3.1.4 unrecognized selector crash 防护方案
既然可以补救,我们完全也可以利用消息转发机制来做文章。那么问题来了,在这三个步骤里面,选择哪一步去改造比较合适呢。
这里我们选择了第二步forwardingTargetForSelector来做文章。原因如下:
resolveInstanceMethod需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余的
forwardInvocation可以通过NSInvocation的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation对象,并且forwardInvocation的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写
forwardingTargetForSelector可以将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写
选择了forwardingTargetForSelector之后,可以将NSObject的该方法重写,做以下几步的处理:
动态创建一个桩类
动态为桩类添加对应的Selector,用一个通用的返回0的函数来实现该SEL的IMP
将消息直接转发到这个桩类对象上。
流程图如下:
注意如果对象的类本事如果重写了forwardInvocation方法的话,就不应该对forwardingTargetForSelector进行重写了,否则会影响到该类型的对象原本的消息转发流程。
通过重写NSObject的forwardingTargetForSelector方法,我们就可以将无法识别的方法进行拦截并且将消息转发到安全的桩类对象中,从而可以使app继续正常运行。
示例
//key 为空
NSString *t = nil;
NSDictionary *dic = @{t:@"Test"};
// value 为空
NSString *t = nil;
NSDictionary *dic = @{@"Test":t};
//运行奔溃报告
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
处理:在运行时替换这个方法:initWithObjects:forKeys:count:
代码摘录:QYCrashProtector
crash保护处理
//NSObject+CrashProtector.h
@interface NSObject (CrashProtector)
+ (void)openCP;
+ (BOOL)swizzlingInstanceMethod:(SEL _Nullable )originalSelector replaceMethod:(SEL _Nullable )replaceSelector;
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSDictionary
@interface NSDictionary (CrashProtector)
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSMutableDictionary
@interface NSMutableDictionary (CrashProtector)
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSArray
@interface NSArray (CrashProtector)
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSMutableArray
@interface NSMutableArray (CrashProtector)
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSString
@interface NSString (CrashProtector)
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSMutableString
@interface NSMutableString (CrashProtector)
@end
//-----------------------------------------------------------------------------------------------------------------------------
//CPWeakProxy
//拿了YY大神的YYWeakProxy,感谢YY大神 链接 :https://github.com/ibireme/YYKit/blob/master/YYKit/Utility/YYWeakProxy.h
@interface CPWeakProxy : NSProxy
/**
The proxy target.
*/
@property (nullable, nonatomic, weak, readonly) id target;
/**
Creates a new weak proxy for target.
@param target Target object.
@return A new proxy object.
*/
- (instancetype _Nullable )initWithTarget:(id _Nullable )target;
/**
Creates a new weak proxy for target.
@param target Target object.
@return A new proxy object.
*/
+ (instancetype _Nullable )proxyWithTarget:(id _Nullable )target;
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSTimer
@interface NSTimer (CrashProtector)
@end
//-----------------------------------------------------------------------------------------------------------------------------
//KVOProxy
@class CPKVOInfo;
@interface KVOProxy : NSObject
-(BOOL)addKVOinfo:(id _Nullable )object info:(CPKVOInfo *_Nullable)info;
-(void)removeKVOinfo:(id _Nullable )object keyPath:(NSString *_Nullable)keyPath block:(void(^_Nullable)()) block;
-(void)removeAllObserve;
@end
typedef void (^CPKVONotificationBlock)(id _Nullable observer, id _Nullable object, NSDictionary * _Nullable change);
//-----------------------------------------------------------------------------------------------------------------------------
//CPKVOInfo
@interface CPKVOInfo : NSObject
- (instancetype _Nullable )initWithKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(void *_Nullable)context;
@end
//NSObject+CrashProtector.m
@implementation NSObject (CrashProtector)
+ (void)openCP
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//字典
[self swizzlingInstance:objc_getClass("__NSPlaceholderDictionary") orginalMethod:@selector(initWithObjects:forKeys:count:) replaceMethod:NSSelectorFromString(@"qiye_initWithObjects:forKeys:count:")];
[self swizzlingInstance:objc_getClass("__NSPlaceholderDictionary") orginalMethod:@selector(dictionaryWithObjects:forKeys:count:) replaceMethod:NSSelectorFromString(@"qiye_dictionaryWithObjects:forKeys:count:")];
[self swizzlingInstance:objc_getClass("__NSDictionaryM") orginalMethod:@selector(setObject:forKey:) replaceMethod:NSSelectorFromString(@"qiye_setObject:forKey:")];
//数组
[self swizzlingInstance:objc_getClass("__NSPlaceholderArray") orginalMethod:@selector(initWithObjects:count:) replaceMethod:NSSelectorFromString(@"qiye_initWithObjects:count:")];
[self swizzlingInstance:objc_getClass("__NSArrayI") orginalMethod:@selector(objectAtIndex:) replaceMethod:NSSelectorFromString(@"qiye_objectAtIndex:")];
[self swizzlingInstance:objc_getClass("__NSArrayI") orginalMethod:@selector(objectAtIndexedSubscript:) replaceMethod:NSSelectorFromString(@"qiye_objectAtIndexedSubscript:")];
[self swizzlingInstance:objc_getClass("__NSArrayM") orginalMethod:@selector(addObject:) replaceMethod:NSSelectorFromString(@"qiye_addObject:")];
[self swizzlingInstance:objc_getClass("__NSArrayM") orginalMethod:@selector(insertObject:atIndex:) replaceMethod:NSSelectorFromString(@"qiye_insertObject:atIndex:")];
[self swizzlingInstance:objc_getClass("__NSArrayM") orginalMethod:@selector(objectAtIndex:) replaceMethod:NSSelectorFromString(@"qiye_objectAtIndex:")];
[self swizzlingInstance:objc_getClass("NSPlaceholderString") orginalMethod:@selector(initWithString:) replaceMethod:NSSelectorFromString(@"qiye_initWithString:")];
[self swizzlingInstance:objc_getClass("__NSCFConstantString") orginalMethod:@selector(hasSuffix:) replaceMethod:NSSelectorFromString(@"qiye_hasSuffix:")];
[self swizzlingInstance:objc_getClass("__NSCFConstantString") orginalMethod:@selector(hasPrefix:) replaceMethod:NSSelectorFromString(@"qiye_hasPrefix:")];
[self swizzlingInstance:objc_getClass("NSPlaceholderMutableString") orginalMethod:@selector(initWithString:) replaceMethod:NSSelectorFromString(@"qiye_initWithString:")];
[self swizzlingClass:objc_getClass("NSTimer") replaceClassMethod:NSSelectorFromString(@"scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:") withMethod:NSSelectorFromString(@"qiye_scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:")];
[self swizzlingClass:objc_getClass("NSTimer") replaceClassMethod:@selector(timerWithTimeInterval:target:selector:userInfo:repeats:) withMethod:NSSelectorFromString(@"qiye_timerWithTimeInterval:target:selector:userInfo:repeats:")];
[self swizzlingInstance:objc_getClass("NSNotificationCenter") orginalMethod:NSSelectorFromString(@"addObserver:selector:name:object:") replaceMethod:NSSelectorFromString(@"qiye_addObserver:selector:name:object:")];
[self swizzlingInstance:self orginalMethod:NSSelectorFromString(@"dealloc") replaceMethod:NSSelectorFromString(@"qiye_dealloc")];
[self swizzlingInstance:self orginalMethod:NSSelectorFromString(@"addObserver:forKeyPath:options:context:") replaceMethod:NSSelectorFromString(@"qiye_addObserver:forKeyPath:options:context:")];
[self swizzlingInstance:self orginalMethod:NSSelectorFromString(@"removeObserver:forKeyPath:") replaceMethod:NSSelectorFromString(@"qiye_removeObserver:forKeyPath:")];
});
}
#pragma load 这个方法只会调用一次
+(void)load
{
if (!CP_OPEN) {
NSLog(@"CrashProtector close !");
return;
}
NSLog(@"CrashProtector open !");
[self openCP];
}
//在进行方法swizzing时候,一定要注意类簇 ,比如 NSArray NSDictionary 等。
+ (BOOL)swizzlingInstanceMethod:(SEL)originalSelector replaceMethod:(SEL)replaceSelector
{
return [self swizzlingInstance:self orginalMethod:originalSelector replaceMethod:replaceSelector];
}
+(BOOL)swizzlingInstance:(Class)clz orginalMethod:(SEL)originalSelector replaceMethod:(SEL)replaceSelector{
Method original = class_getInstanceMethod(clz, originalSelector);
Method replace = class_getInstanceMethod(clz, replaceSelector);
BOOL didAddMethod =
class_addMethod(clz,
originalSelector,
method_getImplementation(replace),
method_getTypeEncoding(replace));
if (didAddMethod) {
class_replaceMethod(clz,
replaceSelector,
method_getImplementation(original),
method_getTypeEncoding(original));
} else {
method_exchangeImplementations(original, replace);
}
return YES;
}
+ (BOOL)swizzlingClass:(Class)klass replaceClassMethod:(SEL)methodSelector1 withMethod:(SEL)methodSelector2
{
if (!klass || !methodSelector1 || !methodSelector2) {
NSLog(@"Nil Parameter(s) found when swizzling.");
return NO;
}
Method method1 = class_getClassMethod(klass, methodSelector1);
Method method2 = class_getClassMethod(klass, methodSelector2);
if (method1 && method2) {
IMP imp1 = method_getImplementation(method1);
IMP imp2 = method_getImplementation(method2);
Class classMeta = object_getClass(klass);
if (class_addMethod(classMeta, methodSelector1, imp2, method_getTypeEncoding(method2))) {
class_replaceMethod(classMeta, methodSelector2, imp1, method_getTypeEncoding(method1));
} else {
method_exchangeImplementations(method1, method2);
}
return YES;
} else {
NSLog(@"Swizzling Method(s) not found while swizzling class %@.", NSStringFromClass(klass));
return NO;
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wobjc-protocol-method-implementation"
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSString *methodName = NSStringFromSelector(aSelector);
if ([NSStringFromClass([self class]) hasPrefix:@"_"] || [self isKindOfClass:NSClassFromString(@"UITextInputController")] || [NSStringFromClass([self class]) hasPrefix:@"UIKeyboard"] || [methodName isEqualToString:@"dealloc"]) {
return nil;
}
CrashProxy * crashProxy = [CrashProxy new];
crashProxy.crashMsg =[NSString stringWithFormat:@"CrashProtector: [%@ %p %@]: unrecognized selector sent to instance",NSStringFromClass([self class]),self,NSStringFromSelector(aSelector)];
class_addMethod([CrashProxy class], aSelector, [crashProxy methodForSelector:@selector(getCrashMsg)], "v@:");
return crashProxy;
}
#pragma clang diagnostic pop
#pragma KVC Protect
-(void)setNilValueForKey:(NSString *)key
{
NSLog(@"need log msg");
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"need log msg");
}
- (nullable id)valueForUndefinedKey:(NSString *)key{
NSLog(@"need log msg");
return self;
}
#pragma NSNotification
-(void)qiye_addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject
{
[observer setIsNSNotification:YES];
[self qiye_addObserver:observer selector:aSelector name:aName object:anObject];
}
-(void)qiye_dealloc
{
if ([self isNSNotification]) {
NSLog(@"[Notification] need log msg");
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
[self qiye_dealloc];
}
static const char *isNSNotification = "isNSNotification";
-(void)setIsNSNotification:(BOOL)yesOrNo
{
objc_setAssociatedObject(self, isNSNotification, @(yesOrNo), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)isNSNotification
{
NSNumber *number = objc_getAssociatedObject(self, isNSNotification);;
return [number boolValue];
}
#pragma KVO
- (void)qiye_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
CPKVOInfo * kvoInfo = [[CPKVOInfo alloc] initWithKeyPath:keyPath options:options context:context];
__weak typeof(self) wkself = self;
if([self.KVOProxy addKVOinfo:wkself info:kvoInfo]){
[self qiye_addObserver:self.KVOProxy forKeyPath:keyPath options:options context:context];
}else{
NSLog(@"KVO is more");
}
}
- (void)qiye_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
NSLog(@"qiye_removeObserver");
[self.KVOProxy removeKVOinfo:self keyPath:keyPath block:^{
[self qiye_removeObserver:observer forKeyPath:keyPath];
}];
}
- (KVOProxy *)KVOProxy
{
id proxy = objc_getAssociatedObject(self, NSObjectKVOProxyKey);
if (nil == proxy) {
proxy = [[KVOProxy alloc] init];
self.KVOProxy = proxy;
}
return proxy;
}
- (void)setKVOProxy:(KVOProxy *)proxy
{
objc_setAssociatedObject(self, NSObjectKVOProxyKey, proxy, OBJC_ASSOCIATION_ASSIGN);
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSDictionary (CrashProtector)
// fix
@implementation NSDictionary (CrashProtector)
- (instancetype)qiye_initWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt{
id safeObjects[cnt];
id safeKeys[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt ; i++) {
id key = keys[i];
id obj = objects[i];
if (!key || !obj) {
NSLog(@"need log msg");
continue;
}
safeObjects[j] = obj;
safeKeys[j] = key;
j++;
}
return [self qiye_initWithObjects:safeObjects forKeys:safeKeys count:j];
}
+ (instancetype)qiye_dictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt
{
id safeObjects[cnt];
id safeKeys[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt ; i++) {
id key = keys[i];
id obj = objects[i];
if (!key || !obj) {
NSLog(@"need log msg");
continue;
}
safeObjects[j] = obj;
safeKeys[j] = key;
j++;
}
return [self qiye_dictionaryWithObjects:safeObjects forKeys:safeKeys count:j];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSMutableDictionary (CrashProtector)
// fix
@implementation NSMutableDictionary (CrashProtector)
- (void)qiye_setObject:(nullable id)anObject forKey:(nullable id )aKey{
if (!anObject || !aKey) {
NSLog(@"need log msg");
return;
}
[self qiye_setObject:anObject forKey:aKey];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSArray (CrashProtector)
// fix
@implementation NSArray (CrashProtector)
- (instancetype)qiye_initWithObjects:(const id _Nonnull [_Nullable])objects count:(NSUInteger)cnt
{
id safeObjects[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt ; i++) {
id obj = objects[i];
if ( !obj) {
NSLog(@"need log msg");
continue;
}
safeObjects[j] = obj;
j++;
}
return [self qiye_initWithObjects:safeObjects count:j];
}
- (id)qiye_objectAtIndex:(NSUInteger)index
{
if (index >= self.count) {
NSLog(@"need log msg");
return nil;
}
return [self qiye_objectAtIndex:index];
}
- (id)qiye_objectAtIndexedSubscript:(NSUInteger)index {
if (index >= self.count) {
NSLog(@"need log msg");
return nil;
}
return [self qiye_objectAtIndexedSubscript:index];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSMutableArray (CrashProtector)
// fix
@implementation NSMutableArray (CrashProtector)
- (void)qiye_addObject:(id)anObject
{
if(nil == anObject){
NSLog(@"need log msg");
return ;
}
[self qiye_addObject:anObject];
}
- (void)qiye_insertObject:(id)anObject atIndex:(NSUInteger)index
{
if(nil == anObject){
NSLog(@"need log msg");
return ;
}
[self qiye_insertObject:anObject atIndex:index];
}
- (id)qiye_objectAtIndex:(NSUInteger)index
{
if (index >= self.count) {
NSLog(@"need log msg");
return nil;
}
return [self qiye_objectAtIndex:index];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSString (CrashProtector)
// fix
@implementation NSString (CrashProtector)
- (instancetype)qiye_initWithString:(NSString *)aString
{
if(nil == aString){
NSLog(@"need log msg");
return nil;
}
return [self qiye_initWithString:aString];
}
- (BOOL)qiye_hasPrefix:(NSString *)str
{
if(nil == str){
NSLog(@"need log msg");
return NO;
}
return [self qiye_hasPrefix:str];
}
- (BOOL)qiye_hasSuffix:(NSString *)str
{
if(nil == str){
NSLog(@"need log msg");
return NO;
}
return [self qiye_hasSuffix:str];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSMutableString (CrashProtector)
// fix
@implementation NSMutableString (CrashProtector)
- (instancetype)qiye_initWithString:(NSString *)aString
{
if(nil == aString){
NSLog(@"need log msg");
return nil;
}
return [self qiye_initWithString:aString];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// CPWeakProxy
@implementation CPWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[CPWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// NSTimer (CrashProtector)
// fix
@implementation NSTimer (CrashProtector)
+ (NSTimer *)qiye_scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo
{
NSLog(@"qiye_scheduledTimerWithTimeInterval");
return [self qiye_scheduledTimerWithTimeInterval:ti target:[CPWeakProxy proxyWithTarget:aTarget] selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
+ (NSTimer *)qiye_timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
NSLog(@"qiye_timerWithTimeInterval");
return [self qiye_timerWithTimeInterval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// KVOProxy
// fix
@implementation KVOProxy{
pthread_mutex_t _mutex;
NSMapTable *> *_objectInfosMap;
}
- (instancetype)init
{
self = [super init];
if (nil != self) {
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_mutex, NULL);
}
return self;
}
-(BOOL)addKVOinfo:(id)object info:(CPKVOInfo *)info
{
[self lock];
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
__block BOOL isHas = NO;
[infos enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if([[info valueForKey:@"_keyPath"] isEqualToString:[obj valueForKey:@"_keyPath"]]){
*stop = YES;
isHas = YES;
}
}];
if(isHas) {
[self unlock];
return NO ;
}
if(nil == infos){
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
[infos addObject:info];
[self unlock];
return YES;
}
-(void)removeKVOinfo:(id)object keyPath:(NSString *)keyPath block:(void(^)()) block
{
[self lock];
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
__block CPKVOInfo *info;
[infos enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if([keyPath isEqualToString:[obj valueForKey:@"_keyPath"]]){
info = (CPKVOInfo *)obj;
*stop = YES;
}
}];
if (nil != info) {
[infos removeObject:info];
block();
if (0 == infos.count) {
[_objectInfosMap removeObjectForKey:object];
}
}
[self unlock];
}
-(void)removeAllObserve
{
if (_objectInfosMap) {
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
for (id object in objectInfoMaps) {
NSSet *infos = [objectInfoMaps objectForKey:object];
if(nil==infos || infos.count==0) continue;
[infos enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
CPKVOInfo *info = (CPKVOInfo *)obj;
[object removeObserver:self forKeyPath:[info valueForKey:@"_keyPath"]];
}];
}
[_objectInfosMap removeAllObjects];
}
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context{
NSLog(@"KVOProxy - observeValueForKeyPath :%@",change);
__block CPKVOInfo *info ;
{
[self lock];
NSSet *infos = [_objectInfosMap objectForKey:object];
[infos enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if([keyPath isEqualToString:[obj valueForKey:@"_keyPath"]]){
info = (CPKVOInfo *)obj;
*stop = YES;
}
}];
[self unlock];
}
if (nil != info) {
[object observeValueForKeyPath:keyPath ofObject:object change:change context:(__bridge void * _Nullable)([info valueForKey:@"_context"])];
}
}
-(void)lock
{
pthread_mutex_lock(&_mutex);
}
-(void)unlock
{
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
[self removeAllObserve];
pthread_mutex_destroy(&_mutex);
NSLog(@"KVOProxy dealloc");
}
@end
//-----------------------------------------------------------------------------------------------------------------------------
// CPKVOInfo
@implementation CPKVOInfo{
@public
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
CPKVONotificationBlock _block;
}
- (instancetype)initWithKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(nullable CPKVONotificationBlock)block
action:(nullable SEL)action
context:(nullable void *)context
{
self = [super init];
if (nil != self) {
_block = [block copy];
_keyPath = [keyPath copy];
_options = options;
_action = action;
_context = context;
}
return self;
}
- (instancetype)initWithKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
return [self initWithKeyPath:keyPath options:options block:NULL action:NULL context:context];
}
@end
使用
[NSObject openCP];
//key 为空
NSString *t = nil;
NSDictionary *dic = @{t:@"Test"};
// value 为空
NSString *t = nil;
NSDictionary *dic = @{@"Test":t};