美文网首页iOS开发iOS 坑的集中营iOS Developer
ViewController中玩过self.view = nil

ViewController中玩过self.view = nil

作者: 踩坑小分队 | 来源:发表于2016-11-11 16:04 被阅读656次

    在ViewController中执行

    self.view = nil;
    

    会发生什么?
    前提是ARC的环境。

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        UIButton *tempBtn = [UIButton buttonWithType:UIButtonTypeSystem];
        [tempBtn setTitle:@"测试按钮" forState:UIControlStateNormal];
        tempBtn.tag = 100;
        tempBtn.frame = CGRectMake(100, 100, 100, 100);
        tempBtn.backgroundColor = [UIColor cyanColor];
        [tempBtn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:tempBtn];
        
        NSLog(@"执行了 --------------------------- viewDidLoad ");
    }
    
    /** 按钮的点击事件 */
    - (void)clickBtn:(UIButton *)sender
    {
        TempViewController *temp = [[TempViewController alloc] init];
        
        [self.navigationController pushViewController:temp animated:YES];
        
        self.view = nil;
        
        NSLog(@"sender ==== %@",sender);
        NSLog(@"self.view.subViews = %@",self.view.subviews);
        UIButton *btn = [self.view viewWithTag:100];
        NSLog(@"btn ==== %@",btn);   
    }
    

    点击按钮的时候执行 self.view = nil;的操作。
    发现程序会马上再走一次,viewDidLoad方法。
    有两个问题:
    1、self.view = nil; self.view被释放了吗?
    2、self.view上面的subViews被释放了吗?
    这个可以参考:http://stackoverflow.com/questions/14679475/about-self-view-nil-in-arc
    大概理解,self.view之前是被Controller强引用的,当 self.view = nil 如果执行完,self.view的引用计数变成0了就自然被释放了
    同样的道理,如果self.view被释放了,那么上面的子控件如果没有其他的强引用的话,那么引用计数也会变成0,因为之前只是被self.view强引用了。所以也会被释放掉。

    可以用以下代码验证:

    #import "ViewController.h"
    #import "TempViewController.h"
    
    @interface ViewController ()
    @property (strong, nonatomic) UILabel *strongLabel;
    @property (weak, nonatomic) UILabel *weakLabel;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        UIButton *tempBtn = [UIButton buttonWithType:UIButtonTypeSystem];
        [tempBtn setTitle:@"测试按钮" forState:UIControlStateNormal];
        tempBtn.frame = CGRectMake(100, 100, 100, 100);
        tempBtn.backgroundColor = [UIColor cyanColor];
        [tempBtn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:tempBtn];
        
        
        UIButton *tempBtn1 = [UIButton buttonWithType:UIButtonTypeSystem];
        [tempBtn1 setTitle:@"测试按钮1" forState:UIControlStateNormal];
        tempBtn1.frame = CGRectMake(220, 100, 100, 100);
        tempBtn1.backgroundColor = [UIColor cyanColor];
        [tempBtn1 addTarget:self action:@selector(clickBtn1:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:tempBtn1];
        
        NSLog(@"执行了 --------------------------- viewDidLoad ");
        
        UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(100, 220, 100, 100)];
        lab.backgroundColor = [UIColor redColor];
        lab.tag = 300;
        [self.view addSubview:lab];
        
        NSLog(@"lab = === %@",lab);
        NSLog(@"strongLabel == %@",_strongLabel);
        NSLog(@"weakLabel == %@",_weakLabel);
    
    }
    
    /** 按钮的点击事件 */
    - (void)clickBtn:(UIButton *)sender
    {
        // 获取label
        UILabel *lab = [self.view viewWithTag:300];
        self.strongLabel = lab;
        self.weakLabel = lab;
        
        TempViewController *temp = [[TempViewController alloc] init];
        [self.navigationController pushViewController:temp animated:YES];
        
    }
    
    - (void)clickBtn1:(UIButton *)sender
    {
        UILabel *lab = [self.view viewWithTag:300];
        
        NSLog(@"lab = === %@",lab);
        NSLog(@"strongLabel == %@",_strongLabel);
        NSLog(@"weakLabel == %@",_weakLabel);
    }
    
    - (void)viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
        self.view = nil;
    }
    
    @end
    

    首次执行,打印结果:

     执行了 --------------------------- viewDidLoad 
     lab = === <UILabel:0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
     strongLabel == (null)
    weakLabel == (null)
    

    点击测试按钮1,打印结果:

     lab = === <UILabel: 0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
     strongLabel == (null)
     weakLabel == (null)
    

    此时 strongLabel 和 weakLabel 还是null。

    点击测试按钮,跳转界面,
    并且将lab分别强引用给strongLabel,弱引用weakLable。
    然后在VieeController的

    • (void)viewWillDisappear:(BOOL)animated方法中执行self.view = nil;操作。
      点击返回按钮,然后点击测试按钮1,查看打印结果
     lab = === <UILabel: 0x7fb5c9c01720; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60000009cca0>>
     strongLabel == <UILabel: 0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
    weakLabel == <UILabel: 0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
    

    发现,strongLabel 、weakLabel是初次label的地址,和重新生成的label地址不同。

    所以总结可得。如果执行self.view = nil操作之后,如果view上面相关的子控件被其他的类强引用的话,就不会被释放,只是引用计数减一。

    如果代码修改成下面这样:

    /** 按钮的点击事件 */
    - (void)clickBtn:(UIButton *)sender
    {
        // 获取label
        UILabel *lab = [self.view viewWithTag:300];
    //    self.strongLabel = lab;
        self.weakLabel = lab;
        
        TempViewController *temp = [[TempViewController alloc] init];
        [self.navigationController pushViewController:temp animated:YES];
        
    }
    

    那么执行完上面的操作之后,最后weakLabel也是nil,因为没有强引用lab。

    这个有什么应用场景呢?当然现在随着iOS设备的硬件的升级,可能不会出现使用self.view = nil;的场景了

    在之前的一些设备上的时候,内存小,很容易就会内存不够用了。

    移动设备终端的内存极为有限,应用程序必须做好low-memory处理工作,才能避免程序因内存使用过大而崩溃。

    low-memory 处理思路
    通常一个应用程序会包含多个view controllers,当从view跳转到另一个view时,之前的view只是不可见状态,并不会立即被清理掉,而是保存在内存中,以便下一次的快速显现。但是如果应用程序接收到系统发出的low-memory warning,我们就不得不把当前不可见状态下的views清理掉,腾出更多的可使用内存;当前可见的view controller也要合理释放掉一些缓存数据,图片资源和一些不是正在使用的资源,以避免应用程序崩溃。

    思路是这样,具体的实施根据系统版本不同而略有差异,本文将详细说明一下iOS 5与iOS 6的low-memory处理。

    iOS 5 的处理
    在iOS 6 之前,如果应用程序接收到了low-memory警告,当前不可见的view controllers会接收到viewDidUnload消息(也可以理解为自动调用viewDidUnload方法),所以我们需要在 viewDidUnload 方法中释放掉所有 outlets ,以及可再次创建的资源。当前可见的view controller 通过didReceiveMemoryWarning 合理释放资源,具体见代码注释。

    举一个简单的例子,有这样一个view controller:

    @interface MyViewController : UIViewController {
        NSArray *dataArray;
    }
    @property (nonatomic, strong) IBOutlet UITableView *tableView;
    @end
    

    对应的处理则为:

    #pragma mark -
    #pragma mark Memory management
    
    - (void)didReceiveMemoryWarning {
        // Releases the view if it doesn't have a superview.
        [super didReceiveMemoryWarning];
        
        // Relinquish ownership any cached data, images, etc that aren't in use.
    }
    
    - (void)viewDidUnload {
        // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
        // For example: self.myOutlet = nil;
        self.tableView = nil;
        dataArray = nil;
        
        [super viewDidUnload];
    }
    

    iOS 6 的处理
    iOS 6 废弃了viewDidUnload方法,这就意味着一切需要我们自己在didReceiveMemoryWarning中操作。
    具体应该怎么做呢?

    1.将 outlets 置为 weak
    当view dealloc时,没有人握着任何一个指向subviews的强引用,那么subviews实例变量将会自动置空。

    @property (nonatomic, weak) IBOutlet UITableView *tableView;
    

    2.在didReceiveMemoryWarning中将缓存数据置空

    #pragma mark -
    #pragma mark Memory management
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
        dataArray = nil;
    }
    

    不要忘记一点,每当tableview reload 的时候,需要判断一下 dataArray ,若为空则重新创建。

    兼容iOS 5 与 iOS 6
    好吧,重点来了,倘若希望程序兼容iOS 5 与 iOS 6怎么办呢? 这里有一个小技巧,我们需要对didReceiveMemoryWarning 做一些手脚:

    #pragma mark -
    #pragma mark Memory management
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        
        if ([self isViewLoaded] && self.view.window == nil) {
            self.view = nil;
        }
        
        dataArray = nil;
    }
    

    判断一下view是否是window的一部分,如果不是,那么可以放心的将self.view 置为空,以换取更多可用内存。

    这样会是什么现象呢?假如,从view controller A 跳转到 view controller B ,然后模拟low-memory警告,此时,view controller A 将会执行self.view = nil ; 当我们从 B 退回 A 时, A 会重新调用一次 viewDidLoad ,此时数据全部重新创建,简单兼容无压力~~

    Note:
    如果你好奇Apple为什么废弃viewDidUnload,可以看看Apple 的解释:
    Apple deprecated viewDidUnload for a good reason. The memory savings from setting a few outlets to nil just weren’t worth it and added a lot of complexity for little benefit. For iOS 6+ apps, you can simply forget about view unloading and only implement didReceiveMemoryWarning if the view controller can let go of cached data that you can recreate on demand later.

    如有失误请各位路过大神即时指点,或有更好的做法,也请指点一二,在下感激不尽。

    相关文章

      网友评论

        本文标题:ViewController中玩过self.view = nil

        本文链接:https://www.haomeiwen.com/subject/asdfpttx.html