美文网首页
内存管理

内存管理

作者: 健健锅 | 来源:发表于2017-01-23 18:00 被阅读51次

    ARC MRC 属性 循环引用 深浅copy
    一 ,先说深浅copy
    概念很简单 浅copy 就是copy指针,不copy对象
    深copy就是copy 对象 完全是新的对象
    (1) 非容器类对象 nsstring
    按照网上找到了的 对 字符串 copy copy的是同一个地址 mutablecopy 是copy的对象. 但是试了了一下 感觉不太对:

    图片.png

    无论是copy 还是 mutablecopy 其指针地址全部是不一样的.
    问题就是出在 &object 和 object 的去别 还有

     NSLog(@" %p ",&object);
    //他的意思是打印了该对象指针地址的指针地址.比较绕需要好好理解一下
    

    我觉得很多人会犯这个毛病

    图片.png

    这才是他的正确打开方式
    (2) 接下来呢是系统容器类的 数组字典

    图片.png

    可以看出来,array对象的情况和刚才的字符串一样, 但是 发现数组中的对象还是浅copy 可以看出来 元素的的内存地址是没变的
    我试了一下 把数组长得元素改成可变字符串也是这个效果

    既然mutablearray数组中的元素是一样的,那么对数组操作一下看看效果

    图片.png

    可以看出对mutablecopy 出来的数组进行对象操作不会影响原数组
    但是 如果去修改数组中的元素 那么数组就全部变化了如图


    图片.png

    由此可见,对于容器而言,其元素对象始终是指针复制

    https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html

    图片.png 图片.png 图片.png

    通过官方这篇文档可以看出来 真正的实现 深复制 是通过 归档接档
    (3) 平时用到过深浅复制么?
    如果说用到深浅复制,,,,,那也就是代码中的 对象赋值了
    (4)实现深浅copy:

    使用copy功能的前提:
        1、copy:需要遵守NSCopying协议,实现copyWithZone:方法。
    
        @protocol NSCopying
    
       - (id)copyWithZone:(NSZone *)zone;
    
          @end
    
          2、mutableCopy : 需要遵守NSMutableCopying协议,实现mutableCopyWithZone:方法
    
          @protocol NSMutableCopying
    
          - (id)mutableCopyWithZone:(NSZone *)zone;
    
          @end   
    

    总结: 之前很多人问我或者告诉我 说 copy 是浅copy mutablecopy 是深copy .看了前面写的你还会这么认为么. 我觉深浅拷贝应该是一种现象,就只 发生了复制指针,叫浅copy 发生了 对象复制 叫深copy.mutablecopy 在不同的情况下可能是深复制也可能事浅复制.


    只有一种情况是浅拷贝:不可变对象调用copy方法时,其他情况都为深拷贝;


    二, 关于属性
    http://blog.csdn.net/chenyufeng1991/article/details/49685527
    【atomic/nonatomic】

    (1)atomic[默认属性]:OC使用的一种线程保护技术,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。

    (2)nonatomic:非原子性访问,属性赋值的时候不加锁,多线程并发访问会提高性能。但可能会造成不安全。如果不加该属性,则默认setter/getter两个方法都是原子性事务访问。所以,atomic和nonatomic用来决定编译器生成的getter,setter是否为原子操作。

    【assign/copy/retain】

    (1)assign[默认值]:新建一个指向对象的指针(引用),但是不更改对象的引用计数,不考虑内存管理;常常用于基础数据类型(NSInteger)和C语言数据类型(int ,float,double,char等)。

    (2)copy:建立一个引用计数为1的对象,原有对象不做改变;常常用于NSString。在赋值时传入值的一份拷贝,拷贝工作由copy方法执行。

    (3)retain: 释放旧的对象,将旧对象的值赋给输入对象,再把输入对象的引用计数+1. ;常常用于NSObject和其子类。需要对变量release,再retain新值。指定retain会在赋值时唤醒传入值的retain消息,此属性只能用于OC对象类型,而不能用于Core Foundation(retain会增加对象的引用计数,而基本数据类型或者Foundation对象都没有引用计数的概念)。

    小结:assign和retain的区别,就是使用引用计数,可以对一个内存的释放方便很多。copy就是把原来的内存复制一遍,使各自都拥有一个内存,这样释放的时候也不会出错。

    【copy和retain的区别】

    copy是创建一个新对象。retain是创建一个指针,引用计数+1.

    如:一个NSString对象,内存地址为:0x1111,内容为@“Hello”。

    (1)copy到另外一个NSString后,地址为0x2222,内容相同(新建一个内容,内容拷贝),新的对象引用计数为1,旧的对象没有改变。

    (2)retain到另外一个NSString后,地址相同(新建一个指针,指针拷贝),内容相同,对象的引用计数+1.

    【assign和retain的区别】

    (1)接触过C,那么假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。此时a和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的。因为a并不知道b是否还在使用这块内存,如果a释放了,那么b在使用这块内存的时候会引起程序crash掉。

    (2)知道了上述assign的问题,那么如何解决呢?最简单的一个方法就是使用引用计数。我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时,引用计数增加到2.这时如果a不再使用这块内存,他只需要引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数-1.当引用计数变为0的时候,代表该内存不再被任何指针所引用,系统可以把它直接释放掉。

    小结:assign就是直接赋值,从而可能引起1中的问题,当数据为int,float等原生类型时,可以使用assign。retain使用了引用计数,retain引起引用计数+1,release引起引用计数-1.当引用计数为0时,dealloc函数被调用,内存被回收。
    【strong和weak】

    (1)strong[默认值]:表明这是一个强引用。相当于retain属性。只要引用存在,对象就不能被销毁。当所有强引用消除时,对象才能被释放。

    (2)weak:弱引用。相当于assign属性。一般为了避免retain cycles(就是父类中含有子类,父类retain了子类;子类中又调用了父类,子类又retain了父类),导致无法release,才会使用weak。

    小结:strong和weak只有你在使用ARC的时候才能使用,此时你是不能使用retain,release,autorelease这些方法的,因为ARC会在必要的地方自动插入这些语句。所以我们需要在对象属性加上strong或者weak。

    三,循环引用
    循环引用的三种场景
    1.定时器

     _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:)
                                                     userInfo:nil repeats:YES];
    
    - (void)dealloc
     {
        [_timer invalidate];
         _timer = nil;
    }
    

    这种情况就是 timer 是 self的成员变量,从timer的角度看,在dealloc事 会清除定时器,顺便销对象. 但是self的角度来看是 定时器不停止,那么就不能调用dealloc 这要就循环引用了 这种情况就是 取消定时器的时机不对.如果是控制器 那么就是即将消失时 先取消定时器

    1. block
      block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题,一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_otherVar = ...};block的这种循环引用会被编译器捕捉到并及时提醒。

    声明block

    @interface ViewController ()
    @property(nonatomic,copy)NSString *string;
    @property(nonatomic,copy) NSString * oristr;
    @property(nonatomic,copy) void (^blocktest)(NSString * blockname) ;
    @end
    -(void)block{
        __weak ViewController * weekself = self;
        self.blocktest = ^(NSString *blockname ){
        weekself.string = @"dddd";
        };
        [UIView animateWithDuration:1.0 animations:^{
            self.string = @"dddd";
        }];
    }
    
    

    这里就是有一个问题 我们调用系统的block 为什么不会有循环引用,之前有朋友说是 因为系统给处理了 ,原因其实不是这个, 应该是没有达成形成循环引用的条件 比如 上面的这个UIview的block 他的持有者并不是self 而是UIview.所以不会形成循环引用

    weak-strong dance

    这时候我们进入到 AFNetworking 这个框架里,看看大牛是如何解决这个问题的~

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    
    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }
    

    };

    strong typeof(weakSelf)strongSelf = weakSelf;就是解决这个问题的关键~先将强引用的对象转为弱引用指针,防止了 Block 和对象之间的循环引用。再在 Block 的第一句代码出将 weakSelf 的弱引用转换成 strongSelf 这样的强引用指针,防止了多线程和 ARC 环境下弱引用随时被释放的问题(因为强引用的引用计数至少为1)。

    这里大家一定要有一个认识,weakSelf 位于 Block 的外面,strongSelf 位于 Block 的里面。从内存管理的角度来看,weakSelf 是要比 strongSelf 的声明周期要长的。这样就形成了从弱引用到强引用,再从强引用到弱引用的一种变化,也称作 weak-strong dance。

    3.代理
    delegate 用week 修饰即可

    四,引用计数
    ARC中引入的引用计数


    自己生成的对象,自己持有
    非自己生成的对象,自己也能持有
    不在需要自己持有的对象时释放
    非自己吃藕的对象无法释放


    自己生成的对象,自己持有
    alloc new copy mutablecopy
    非自己生成的对象,自己也能持有

    id obj = [nsmutablearray array];
    

    生成的对象存在,但是并不持有会造成内存泄漏

    五.block
    使用block时 遇到的问题有两个常见的一个是上面提到的循环引用,一个就是捕获变量
    循环引用是因为,block从栈复制到堆上时,对block中引用的数据进行了强引用,那么就会形成循环引用.解决办法就是__week.__week使得引用计数不加1,不去持有该对象.当原对象没有任何强引用的时候,弱引用指针也会被设置为nil。
    还有就是捕获变量,block外的变量,被block捕获以后是不能改变的,即使改变了,block以外的也是没有改变的.
    如果block中是一个可变的数组,那么对一个数组进行改变那么外面的是有效的.持有数组时,获取的是一个指针地址,那么修改的是同一个指针的内容,所以有效.
    __block可以使得修改

    1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
    2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
    3.__block对象可以在block中被重新赋值,__weak不可以。
    4.__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。


    block下循环引用的问题
        __block本身并不能避免循环引用,避免循环引用需要在block内部把__block修饰的obj置为nil
        __weak可以避免循环引用,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong
        的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题
    
    __block与__weak功能上的区别。
        __block会持有该对象,即使超出了该对象的作用域,该对象还是会存在的,直到block对象从堆上销毁;而__weak仅仅是将该对象赋值给weak对象,当该对象销毁时,weak对象将指向nil;
        __block可以让block修改局部变量,而__weak不能。
    

    另外,MRC中__block是不会引起retain;但在ARC中__block则会引起retain。所以ARC中应该使用__weak。

    为什么block要用copy,而不是assign,retain?

    因为block默认申请的是栈内存,当block所在的方法执行完之后内存空间就被释放。为了可以在外部使用block,需要把block放入堆内存中。MRC中只有copy做到这一点,ARC中copy可以。

    为什么NSString要用copy,而不用用strong? (block 字典 数组同理)

    一个场景:如果用NSString指向一个NSMutableString的内存空间的话,用strong修饰。当NSMutableString中内容改变时,NSString指针指向的也会改变,而用copy不会

    必须使用copy的场景:A对象持有string记做A.string,然后赋值给B对象,记做B.string,若希望B.string的内容改变时A.string不改变就必须用copy
    必须用strong的场景:若希望B.string的内容改变时同时A.string也改变则必须用strong
    

    相关面试题
    http://www.cocoachina.com/ios/20160301/15498.html
    http://www.jianshu.com/p/0ad9957e3716

    相关文章

      网友评论

          本文标题:内存管理

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