讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(中)- IOS不为人知的Bug
前言:
话说昨晚还是前晚,写了一篇:讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(上)
文章写到最后时,多了很多莫名奇妙的问题!!!
为了解决了这些莫名奇妙的问题,我又战斗了24小时〜〜〜
然后终于解决了问题,原来是IOS的隐藏性Bug,只想恨恨的说一声fuck~~~
故事起源:
故事是这样的,为了处理内存释放的问题,正常人的思维,都是给对象的dealloc增加日志输出。
于是,UIView、UIViewController和两个Sagit定义的基类STView、STController,自然而然就加上了这么一行代码:
- -(void)dealloc{
- NSLog(@"UIView relase -> %@", [self class]);
- }
然后就通过输出的日志,观察该方法被执行与否,来确认对象是否正常被释放。
在各种强引用、弱引用、循环引用、先强后弱引用的坑里跳来跳去后,终于祭出了完美的杀招,来处理这些问题:
- -(id)key:(NSString *)key
- {
- id value=[self.keyValue get:key];
- if(value==nil)
- {
- value=[self.keyValueWeak get:key];
- }
- return value;
- }
- -(UIView*)key:(NSString *)key valueWeak:(id)value
- {
- [self.keyValueWeak set:key value:value];
- return self;
- }
- -(UIView*)key:(NSString *)key value:(id)value
- {
- [self.keyValue set:key value:value];
- return self;
- }
- -(NSMapTable*)keyValueWeak
- {
- NSMapTable *kv=[self.keyValue get:@"keyValueWeak"];
- if(kv==nil)
- {
- kv=[NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableWeakMemory];
- [self.keyValue set:@"keyValueWeak" value:kv];
- }
- return kv;
- // NSMapTable *kv= (NSMapTable*)objc_getAssociatedObject(self, &keyValueWeakChar);
- // if(kv==nil)
- // {
- // kv=[NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableWeakMemory];
- // objc_setAssociatedObject(self, &keyValueWeakChar, kv,OBJC_ASSOCIATION_RETAIN);
- // }
- // return kv;
- }
- -(NSMutableDictionary<NSString*,id>*)keyValue
- {
- NSMutableDictionary<NSString*,id> *kv= (NSMutableDictionary<NSString*,id>*)objc_getAssociatedObject(self, &keyValueChar);
- if(kv==nil)
- {
- kv=[NSMutableDictionary<NSString*,id> new];
- objc_setAssociatedObject(self, &keyValueChar, kv,OBJC_ASSOCIATION_RETAIN);
- }
- return kv;
- }
通过在强引用的Dictionary里,保存一个弱引用NSTableMap,来存档一些需要弱引用的对象,比如View或Controller等对象。
正当我在思索上面的解法的时候:另一个要命的导航栏Crash问题也出现了,而且在以下三种情形都会Crash掉:
导航栏命案一:回退即Crash
一开始是通过导航栏回退看日志输出,结果却动不动给我来这个:
关键还什么全局断点、僵使对象等方法都无效,只能靠猜〜〜〜〜〜
处理这个问题呢,还好,我是把代码折半注释,最后定位到:
圈起来的代码,是为每一个UI,都增加两个属性,前一个UI和后一个UI。
解决也很简单,用弱引用存档就好了(这个时候,强弱引用的问题已经被我完美解决了):
- // Name
- - (UIView*)preView{
- return [self key:@"preView"];
- }
- - (UIView*)preView:(UIView*)view
- {
- return [self key:@"preView" valueWeak:view];
- }
- - (UIView*)nextView{
- return [self key:@"nextView"];
- }
- - (UIView*)nextView:(UIView*)view
- {
- return [self key:@"nextView" valueWeak:view];
- }
导航栏命案2:回退两次即Crash
上一次是引用问题,这一次呢?我X,错误的提示,还是和上图一样,一个main含数里让你猜〜〜〜〜〜
而且一级就正常,二级就挂给你看〜〜〜真不要脸。
然后,就是针对这种灵异事件,各种渡,发现全世界都没出现我这个问题〜〜〜〜这真神了去了。
然后又开始注释代码,神奇的发现,在弹回后退时,如果把上一个状态栏给重新设置一遍,即不会出现Crash现象。
所以,我是这么思考导航栏问题的:
- : 一个navigationCtroller只有一个navigationBar。
- : 每一个viewController,都会复写前一个navigationBar。
- : 所以,到下一层时,UINavigationBar指向最后一个Controller
- : 当回退时,最后一个Controller被释放后,navigationBar没有被重绘的话,事件指向就会出问题。
然后又开始着手保存当前状态,然后后退时还原状态这条路。
由于后退是自定义事件,所以可以在事件里加代码还原,但是如果是滑动返回呢?
千身万苦之后,找到:shouldPopItem,在这里可以做点事情:
- @implementation UINavigationController (ST)
- #pragma mark NavigationBar 的协议,这里触发
- // fuck shouldPopItem 方法存在时,只会触发导航栏后退,界面视图却不后退。
- - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item // same as push methods
- {
- //重设上一个Controller的导航(不然在二次Push后再Pop会Crash)
- NSInteger count=self.viewControllers.count;
- if(count>)//发现这里返回的viewControllers,已经是移掉了当前的Controller后剩下的。
- {
- UIViewController *preController=self.viewControllers[count-];//获取上一个控制器
- if([preController needNavBar])
- {
- [preController reSetNav:self];
- }
- }
- return YES;
- }
- //返回到当前页面
- - (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
- {
- // if(navigationBar!=nil && [navigationBar.lastSubView isKindOfClass:[UIButton class]])
- // {
- // // [navigationBar.lastSubView height:0];//取消自定义复盖的UIButton
- // }
- NSInteger count=self.viewControllers.count;
- if(count>)
- {
- UIViewController *current=self.viewControllers[count-];
- self.navigationBar.hidden=![current needNavBar];
- // if(!self.navigationBar.isHidden)
- // {
- // [current reSetNav:self];
- // }
- if(self.tabBarController!=nil)
- {
- self.tabBarController.tabBar.hidden=![current needTabBar];
- }
- }
- }
- -(void)dealloc
- {
- NSLog(@"UINavigationController relase -> %@", [self class]);
- }
- @end
改完之后,发现一切又正常了,然后,又迎来了导航栏的第3波bug。
(PS:shouldPopItem 这个方法,是第二个超级Bug坑)
导航栏命案3:第一个页面存在自定义按钮,则Crash
以下这个界面,右上角一个小相机事件。
因为解决问题2的代码中,并没有还原第一个页的导航栏,所以事故还是发生了。
然后又思考,这默认的第一个页面状态怎么保存?
直接保存整个UIButtonBar或者整个NavigationBar,再还原,竟然不行!!!
简直欲哭无泪,各种渡,仍无果,为啥全世界,都没这种问题呢?
难道全世界写代码都是内存不释放,所以木有这个问题?
这么基础的问题,不是到处都会遇到的么?
然后到了隔壁小伙的电脑上,把这种神奇的故事,在他电脑上重新演示了一遍!!!
果然还是和我电脑上的一样!!!
果然这问题还是很常见,为啥呢,渡不出果呢?
不知道为什么,作为一名新手,他跑去把这段代码给注释了:
- //fuck dealloc 方法存在时,会影响导航的后退事件Crash,以下两种情况:1:当前UI有自定义导航按钮时;2:Push两层再回退。
- //-(void)dealloc
- //{
- //// if(self.gestureRecognizers.count>0)
- //// {
- //// if(self.gestureRecognizers!=nil)
- //// {
- //// for (NSInteger i=self.gestureRecognizers.count-1; i>=0; i--)
- //// {
- //// [self removeGestureRecognizer:self.gestureRecognizers[i]];
- //// }
- //// }
- //// }
- // //[self.keyValueWeak removeAllObjects];
- // // [self.keyValue removeAllObjects];
- //
- // NSLog(@"UIView relase -> %@ name:%@", [self class],self.name);
- //}
然后,一切正常了〜〜〜〜咦!!!!
是dealloc里的代码有问题引发的?
然后内部代码全注释掉了,问题还是有!!!
把dealloc整个注释掉,又正常了!!!
真相:IOS的Bug,不能扩展UIView的dealloc方法
到了这里,真相终于出来了,只要扩展了UIView的dealloc方法,导航栏就敢死给你看!!!
我了个去,为了查内存释放,所以要写dealloc方法,但写了这个方法,就引出来这么多神奇的灵异事件。
无语啊,这是为了让我们不要管内存释放问题,故意设下的坑么。
IOS除了这个Bug,还有那个shouldPopItem事件,只要这个事件存在,默认return YES,就只返回导航头,不会返回界面,也是个坑!!!
效果是这样的:
总结:
真是人算不如天算,遇到这种坑,也是全宇宙第一人了。
这里给大伙提供一个坑队友的的新方法:
找个地方,对UIView扩展一个dealloc空方法。
然后就说这Bug很神奇,让他帮忙看看,包对方头大两尺三〜〜〜
本来内存泄漏的问题,到此篇就结束的,不过还有一个任性的问题想解决:
希望在block里,任性的写self,也不会造成内存汇漏问题。
又经过48小时的奋战,终于解决了。
同时又发现另一个IOS的坑,好吧,只好把此文改成中,准备再来一篇下。
讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(中)- IOS不为人知的Bug的更多相关文章
- 讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(下)- block中任性用self
前言: 在处理完框架内存泄漏的问题后,见上篇:讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(中)- IOS不为人知的Bug 发现业务代码有一个地方的内存没释放,原因很也简单: ...
- 讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(上)
前言: 好久没写文章了,最近先是重构IT恋.又重写IT恋中. Sagit框架也不断的更新,调整,现在感觉已完美了了相当的多. 今天不写教程,先简单分享一下技术内容. 1:见Block必有:#defin ...
- [转] weak_ptr解决shared_ptr环状引用所引起的内存泄漏
http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引 ...
- weak_ptr解决shared_ptr环状引用所引起的内存泄漏[转]
转载:http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理 ...
- 使用Xcode和Instruments调试解决iOS内存泄漏
尽管iOS 5.0加入版本号之后ARC机制,由于相互引用关系是复杂的.内存泄漏可能仍然存在.于是,懂原理是非常重要的. 这里讲述在没有ARC的情况下,怎样使用Instruments来查找程序中的内存泄 ...
- android中handler使用应该注意的问题(解决由handler引起的OOM内存泄漏)
最近,在项目过程中频繁的使用handler处理一些ui线程上的操作,以及使用handler的postdealy.然而使用过后却不对handler进行处理,进而产生了内存溢出现象,通过google,发现 ...
- .net 循环引用是否会造成内存泄漏
一直想做这么一个测试,人和手的测试.类型"人"有一个属性"手",需要"手"也可以读取"人"的数据.则"手&qu ...
- 解决因为手机设置字体大小导致h5页面在webview中变形的BUG
首先,我们做了一个H5页面,在各种手机浏览器中打开都没问题.我们采用了rem单位进行布局,通过JS来动态计算网页的视窗宽度,动态设置html的font-size,一切都比较完美. 这时候,你自信满满的 ...
- 一步步调试解决iOS内存泄漏
虽然iOS 5.0版本之后加入了ARC机制,由于相互引用关系比较复杂时,内存泄露还是可能存在.所以了解原理很重要. 这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄 ...
随机推荐
- CenterOS 7 基础命令学习
CentOS 7 命令 网络配置 nmcli(NetworkManageCommandLineInterface)查看网卡 nmtui(NetworkManageTextUserInterface)网 ...
- 深入理解javascript函数进阶系列第二篇——函数柯里化
前面的话 函数柯里化currying的概念最早由俄国数学家Moses Schönfinkel发明,而后由著名的数理逻辑学家Haskell Curry将其丰富和发展,currying由此得名.本文将详细 ...
- 利用spring,实现package下的类扫描
项目中需要用到包扫描的情况是很多的,一般是在项目初始化的时候,根据一些条件来对某个package下的类进行特殊处理.现在想实现的功能是,在一个filter或interceptor初始化的时候,扫描指定 ...
- 16进制到byte转换
我们经常会看到这样的语法 (byte) 0xAD 0xAD实际是个16进制,转换成二进制为:10101101,转换成10进制是:173,它是个正数 10101101只是int的简写,int由4个byt ...
- 为并发而生的 ConcurrentHashMap(Java 8)
HashMap 是我们日常最常见的一种容器,它以键值对的形式完成对数据的存储,但众所周知,它在高并发的情境下是不安全的.尤其是在 jdk 1.8 之前,rehash 的过程中采用头插法转移结点,高并发 ...
- Abp扩展之【配置功能】
Abp的扩展功能非常强大,配合持久化可以很方便的配置系统.租户.用户的配置,关于ABP的配置请参考: http://www.cnblogs.com/farb/p/ABPSettingManagemen ...
- Java数据结构和算法(十)——二叉树
接下来我们将会介绍另外一种数据结构——树.二叉树是树这种数据结构的一员,后面我们还会介绍红黑树,2-3-4树等数据结构.那么为什么要使用树?它有什么优点? 前面我们介绍数组的数据结构,我们知道对于有序 ...
- Winform开发框架中工作流模块之申请单草稿处理
在我们开发工作流模块的时候,有时候填写申请单过程中,暂时不想提交审批,那么可以暂存为草稿,以供下次继续填写或者提交处理,那么这个草稿的功能是比较实用的,否则对于一些填写内容比较多的申请单,每次要重填写 ...
- Hello Docker
Docker: Build, Ship, and Run Any App, Anywhere 在任何地方构建.交付和运行任何应用 1. 引言 最近简单的学习了下Docker,本文先简要梳理下Docke ...
- Mybatis(基于SqlSessionTemplate的实现) + Spring 练习实战
mybatis学习篇:上次使用映射接口实现Mybatis,有不方便指出就是需要接口,且需要保证接口上不能存在其他的代理.这次通过SqlSessionTemplate基于模板类实现Mybatis,总的来 ...