场景
UITableViewCell B继承自 UITableViewCell A, UITableViewCell A 注册了名为A的通知,通知绑定的方法为 方法 A;UITableViewCell B 注册了名为B的通知,通知绑定的方法为 方法 B。
问题
点击进入UITableViewCell B中后返回,再进入到 UITableViewCell A中,触发通知A,此时会崩溃,崩溃在 UITableViewCell B 的方法B中,( 这里说一下,方法B和方法A是一样的)
分析原因
如图所示:UITableViewCell A和UITableViewCell B的关系和方法的调用关系大致如此,崩溃的原因是,由于 方法B和方法A是一样的,UITableViewCell B 继承与 UITableViewCell A,由于
UITableViewCell B在初始化的时候调用了 UITableViewCell A中的初始化方法,所以由于继承的机制,实际上 UITableViewCell B注册了两个通知:通知A和通知B。由于方法B和方法A是一样的,所以UITableViewCell B中的通知A调用方法A的时候,实际上就调用了方法B,(当子类的方法列表中有和父类的方法列表中的方法一样的情况下,会调用子类中的方法,而不调用父类中的方法,也就是重写),而实际上 UITableViewCell B 中的方法B设计上不是为 通知A服务的,其中调用的一些未知的数据,所有就出现了崩溃。
有一个问题:为什么从 UITableViewCell B中POP出后,UITableViewCell B没有被释放呢?,就是因为UITableViewCell B没有在页面被 POP后被释放掉,才会出现这样的 Crash,那么为什么没被释放呢
dealloc的不被调用的情况。
ARC下,控制器在被pop后移出栈后会被释放,但有些时候会发现控制器出栈的时候不会调用dealloc方法,系统可以帮我们释放该对象,及其包含的对象;但是却无法释放不属于该对象的一些东西,就造成了 对象的dealloc方法不被调用。而且重写该方法时不能显式调用[super dealloc],和继承中先加载父类再加载子类相反,注销时先注销子类之后再注销父类。因为系统会自动帮你调用父类的dealloc方法。
- 1.通知的观察者,或KVO的观察者
由于通知中心是系统的一个单例,你在注册通知的观察者时,实际上是在通知中心注册的,
这时,即使ARC下系统帮我们释放了对象,但是在通知中心的观察还是没有移除,那么当有
该通知时,依然会尝试调用该对象的接受通知的方法,这可能会导致一些问题.
- 2.对象强委托
对于其他的对象来把你当做委托 delegate时,并且是 强引用时,即时你自身被释放,但是引用你的对象依然还在,这时需要在引用你的对象移除该delegate
- 3.一些其它的资源,类似地图页面。C语言写的一些好内存的类文件,
- 4.控制器中NSTimer没有被销毁
当viewController中存在NSTimer时,需要特别注意,当调用
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES]
时,因为 target:self ,也就是引用了当前viewController,导致控制器的引用计数加1,如果没有将这个NSTimer 销毁,它将一直保留该viewController,无法释放,也就不会调用dealloc方法。所以,需要在viewWillDisappear之前需要把控制器用到的NSTimer销毁.
[timer invalidate]; // 销毁
timertimer = nil; // 置nil
- 5.viewController中block的循环引用在ARC下,
block会把它里面的所有对象强引用,包括当前控制器self,因此有可能会出现循环引用的问题。比如viewController中有个block属性,在block中又强引用了self或者其他成员变量,那么这个viewController与自己的block属性就形成循环引用,导致viewController无法释放。
很显然,UITableViewCell B不被释放是因为在初始化的时候注册的通知没有移除,也没有机会移除了,造成的每创建一个UITableViewCell B 都不会被释放,而是一直在内存中。
验证猜想
我们修改 方法B 使方法A和 方法B不一样。在方法A中打印当前类名,然后多次 push进入UITableViewCell B中后再次进入 UITableViewCell A中,触发通知A,调用方法A会出现下面的情况:
跟我们猜想的一样,由于很多不同的UITableViewCell B 被创建,(都注册了俩通知,由于继承的关系,虽然UITableViewCell B 中没有写 UITableViewCell A的一些方法,但是UITableViewCell B的方法列表中还是会有 那些方法,只是省去了书写而已,书写在了父类文件中)而且没有被销毁,所以当UITableViewCell A 中的通知A被触发时,同样的 UITableViewCell B 中的通知A 也被触发,由于UITableViewCell B 中没有方法A,于是就去执行了 父类(UITableViewCell A)中的方法A,于是就出现了 上图那样的场景。
解决办法
单纯避免崩溃的话,在UITableViewCell B中第一个 空的方法A 即可,或者把方法B 和 方法A 修改为不同即可。
可是这样,UITableViewCell A中的方法A依然会被执行很多次。
#最后一个参数是表示会对哪个发送者对象发出的事件作出响应,nil 时表示接受所有发送者的事件。,
#所以我们这里把 object:self ,即可只接受自己触发的通知,而不会接受到其它 UITableViewCell触发的通知了
#添加之前先移除所有监听,可以解决多次注册相同监听的问题。
[[NSNotificationCenter defaultCenter]removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(goodtongzhi:) name:@"goodPopAlert" object:self];
- (void)goodtongzhi :(NSNotification *)sender
{
#如果不是自己触发的通知就不处理。
if (![sender.object isEqual:self]) {
return;
}
NSDictionary *dataDic = sender.userInfo;
NSLog(@"你好我是 %@",self.class);
if ([dataDic[@"index"] integerValue] == self.tag) {
goods =dataDic[@"data"];
rightTextLablel[0].text = goods.name;
rightTextLablel[2].text = goods.danwei;
baozhiqiTexttF.text = goods.baizhiqi;
rightTextLablel[4].text = goods.baizhiqidanwei;
}
}
小结
虽然我们解决了崩溃,也解决了方法的多次调用,看似达到了要求,其实在 UITableViewCell中注册通知是很不好的方法,这样会造成很多 UITableViewCell 无法被释放,一直在内存中,使用 多层次的Block回调,一样可以达到通知的效果,而且不会造成UITableViewCell无法被释放的问题,本文详细分析这个问题,旨在希望大家写程序时注意这个问题。