前言:

在处理完框架内存泄漏的问题后,见上篇:讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(中)- IOS不为人知的Bug

发现业务代码有一个地方的内存没释放,原因很也简单:

在block里用到了self,造成双向引用,然后就开始思考怎么处理这个问题。

常规则思维,就是改代码,block不要用到self,或只用self的弱引用。

只是框架这里特别,有一个特好用的系列,STLastXXX系列,是用宏定义的,而且该宏指向了self。

这么好用的STLastXXXX宏定义系列,我希望它可以不受限制的到处使用。

于是开始折腾之路:

折腾一:在代码中重新定义宏?

上面的代码,说白了就是用到了self,我们看一下宏定义:

通常的做法,遇到block,都伴随有WeakSelf这东东,像这样:

STWeakSelf的默认定义:

  1. //block块中用的引用
  2. #define STWeakSelf __weak typeof(self) selfWeak = self;__weak typeof(selfWeak) this = selfWeak;
  3. #define STWeakObj(o) __weak typeof(o) o##Weak = o;
  4. #define STStrongObj(o) __strong typeof(o) o = o##Weak;

说白了,宏定义就是编绎期的文字替换游戏,如果我在block里的第一行代码,重新定义sagit这个宏会怎样?

比如这样定义:

然后这么使用:

看来是我想多,编绎都过不去。

折腾二:将宏定义指向一个函数

比如这样定义:

  1. #define sagit [Sagit share].Layout

然后跑到Sagit这个类里定义一个UIView* Layout属性,比如这样:

然后,就是在各个基类(STController或STView)初始化时,将自身的self.baseView赋值给它。

PS:baseView是对UIView和UIViewController扩展的一个属性,都指向View。

比如:

看起来有点效果,不过,要用这种方式,还得思考的更全面:

  1. :架框中每个STView都是baseView
  2.  
  3. STView可以无限嵌套STView
  4.  
  5. :因此:在STView被初时化时,设置它为baseView,加载完后,如果STView有父的STView,交还控制权,(这里就涉及到嵌套的控制流程,如果各个子View是异步加载,那就悲催了)。
  6.  
  7. :没有继承自STView的情况呢,怎么拦截View的开始和结束呢?(Sagit已经慢慢弱化基类的功能,多数功能都是在原生的上做扩展,所以不用STView,很多功能也可以正常使用)

好吧,写这么多,就是说这个方法是可以的,但必须考虑的更仔细好多些!!!!

而且这个方法,只是避开了self,self仍然不允许被存在。

所以,这个方法,先暂放,看看有没有办法,打破self的循环引用先。

折腾三:思考如何打破block和self的双向引用?

block和self,互相的强引用,要打破它,总得有一方要示弱。

既然block中要允许self中存,就意味着block对self必然是强引用,辣么就只能思考,如果让self对block只能是弱引用了,或者没有引用!

先来看框架的一段代码:

被圈起来的代码,实现的下列图中圈起来的功能:

对于Sagit中,实现表格的代码是不是觉的很简单,不过教程还没写,这里提前泄漏了。

还是说重点,重点为UITableView中,扩展了一个属性AddCell的Block属性,见代码如下:

  1. @interface UITableView(ST)
  2.  
  3. #pragma mark 核心扩展
  4. typedef void(^AddTableCell)(UITableViewCell *cell,NSIndexPath *indexPath);
  5. typedef BOOL(^DelTableCell)(UITableViewCell *cell,NSIndexPath *indexPath);
  6. typedef void(^AfterTableReloadData)(UITableView *tableView);
  7. //!用于为Table追加每一行的Cell
  8. @property (nonatomic,copy) AddTableCell addCell;
  9. //!用于为Table移除行的Cell
  10. @property (nonatomic,copy) DelTableCell delCell;
  11. //!用于为Table reloadData 加载完数据后触发
  12. @property (nonatomic,copy) AfterTableReloadData afterReload;
  13. //!获取Table的数据源
  14. @property (nonatomic,strong) NSMutableArray<id> *source;
  15. //!设置Table的数据源
  16. -(UITableView*)source:(NSMutableArray<id> *)dataSource;

虽然定义了一个addCell属性,但是怎么持有,还得看getter和setter怎么实现:

  1. @implementation UITableView(ST)
  2.  
  3. #pragma mark 核心扩展
  4.  
  5. -(AddTableCell)addCell
  6. {
  7. //从第三方持有返回
  8. }
  9. -(void)setAddCell:(AddTableCell)addCell
  10. {
  11. if(addCell!=nil)
  12. {
  13. //第三方持有addCell
  14. }
  15. else
  16. {
  17.  
  18. }
  19. }

如果这里,把存档addCell这个block存到和UITableView无关的地方去了?

用一个第三方持有block,只要这个第三方不和和self发生直接关系,那么应该就不会有问题。

引用关系就变成:

  1. 第三方:强引用=》block
  2.  
  3. block:强引用=》self

这里的释放关系,第三方活着,就不会释放block,block活着,就不会释放self。

所以结论:还是不释放。

也就是说,一顿代码写下来,虽然解除了关系,但还是没释放。

如果第三方对block是弱引用呢?

为了这个实验,我新建了一个项目,然后在这个项目上,试了整整24小时:

实验过程没有得到的想要的结果,但了解block的原理,和认清自己的逻辑误区。

实验的得到的知识后面再分享,这里先继续:

  1. 由于这里addCell,必须始终存活,因为你不知道什么时候,可能又要UITableViewreloadData一下。
  2.  
  3. 所以,addCell这个block,必须被强引用,而且生命周期得和UITableView一致。
  4.  
  5. 所以,要打破这个核心,还是得有第三方行为事件来触发破除关系,故事就转变成为:由self去移除第三方。
  6.  
  7. 如果一定义要由某个事件来触发解除关系,那么第三方也没存在的必要了。
  8.  
  9. 因为正常AB互相引用无解,是指他们互相无解,但只要有第三者存在,对其中一个置nil就解了。

最后的最后,框架的代码是这样的:

  1. -(AddTableCell)addCell
  2. {
  3. return [self key:@"addCell"];
  4. }
  5. -(void)setAddCell:(AddTableCell)addCell
  6. {
  7. if(addCell!=nil)
  8. {
  9. addCell=[addCell copy];
  10. [self key:@"addCell" value:addCell];
  11. }
  12. else
  13. {
  14. [self.keyValue remove:@"addCell"];
  15. }
  16. }

仍然是用强引用来存档block,这里有一个注意事项:

如果你想将一个block持久化,先copy一下,不然你会死的很惨。

好吧,让它们互相强引用吧,剩下的事,就是谁来当这个第三方,以及怎么解除这层关系!!!

于是,到导航栏后退事件中,拦截,并做销毁工作:

  1. @implementation UINavigationController (ST)
  2.  
  3. #pragma mark NavigationBar 的协议,这里触发
  4. // fuck shouldPopItem 方法存在时,只会触发导航栏后退,界面视图却不后退。
  5. //- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item // same as push methods
  6. //{
  7. //// //重设上一个Controller的导航(不然在二次Push后再Pop会Crash)
  8. //// NSInteger count=self.viewControllers.count;
  9. //// if(count>0)//发现这里返回的viewControllers,已经是移掉了当前的Controller后剩下的。
  10. //// {
  11. //// UIViewController *preController=self.viewControllers[count-1];//获取上一个控制器
  12. //// if([preController needNavBar])
  13. //// {
  14. //// [preController reSetNav:self];
  15. //// }
  16. //// }
  17. ////
  18. // return YES;
  19. //}
  20. //返回到当前页面
  21. - (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
  22. {
  23. // if(navigationBar!=nil && [navigationBar.lastSubView isKindOfClass:[UIButton class]])
  24. // {
  25. // // [navigationBar.lastSubView height:0];//取消自定义复盖的UIButton
  26. // }
  27. NSInteger count=self.viewControllers.count;
  28. if(count>)
  29. {
  30. UIViewController *current=self.viewControllers[count-];
  31. self.navigationBar.hidden=![current needNavBar];
  32. if(self.tabBarController!=nil)
  33. {
  34. self.tabBarController.tabBar.hidden=![current needTabBar];
  35. }
  36. //检测上一个控制器有没有释放
  37. UIViewController *nextController=current.nextController;
  38. if(nextController!=nil)
  39. {
  40. [nextController dispose];
  41. nextController=nil;
  42. }
  43. }
  44. }
  45. -(void)dealloc
  46. {
  47. NSLog(@"UINavigationController relase -> %@", [self class]);
  48. }

Sagit框架为:每个view和controller扩展了dispose方法,里面清掉键值对,等于把block置为nil,解除了关系。

除了导航后退,还需要拦截多一个事件,就是presentViewController的事件跳转时,也需要检测并销毁。

做好这两步之后,以后就可以轻松的在block里写self了,爱引用就引用了,反正故事的结尾,都有一个第三者来收尾

而且强引用有一个好处:

  1. :再也用不上WeakSelf这种定义了。
  2.  
  3. :由于是强引用,就不用去管:里面还要套个StrongSelf,去避开多线程时,self可能被移除时带来的闪退问题。

最后:吐槽一个IOS的另一个坑,又是神秘的dealloc方法:

上一篇文章,讲到,如果对UIView扩展了dealloc这方法,引发的命案是:

导航栏:二次后退就闪退。

这一篇,又被我发现,如果对UIViewController扩展dealloc这个方法,引发的命案:

UIAlertView:当alertViewStyle设置为带文本框时就闪退。

给大伙上一个图,先把dealloc打开:

然后运行的效果:

坑吧,好在有上一次的经验,赶紧新建了一个项目,然后用代码排除法,最终还是排到dealloc这里来。

看到这文章的同学,你们又可以去忽悠同事了。

总结:

整体折腾完内存释放问题后,Sagit框架也高效了很多,也许是错觉。

IT连的创业的也在继续,欢迎大伙持续关注,谢谢!

讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(下)- block中任性用self的更多相关文章

  1. 讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(中)- IOS不为人知的Bug

    前言: 话说昨晚还是前晚,写了一篇:讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(上) 文章写到最后时,多了很多莫名奇妙的问题!!! 为了解决了这些莫名奇妙的问题,我又战斗了2 ...

  2. 讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(上)

    前言: 好久没写文章了,最近先是重构IT恋.又重写IT恋中. Sagit框架也不断的更新,调整,现在感觉已完美了了相当的多. 今天不写教程,先简单分享一下技术内容. 1:见Block必有:#defin ...

  3. [转] weak_ptr解决shared_ptr环状引用所引起的内存泄漏

    http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引 ...

  4. weak_ptr解决shared_ptr环状引用所引起的内存泄漏[转]

    转载:http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理 ...

  5. 一步步调试解决iOS内存泄漏

      虽然iOS 5.0版本之后加入了ARC机制,由于相互引用关系比较复杂时,内存泄露还是可能存在.所以了解原理很重要. 这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄 ...

  6. 使用Xcode和Instruments调试解决iOS内存泄漏

    尽管iOS 5.0加入版本号之后ARC机制,由于相互引用关系是复杂的.内存泄漏可能仍然存在.于是,懂原理是非常重要的. 这里讲述在没有ARC的情况下,怎样使用Instruments来查找程序中的内存泄 ...

  7. 调试解决iOS内存泄漏

    这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄露,以及NSZombieEnabled设置的使用. 本文假设你已经比较熟悉Obj-C的内存管理机制. 实验的开发环境:X ...

  8. android中handler使用应该注意的问题(解决由handler引起的OOM内存泄漏)

    最近,在项目过程中频繁的使用handler处理一些ui线程上的操作,以及使用handler的postdealy.然而使用过后却不对handler进行处理,进而产生了内存溢出现象,通过google,发现 ...

  9. .net 循环引用是否会造成内存泄漏

    一直想做这么一个测试,人和手的测试.类型"人"有一个属性"手",需要"手"也可以读取"人"的数据.则"手&qu ...

随机推荐

  1. 用 Label 控制 Service 的位置 - 每天5分钟玩转 Docker 容器技术(106)

    上一节我们讨论了 Service 部署的两种模式:global mode 和 replicated mode.无论采用 global mode 还是 replicated mode,副本运行在哪些节点 ...

  2. Android IntentService使用介绍以及源码解析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.IntentService概述及使用举例 IntentService内部实现机制用到了HandlerThread,如果对HandlerThrea ...

  3. 《跟我学IDEA》二、配置maven、git、tomcat

    上一篇博文我们讲解了如何去下载并安装一个idea,在这里我们推荐的是zip的解压版,另外我们配置的一些编码和默认的jdk等.今天我们来学习配置maven.git.tomcat等.还是那句话,工欲善其事 ...

  4. java web 学习笔记 jsp内置对象

    jsp2 表达式语言的内置对象 使用方式${object.attributename} 或者${object["attributename"]} pageContext pageS ...

  5. 为了提高性能,怎样动态载入JS文件

    超级表格是一款多人协作的在线表格.程序相当复杂,用到十几个JS文件. 可是有些文件是在打开某些类型的表格时才须要载入. 比如,仅仅有当打开甘特图表格时,才须要载入gantetu.js文件. 那么问题来 ...

  6. android 程序执行linux命令注意事项

    一:问题描述    在已经root过的android设备下,app执行一个linux命令,app需要获取su权限,在某些android主板下会出现异常, Command: [su] Working D ...

  7. 多命令顺序执行,dd命令,管道|,grep,通配符,其他特殊符号

    多命令顺序执行:命令1;命令2 命令之间没有逻辑关系 命令1&&命令2 命令1执行正确才执行命令2,命令1执行错误不会执行命令2 命令1||命令2 命令执行错误才执行命令2,命令1执行 ...

  8. iOS voip电话和sip软电话 --网络电话

    一|介绍1.两者区别: SIP软电话与IP电话在技术上属于同一类型,只是SIP软电话是使用电脑软件实现的,而IP电话有一部分是在话机中直接写入了程序,可以通过硬件直接使用.IP(简称VoIP,源自英语 ...

  9. MySQL操作时间的函数集

    求两个Timestamp之间的秒差值: select TIMESTAMPDIFF(SECOND,TIMESTAMP("2017-03-01 07:58:20"),timestamp ...

  10. Error Running Git Empty git --version output:IDEA关联GitHub时出现这个错误

    刚刚学习使用idea中,想要把自己的项目上传到github,遇到这样一个问题,先记录下来,到时候解决了在把方法贴出来. ---------------------------------------- ...