最近写了一个小小框架吧,当然,也可以说是小功能吧,随着知识面的增加,我发现自己未知的东西越来越多,因此也不敢说自己写的东西算上什么框架,在我印象里,框架好像都是大神才能写出来的东西,但是毕竟自己也花了一定的时间,希望能得到大家的指点批评。
这个小框架的作用是保证你注册的监听器对象能够被及时的被移除掉。
写这个东西主要有以下两大点原因:
1.避免项目崩溃(如下代码)
#import "Person.h"
@implementation Person
- (void)personReceiveNsnotification:(id)sender
{
NSLog(@"person收到信息了");
}
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person*p = [[Person alloc]init];
[[NSNotificationCenter defaultCenter]addObserver:p selector:@selector(personReceiveNsnotification:) name:@"xixi" object:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[[NSNotificationCenter defaultCenter]postNotificationName:@"xixi" object:nil];
}
上述代码,如果你点击 -(void)touchesBegan方法在xcode6.4上是可以看到崩溃的,但是在xcode7.0之后并没什么反应。现在只能推测着分析一下
我们知道,我们每次在通知中心注册一个监听对像时,通知中心对我们的监听对象不会retain的,原因很简单,我们的通知中心是一个单例,整个应用只有一个,因此如果要是对我们的监听对象强引用的话,会造成我们的监听器对象很难得到释放,试想一下,如果我们在很多控制器中注册过通知,而且很多监听对象又是控制器本身的话,那么当我们不需要这些控制器的时候,仍然不能得到释放,那么我们的内存会变成一个什么样子呢?所以通知中心对我们注册的监听对象不会产生强引用。不会强引用就意味着我们必须时刻注意我们的监听对象是否还存在,还活着,那么如果我们的observer对象release过后而我们又忘记在通知中心移除我们注册过的observer的话,程序就会出现崩溃,从调试结果上来看,我们能看到一段熟悉的报错: -[Person personReceiveNsnotification:]: message sent to deallocated instance 0x7f85a0dde470
没错,是僵尸对象,从这段代码中我们可以推测,当我们的observer被释放后,其所在的内存区域就不能用了,我们p指针此时并未被清空,因此就会出现我们熟悉的僵尸对象。至少这种现象在xcode6.4时还是如此,但是在xcode7.0之后没这种错误不能复现了,个人猜测可能是在xcode7.0后苹果对注册过的监听器对象进行了处理,可能进行了指针的清空如 p = nil,但也只是可能,而且苹果官网也未给出明确说明,因此确保万无一失,我们必须确保监听器对象销毁之前移除监听器对象
2.避免重复注册
有时我们在同一个界面注册过一个通知,但是我们的同事或者我们自己在写程序的时候由于各种原因而不知道这个通知注册过了,又去注册了一遍,那么当我们发送该通知时就会连续调两次回调函数,这样的结果看起来就会很怪异
下面主要说一下我的写作思路吧,主要分为三大块:
1.NSNotificationCenter (XM):是一个NSNotificationCenter的分类,在这个类里面我提供了两个接口,如下所示:
#import "NSNotificationCenter+XM.h"
#import "XMNotificationCenter.h"
#import <objc/runtime.h>
@implementation NSNotificationCenter (XM)
/** 默认情况下是等待对象销毁前移除 */
- (void)xm_addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject
{
[self xm_addObserver:observer selector:aSelector name:aName object:anObject isRightNowRemove:NO];
}
/** 接受到通知后是否立即移除监听器 */
- (void)xm_addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject isRightNowRemove:(BOOL)isRightNowRemove
{
[[XMNotificationCenter share]builtWithNotificationCenter:observer selector:aSelector name:aName object:anObject isRightNowRemove:isRightNowRemove];
}
前者是默认的做法,我们无需关心什么时候移除监听器对象,该监听器对象会在销毁前(dealloc)前被自动移除,后者多了一个BOOL值,如果设置为NO就跟前者默认方法一样,如果设置为YES,则会在接到通知时立即移除监听器对象
2.XMNotificationCenter:
代码块
- (instancetype)init{
if (self = [super init]) {
//记录XMDealloc的IMP
_nsobjectXMDeallocImp = class_getMethodImplementation([NSObject class], NSSelectorFromString(@"XMDealloc"));
//记录receiveNotificationResult:的IMP
_receiveNotifacationImp = class_getMethodImplementation([NSObject class], NSSelectorFromString(@"receiveNotificationResult:"));
//数组内存储模型,每个模型记录一条通知相关信息
_implentationArray = [[NSMutableArray alloc]init];
}
return self;
}
//公共接口
- (void)builtWithNotificationCenter:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject isRightNowRemove:(BOOL)isRightNowRemove
{
if (!observer) return;
self.aName = aName;
self.anObject = anObject;
//1 记录1条通知相关信息
//监听器对象
[self.notifacationInfoDic setObject:[observer description] forKey:obseverKey];
//通知名字
if (aName) {
[self.notifacationInfoDic setObject:aName forKey:notifacationName];
}else{
[self.notifacationInfoDic setObject:@"" forKey:notifacationName];
}
//通知对象
if (anObject) {
[self.notifacationInfoDic setObject:anObject forKey:notifacationObjectKey];
}else{
[self.notifacationInfoDic setObject:@"" forKey:notifacationObjectKey];
}
if (aSelector) {
[self.notifacationInfoDic setObject:NSStringFromSelector(aSelector) forKey:notifacationSelctor];
}else{
[self.notifacationInfoDic setObject:@"" forKey:notifacationSelctor];
}
//2 处理拦截事件
[self exchangeImplementationWithNotificationCenterWithObserver:observer andIsRightNowRemove:isRightNowRemove];
}
这是一个单例,只要我们通过xm_add…方法注册过的所有监听器对象都会在该类内部进行相应的处理(核心代码区,详情请看demo:https://github.com/DreamOfXM/XM_NSNotification.git),如记录注册过的监听器对象及通知相关信息,防止下次同一通知的重复注册,利用oc运行时的特性,我们可以截获每个监听对象类的dealloc方法的实现指针(即IMP),并用一个模型保存,同时用一个全局的IMP指针记录NSObject(dealloc)中的XMDealloc的方法实现(这步用到下面要介绍的方法)
3.NSObject (dealloc):
代码块
//拦截dealloc回调
- (void)XMDealloc
{
NSLog(@"NSObject (dealloc)come here =====%@",self);
//调用一个block,然后告诉要移除的监听器对象
[XMNotificationCenter share].complection(self);
NSMutableArray* impArray = [[XMNotificationCenter share].implentationArray mutableCopy];
//记录进来的类
NSString*classStr = nil;
IMP imp = nil;
//数组中的监听器对象有多少个是同类,如Person
int num = 0;
NSString*obseverStr = nil;
for (XMImplenmentation*imp in impArray) {
obseverStr = [[XMNotificationCenter share] observerClassSubstringWith:imp.objectStr];
if ([self isKindOfClass:NSClassFromString(obseverStr)]) {
num++;
}
}
for (XMImplenmentation*implentation in impArray) {
//记录进来的类
classStr = NSStringFromClass([self class]);
if ([implentation.objectStr isEqualToString: [self description]]) {
//每个监听器对象的dealloc的IMP
imp = implentation.objectDeallocImp;
Method method = class_getInstanceMethod([NSObject class], NSSelectorFromString(@"XMDealloc"));
method_setImplementation(method, imp);
//获得该调用者的类,取出对方的dealloc实现,设置XMDealloc的实现为调用者dealloc的实现,然后调用XMDealloc即可
[self XMDealloc];
//如果该类型的对象在数组中=1个,把该类的dealloc实现归位
if(num == 1){
Class c = NSClassFromString(classStr);
Method method2 = class_getInstanceMethod(c, NSSelectorFromString(@"dealloc"));
method_setImplementation(method2, imp);
}
//对象释放完毕后必须从数组中移除
[[XMNotificationCenter share].implentationArray removeObject:implentation];
break;
}
}
}
//拦截通知回调(收到通知立即移除监听器对象的情况)
- (void)receiveNotificationResult:(id)sender
{
NSNotification*notification = (NSNotification*)sender;
NSString*name = notification.name;
id object = notification.object;
NSMutableDictionary*infoDic = [NSMutableDictionary new];
NSLog(@"%@===receiveNotificationResult",name);
if (!infoDic||!name) return;
//1通过字典中的通知名字和监听器对象定位移除
XMNotificationCenter*center = [XMNotificationCenter share];
NSMutableArray*infos = center.dic[keyInfo];
NSMutableArray*infosCopy = [infos mutableCopy];
//耗时操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSMutableDictionary*info in infosCopy) {
BOOL isSameObserver = [[self description]isEqualToString:info[obseverKey]];
if ([info[notifacationName] isEqualToString:name]&&isSameObserver) {
//数组中移除
[infos removeObject:info];
// 注销监听器对象
[[NSNotificationCenter defaultCenter]removeObserver:self name:name object:object];
}
}
NSMutableArray* impArray = [[XMNotificationCenter share].implentationArray mutableCopy];
//记录进来的类
NSString*classStr = nil;
IMP imp = nil;
for (XMImplenmentation*implentation in impArray) {
//记录进来的类
classStr = NSStringFromClass([self class]);
//同一个对象
if ([implentation.objectStr isEqualToString: [self description]]&&[implentation.notificationName isEqualToString:name] ) {
NSLog(@"object == %@ ----- self == %@",implentation.objectStr,[self description]);
imp = implentation.receiveNotifacationImp;
Method method = class_getInstanceMethod([NSObject class], NSSelectorFromString(@"receiveNotificationResult:"));
method_setImplementation(method, imp);
//2 调用selector方法
[self receiveNotificationResult:sender];
//把该类的收通知方法的实现归位
Class c = NSClassFromString(classStr);
Method method2 = class_getInstanceMethod(c, NSSelectorFromString(@"receiveNotificationOfPerson:"));
method_setImplementation(method2, imp);
//对象释放完毕后必须从数组中移除,数组中移除
[[XMNotificationCenter share].implentationArray removeObject:implentation];
break;
}
}
});
}
这个类中主要实现了两个方法,一个是- (void)XMDealloc,另一个就是- (void)receiveNotificationResult:(id)sender,前者主要是为了拦截所注册监听器对象销毁的方法,并在其销毁前做一些事情,如取消监听器对象,同时将- (void)XMDealloc的原有的实现复位,将监听器对象类的-(void)deallco复位,后者主要为了拦截通知回调的方法,在通知回调执行之前做一些事情,同样是移除监听器对象等,然后将相应的方法实现复位
以上就是对我写的小助手的简要介绍,里面若有什么错误或不足的地方请大家给予批评和指正,随时恭候大家批评。