随便说说removeFromSuperview方法

作者: 刘小壮 | 来源:发表于2016-06-29 21:11 被阅读15916次
    该文章属于<简书 — 刘小壮>原创,转载请注明:

    <简书 — 刘小壮> http://www.jianshu.com/p/b817c94cac0b


    之前写过一篇关于removeFromSuperview方法处理的文章,写完后一直就没怎么更新这篇文章。这两天回过头来看看,感觉这篇文章有些地方写的不够严谨,而且还有一些自己理解错的地方,所以打算重写这篇文章。

    在使用removeFromSuperview方法的时候,发现这个方法有很多我们没有注意的地方。而且对于一些不规范的操作,苹果也对其进行了容错处理。所以我对removeFromSuperview的一些使用细节整理了一下,包括ARCMRC两种情况。其中还会简单涉及一些内存管理相关的部分,文章中有什么问题,还希望多多指出,谢谢!😊


    占位图

    测试环境

    运行环境 版本号
    Xcode 7.1
    Mac OS X 10.11
    iOS 9.3.1
    硬件设备 iPhone 5S

    视图结构

    iOS应用中,视图的结构是树型数据结构,以这种结构来控制视图显示,这种数据结构有一个很好的优点:
    层级关系分明,并且方便传递事件。从根节点出发,通过叶节点向下扩展,同一枝的上一个节点就是下一个节点的superview,下一个节点就是上一个节点的subview。每个应用程序都有一个主window,这个window就是根节点。

    removeFromSuperview

    每一个View都和视图结构以及响应者链有直接的关系,但是这篇文章不打算着重的讲这两个方面,主要讲removeFromSuperview方法。将当前视图从其父视图移除,需要调用removeFromSuperview方法。下面是苹果对于这个API的官方定义:

    Unlinks the receiver from its superview and its window, and removes it from the responder chain.
    

    译:把当前View从它的父View和窗口中移除,同时也把它从响应事件操作的响应者链中移除。

    removeFromSuperview就是一个视图节点删除的操作,执行这个方法,就等于在树形结构中找到该节点,从树型数据结构中删除该节点及其子节点,而并非只是删除该节点自己。同时,另一个操作就是把该对象从响应者链中移除。

    执行removeFromSuperview方法后,会从父视图中移除,并且将Superview对视图的强引用删除,此时如果没有其他地方再对视图进行强引用,则会从内存中移除。如果还存在其他强引用,视图只是不在屏幕中显示,并没有将该视图从内存中移除。所以如果需要使用该视图,不需要再次创建,而是直接addSubview就可以了。

    对于这个API,苹果并没有给出过多的解释,只是简单的描述了一下这个API,以及说明了这个API的注意点。所以,下面将会根据我的使用经验,继续讲解这个API。

    内存管理

    方法调用后的内存管理

    经过测试,在ARC的情况下执行removeFromSuperview方法多次也没有问题,因为ARC内存是系统为我们管理的。

    但是在MRC中,根据官方API的说明:
    If the view’s superview is not nil, the superview releases the view.
    

    也就是每执行一次removeFromSuperview方法,方法内部都会执行一次release操作。但是经过我的测试,发现调用removeFromSuperview方法后,引用计数并没有减少,反而增加了一个。(我是通过调用retainCount查看的引用计数,但是并不是真正准确的,后面会讲解这个问题)

    内存陷阱

    那如果是这样,那就遇到一个和我们之前认知不太相同的答案了。具体是什么问题,还是需要自己写代码验证,于是我基于上面描述的测试环境,写了一些关于视图的测试代码。

    UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    [self.view addSubview:view];
    [view release];
    [view removeFromSuperview];
    // 多次调用remove方法
    [view removeFromSuperview];
    

    经过我的测试发现,调用removeFromSuperview方法后引用计数并没有增加,调用完之后还是会release的。我们之前看到的引用计数的增加,是因为系统的隐藏操作导致的。之前在MRC时期经常发现retainCount不准确,这主要是因为iOS系统API的引用、或自动释放池导致的,所以retainCount并不能当做可靠的参考。

    所以,如果调用多个release,还是会崩溃的,始终要相信iOSMRC内存管理原则,这才是可靠的。可以多次调用removeFromSuperview方法,在已经移除父视图后,其他多余的调用不会改变任何引用计数。对于addSubview:方法也是一样的,下面会讲这个方法。

    使用细节

    多次执行addSubview:操作

    假设现在有ViewAViewBViewC三个视图,ViewA添加到ViewB之后又要添加到ViewC上面,此时ViewA同时执行了向ViewBViewC两个视图addSubview:的操作。但是因为只有一个视图对象,所以只会以最后一次添加的为准,第一次执行的添加到ViewB的操作是无效的。通过打印两个View的子视图可以看到,只有最后执行的添加到ViewC上的操作才是有效的,ViewC才真正拥有了ViewA,而ViewB的子视图是空的。

    一个视图不只是向其他多个页面进行添加操作不会出现问题,而且向同一个视图上执行多次添加操作也是没有问题的,并不会导致视图被多次添加的问题,也不需要在添加之前进行removeFromSuperview操作,这个是在MRCARC都是有效的。因为系统在addSubview:方法中进行了一些判断操作,如果当前视图已经添加到其他视图,会将当前视图从其他视图中移除,然后执行添加操作。如果当前视图已经添加到这个视图中,就不会再次执行添加操作。

    一个小坑

    其实也说不上是坑,可以算是一个了解的知识点吧。在ARCMRC的情况下,调用removeFromSuperviewaddSubview:方法其中之一,都需要在另一个方法已经执行的情况下才会有效,对于多次执行一个同方法系统也是有判断操作的,并不会被执行多次。

    例如调用remove方法之后,此时视图已经不在父视图之上了,在多次调用这个方法是不起作用的,而且MRC下引用计数也不会被减少多次。对于addSubview:方法也是一样的,向同一个父视图上添加子视图,不会被重复添加,添加之后引用计数也不会多次+1

    注意点

    1. 无论是ARC还是MRC中多次调用removeFromSuperviewaddSubview:方法,都不会造成造成重复释放和添加。
    2. 苹果的官方API注明:Never call this method from inside your view’s drawRect: method.
      译:永远不要在你的ViewdrawRect:方法中调用removeFromSuperview

    相关文章

      网友评论

      • 一个走在路上的人go:楼主 我刚试了下 button . UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        // btn.layer.borderWidth = 1;
        [self.view addSubview:btn];
        btn.frame = CGRectMake(100, 100, 100, 100);

        [btn removeFromSuperview];
        [btn release];
        这样创建会crash;
        用alloc 创建就不会崩溃,没想明白
        一个走在路上的人go:是的:sob: button 点击事件,button 在arc 下引用计数为4 mrc 为5 都是看你博客发现的
        刘小壮:+ (instancetype)buttonWithType:(UIButtonType)buttonType;
        这种通过类方法实例化对象的方式,内部都会写autorelease的,这是MRC默认的编程范式。
        刘小壮:你应该是直接从ARC时代开始了,没经历过MRC时代
      • 小蜗牛吱呀之悠悠:楼主,addSubview操作的时候,我将一个view 添加到self.view上,将redView添加到view上,将blueView添加到redView上,然后查看他们各自的子视图发现,并不是楼主说的情况诶.self.view的子视图是红视图,红视图的子视图是蓝视图,但是楼主说的是中间步骤的addSubview是不执行的,只会执行最后一次的,那这样的话,子视图关系不是应该这样的吗:所有的视图都存在于self.view的子视图中,红视图的子视图为空。这样的话和我测试的情况不太一样,麻烦楼主教我一下,是不是我理解错了?
        刘小壮:@一只努力学习的小蜗牛 :blush:
        小蜗牛吱呀之悠悠:@刘小壮 明白了,感谢楼主解答,谢谢~
        刘小壮:我不太清楚你是通过什么方式看的。按照你的描述,那应该是self.view -> view -> redView -> blueView的结构。我在文中表达的addSubview的观点,是当前视图被通过addSubview方法添加到其他视图上的时候,都是以最后一次添加为标准,也就是之前不管被添加多少次,最后只会被添加到最后一次的视图上。
      • 混不吝丶: 调用 removeFromSuperview 会自动置nil 吗 ? 还有我这两天在项目中发现, 只要是被[window addSubView:testView] , 当testView是强引用 ,写了一个延迟 0.5 秒 调用 [testView removeSuperView] 然后再点击按钮 触发 testView 创建, 并addSubView 后 ,当第一次点击这个按钮的时候 会出现两个testView 显示在 window 上, 等待0.5秒 testView 调用 removeSuperView 后 ,再次点击按钮 显示的是一个testView 。 后来我在[testView removeSuperView] 后 调用 testView = nil 后 再次运行依然是两个testView 显示在window 上 , 最终我在btn的点击方法中 先进行forin window.subViews 将其删除 后 再创建,问题得到解决
        混不吝丶:@刘小壮 确定
        刘小壮:@混不吝丶 执行了一次remove操作之后,视图应该不在window上显示了才对,那个时候其他对象有强引用也无所谓。视图对象在那个时候只存在内存中,并不会显示。
        刘小壮:@混不吝丶 确定第一次点击按钮,是在remove之后吗?
      • Calvin_Shen:写的很不错,学习了,前辈;
        刘小壮:@Calvin_Shen 客气啦,相互交流而已。
      • 归去_来兮:http://blog.csdn.net/likendsl/article/details/7573504 这个是你吗?
        刘小壮:@归去_来兮 不一样,差多了。
      • 超_iOS:我发现移除后这个view 还是不等于nil,怎么看他有没有被强引用啊?
        超_iOS: @刘小壮 嗯嗯,谢谢
        刘小壮:@不知有汉_超 以上说的是在ARC情况下,并且指针不是assign类型修饰。
        刘小壮:@不知有汉_超 打印一下看看,如果没有被释放,那就是还是被强引用,那就可能是被其他成员变量或全局变量的指针引用着。
      • 看个客人:大神,如果是加在window上面的呢,romve以后好像并不会立刻释放被加的view的内存
        我想做女人:remove 后 widow 不再对它强引用 引用计数-1 如果其他地方没人引用了 那就会释放这view啊 放在window上 和 放在普通view 上一样的 内存管理原则
        刘小壮:@看个客人 这个我没注意过,但是内存管理原则应该是一样的吧,window本质上也是继承自view的。你要不写个Demo,然后把结果评论区里分享一下 :blush:
      • OCDak:执行removeFromSuperview方法后,会从父视图中移除,并且将Superview对视图的强引用删除,此时如果没有其他地方再对视图进行强引用,则会从内存中移除`````这个的意思是说removeFromSuperview后打印这个控件的内存地址,可能为空,也可能不为空是吗?那如何销毁一个对象啊?置为nil 嘛
        OCDak:@刘小壮 oo 感谢解释
        刘小壮:@OCDak 强引用的问题在ARC和MRC都是一样的,被强引用则不能释放,包括addSubview这样的隐性强引用。
        刘小壮:@OCDak 看强引用的情况了。就算remove之后,被其他地方强引用,照样不能释放。
      • mysteryemm:有这样一个问题:项目中自定义一个类似alert弹窗的view类,当把它从父视图上移除时,会执行dealloc的方法,并且内存大小也会降到初始状态,再加上苹果文档里的那句话:if the view’s superview is not nil, the superview releases the view. 我一直认为当从父视图上移除时便会销毁(ARC下)……但这和楼主说的有些出入,可否帮忙解答此疑问,谢谢。
        刘小壮:@心董儿 我重新看了一下博客,上面描述有点问题,已改。在ARC或MRC下,只要没有其他强引用去引用视图,视图执行removeFromSuperview方法后会被释放,主要还是看强引用的情况。

      本文标题:随便说说removeFromSuperview方法

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