iOS中的crash防护(四)NSNotificationCenter指定线程接收通知
来源:互联网 发布:linux线程退出函数 编辑:程序博客网 时间:2024/06/14 21:01
这篇文章本来是要写NSNotificationCenter造成的 crash的防护方案的,但是分析总结出来发现有两点:(1) NSNotificationCenter添加或者移除通知的时候不存在像KVO出现的重复添加或者重复移除的情况(苹果内部已经进行了处理,不需要开发者进行处理了)。(2) NSNotificationCenter造成的crash主要是由于开发者忘记移除通知造成的,这是开发者不好的开发习惯造成的,不在JKCrashProtect的优化方向(引导用户养成良好的编码习惯),而且这个问题在ios9以后已经被苹果优化掉了。
但是使用过NSNotificationCenter的小伙伴都应该知道,通常情况下,是无法确定接收通知的的函数在指定的线程内执行的。在某些时候需要在接收通知函数内刷新UI,而如果此时我们无法保证刷新代码是在主线程内执行,那么就有可能会产生crash。我们虽然可可以主动的将线程切换到主线程来避免crash,但是这种做法费时费力,还容易遗漏。虽然后来苹果推出了新的API如下:
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0); // The return value is retained by the system, and should be held onto by the caller in // order to remove the observer with removeObserver: later, to stop observation.
但是这个方法是通过block的形式来实现的,无法发送通知,和接收通知相分离,无法满足在不同页面间实现指定线程接收通知的需求。
为了实现NSNotificationCenter指定线程接收通知,而且满足在不同页面间实现指定线程接收通知的需求。我这边写了一个NSNotificationCenter的catergory,新定义了几个方法。
- (void)postNotification:(nonnull NSNotification *)notification handleThread:(nullable NSThread *)thread;- (void)postNotificationName:(nonnull NSNotificationName)aName object:(nullable id)anObject handleThread:(nullable NSThread *)thread;- (void)postNotificationName:(nonnull NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo handleThread:(nullable NSThread *)thread;
通过发送通知的时候指定接收通知的线程,我这边主要是将handleThread,保存到了notification的userInfo中,具体实现如下:
- (void)postNotification:(nonnull NSNotification *)notification handleThread:(nullable NSThread *)thread{ NSNotificationName aName =notification.name; NSDictionary *userInfo = notification.userInfo; id object = notification.object; if (userInfo) { NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:userInfo]; [dic setObject:thread?:@"" forKey:@"thread"]; userInfo = [dic copy]; }else{ userInfo =@{@"thread":thread?:@""}; } NSNotification *notif = [[NSNotification alloc] initWithName:aName object:object userInfo:userInfo]; [[NSNotificationCenter defaultCenter] postNotification:notif];}- (void)postNotificationName:(nonnull NSNotificationName)aName object:(nullable id)anObject handleThread:(nullable NSThread *)thread{ NSDictionary *userInfo=nil; [self postNotificationName:aName object:anObject userInfo:userInfo handleThread:thread];}- (void)postNotificationName:(nonnull NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo handleThread:(nullable NSThread *)thread{ if (aUserInfo) { NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:aUserInfo]; [dic setObject:thread?:@"" forKey:@"thread"]; aUserInfo = [dic copy]; }else{ aUserInfo =@{@"thread":thread?:@""}; } [[NSNotificationCenter defaultCenter] postNotificationName:aName object:anObject userInfo:aUserInfo];}
如何将将notification中的thread信息读取出来,并根据thread信息将接收通知的函数切换到指定的线程去执行呢,我这边主要method swizzle对- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject
这个方法进行了方法交换,实现代码如下:
+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [NSNotificationCenter defaultCenter].isAspected =YES; [self JKCrashProtectswizzleMethod:@selector(addObserver:selector:name:object:) withMethod:@selector(JKCrashProtectaddObserver:selector:name:object:) withClass:[NSNotificationCenter class]]; });}
然后在- (void)JKCrashProtectaddObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject
这个函数中我这边通过JKUBSAspects对接收通知的函数进行hook操作,并且在相关的block内进行线程切换的处理,具体实现如下:
- (void)JKCrashProtectaddObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject{ NSString *selectorStr = NSStringFromSelector(aSelector); NSString *className = NSStringFromClass([observer class]); if ([selectorStr hasPrefix:@"_"] || [className hasPrefix:@"_"] || [aName hasPrefix:@"UI"]) { [self JKCrashProtectaddObserver:observer selector:aSelector name:aName object:anObject]; return; } //过滤系统方法,如果是系统方法直接交换方法,不进行线程切换的处理 NSObject *target = (NSObject *)observer; target.isAspected = YES; [target aspect_hookSelector:aSelector withOptions:JKUBSAspectPositionInstead usingBlock:^(id<JKUBSAspectInfo> data){ NSArray *arguments = [data arguments]; NSNotification *notif =arguments[0]; NSThread *thread = notif.userInfo[@"thread"]; thread=[thread isKindOfClass:[NSThread class]]?thread:[NSThread currentThread]; NSInvocation *invocation = [data originalInvocation]; SEL selector =invocation.selector; id currentTarget = invocation.target; [currentTarget performSelector:selector onThread:thread withObject:notif waitUntilDone:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop run]; } error:nil]; [self JKCrashProtectaddObserver:observer selector:aSelector name:aName object:anObject];}
而且我这边做了处理,如果没有指定接收通知函数执行的线程,那么我这边默认是currenthread。
好了说了这么多,具体能不能用还要看测试是否过关,我的测试代码如下:
1)指定主线程接收通知
- (void)clicked{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ [[NSThread currentThread] setName:@"childthread"]; NSLog(@"thread name111 %@",[NSThread currentThread].name); // NSNotification *notif = [[NSNotification alloc] initWithName:@"printLog" object:nil userInfo:nil]; //[[NSNotificationCenter defaultCenter] postNotification:notif handleThread:nil]; //[[NSNotificationCenter defaultCenter] postNotificationName:@"printLog" object:@(123) handleThread:[NSThread mainThread]]; [[NSNotificationCenter defaultCenter] postNotificationName:@"printLog" object:@"123" userInfo:@{@"name":@"jack"} handleThread:[NSThread mainThread]]; });}- (void)printLog:(NSNotification *)notification{ NSLog(@"%@",notification); NSLog(@"thread name222 %@",[NSThread currentThread].name);}
运行结果如下:
2017-08-07 16:02:59.148 JKCrashProtect_Example[54276:8537577] thread name000 MainThread2017-08-07 16:03:00.436 JKCrashProtect_Example[54276:8537655] thread name111 childthread2017-08-07 16:03:00.438 JKCrashProtect_Example[54276:8537577] NSConcreteNotification 0x608000243660 {name = printLog; object = 123; userInfo = { name = jack; thread = "<NSThread: 0x608000065440>{number = 1, name = MainThread}";}}2017-08-07 16:03:00.438 JKCrashProtect_Example[54276:8537577] thread name222 MainThread2017-08-07 16:03:01.172 JKCrashProtect_Example[54276:8537656] thread name111 childthread2017-08-07 16:03:01.173 JKCrashProtect_Example[54276:8537577] NSConcreteNotification 0x600000250f80 {name = printLog; object = 123; userInfo = { name = jack; thread = "<NSThread: 0x608000065440>{number = 1, name = MainThread}";}}2017-08-07 16:03:01.173 KCrashProtect_Example[54276:8537577] thread name222 MainThread
大家可以看到thread name222 MainThread
接收通知的函数在主线程内执行。
2)指定子线程接收通知
- (void)clicked{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ [[NSThread currentThread] setName:@"childthread"]; NSLog(@"thread name111 %@",[NSThread currentThread].name); // NSNotification *notif = [[NSNotification alloc] initWithName:@"printLog" object:nil userInfo:nil]; //[[NSNotificationCenter defaultCenter] postNotification:notif handleThread:nil]; //[[NSNotificationCenter defaultCenter] postNotificationName:@"printLog" object:@(123) handleThread:[NSThread mainThread]]; [[NSNotificationCenter defaultCenter] postNotificationName:@"printLog" object:@"123" userInfo:@{@"name":@"jack"} handleThread:[NSThread currentThread]]; });}- (void)printLog:(NSNotification *)notification{ NSLog(@"%@",notification); NSLog(@"thread name222 %@",[NSThread currentThread].name);}
运行结果如下:
2017-08-07 16:06:36.265 JKCrashProtect_Example[54368:8546384] thread name111 childthread2017-08-07 16:06:36.266 JKCrashProtect_Example[54368:8546384] NSConcreteNotification 0x60800024ea30 {name = printLog; object = 123; userInfo = { name = jack; thread = "<NSThread: 0x600000074f00>{number = 3, name = childthread}";}}2017-08-07 16:06:36.267 JKCrashProtect_Example[54368:8546384] thread name222 childthread
大家可以看到我们接受通知的线程是在thread name222 childthread
内执行的。是我们指定的子线程。
3)未指定某个线程接收通知的,我们默认当前线程接收通知。
关键代码如下:
[[NSNotificationCenter defaultCenter] postNotificationName:@"printLog" object:@"123" userInfo:@{@"name":@"jack"} handleThread:nil];
具体结果就不贴出来了,大家感兴趣的话可以下载demo运行下试试。
demo下载地址
如果使用cocoapod的小伙伴也可以直接使用:
pod "JKCrashProtect"
导入到项目中进行验证。
- iOS中的crash防护(四)NSNotificationCenter指定线程接收通知
- iOS中的crash防护(二)KVC造成的crash
- iOS中的crash防护(三)KVO造成的crash
- NSNotificationCenter通知接收多次
- NSNotificationCenter通知接收多次
- 关于iOS中的通知中心(NSNotificationCenter)
- iOS系统NSNotificationCenter中的常用通知名称
- iOS--通知的使用(NSNotificationCenter )
- IOS 之 通知NSNotificationCenter
- IOS NSNotificationCenter 通知中心
- IOS NSNotificationCenter 通知中心
- iOS通知中心(NSNotificationCenter)
- iOS通知:NSNotificationCenter
- IOS NSNotificationCenter 通知使用方法
- iOS通知中心NSNotificationCenter
- iOS NSNotificationCenter 通知
- IOS通知中心(NSNotificationCenter)
- iOS Swift NSNotificationCenter 通知
- mysql查询表里的重复数据方法:
- Java读取文件
- FastJson--常用方法总结
- 创建型模式—建造者模式
- 本地存储封装-localStorage,sessionStorage,userData
- iOS中的crash防护(四)NSNotificationCenter指定线程接收通知
- MyBatis教程之一基本使用入门
- Jvm垃圾收集器和垃圾回收算法
- 关于json数据中包含json在低版本中出错的状况
- iOS 加载H5页面的时候添加一个菊花
- WebRTC实时通信系列教程7 使用Socket.IO搭建信令服务器交换信息
- 洛谷 P2619 奶牛工资 贪心
- ulimit -n永久生效
- 分布式哈希算法DHT