zoukankan      html  css  js  c++  java
  • 讲述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现象。

    所以,我是这么思考导航栏问题的:

        1: 一个navigationCtroller只有一个navigationBar。
        2: 每一个viewController,都会复写前一个navigationBar。
        3: 所以,到下一层时,UINavigationBar指向最后一个Controller
        4: 当回退时,最后一个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>0)//发现这里返回的viewControllers,已经是移掉了当前的Controller后剩下的。
        {
            UIViewController *preController=self.viewControllers[count-1];//获取上一个控制器
            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>0)
        {
            UIViewController *current=self.viewControllers[count-1];
            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的坑,好吧,只好把此文改成中,准备再来一篇下。

  • 相关阅读:
    js基础之BOM
    js基础之DOM
    js基础之数组
    js基础之arguments、css
    四个使用this的典型应用
    网页优化URI(http URI scheme与data URI scheme)
    FF与IE对JavaScript和CSS的区别
    javascrip自定义对象的方式
    常用的JavaScript验证正则表达式1
    4.26日软件开发日记:今天我干了什么?
  • 原文地址:https://www.cnblogs.com/cyq1162/p/8207497.html
Copyright © 2011-2022 走看看