美文网首页iOS 面试iOS之基础知识程序员
iOS 内存管理篇(三)---__strong/__weak/_

iOS 内存管理篇(三)---__strong/__weak/_

作者: wanna_dance | 来源:发表于2017-08-01 18:21 被阅读234次

    之前学习的文章链接

    iOS内存管理篇(一)---alloc/reatain/release/dealloc方法实现

    iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解与管理

    前言:说到内存管理,避免不了的就是循环应用和某个变量释放的实际,虽然在实际开发种,ARC会为我们自动的加上引用技术和减少引用技术,但是并不是万能的,百密一疏,还是会在实际开发过程中内存管理出现问题,今天我们要来看的就是__strong,__weak,这些关键词的作用和使用方法

    __strong关键字与retain关似,用了它,引用计数自动+1

    有如下 Demo:

    id obj = [[NSObject alloc]init];
    
    /*等价于*/
    
    id __strong obj = [[NSObject alloc]init];
    

    那么在内存里面是怎么执行这段代码的呢

    /*编译器模拟的代码*/
    id obj = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    objc_release(obj);
    

    从上面的代码我们可以看出,系统两次调用objc_msgSend来创建这个对象并为之开辟内存空间,然后在变量作用于失效的时候使用objc_release来释放对象,虽然在 ARC模式下我们不能使用 release,但是系统在运行时为我们自动添加了 release。

    那么我们来看看实际的例子吧

    有两个变量,是用 strong 修饰的

    @property (nonatomic, strong) NSString *str1;
    @property (nonatomic, strong) NSString *str2;
    

    做了如下的操作

    self.str1 = @"A";
    self.str2 = self.str1;
    self.str1 = nil;
    NSLog(@"self.str2 = %@",self.str2);
    

    大家可以猜猜打印结果是什么

    打印结果是 self.str2 = A
    

    由于str2是strong定义的属性,所以引用计数+1,使得它们所指向的值都是@"A", 虽然 self.str = nil 了,但是并没有使得 str2的引用计数减1导致释放,所以打印的结果为 A


    __weak修饰符提供的功能如同魔法一般

    • 若附有__weak修饰符的变量所引用的对象被废弃,则将 nil赋值给该变量

    • 使用附有__weak修饰符的变量,即是使用注册到__autoreleasepool 中的对象

    有如下代码:

    id __strong obj = [[NSObject alloc]init];
    id __weak obj1 = obj;
    

    我们看看系统是如何为我们处理的

    /*编译器模拟的代码*/
    id obj1;
    objc_initWeak(&obj1,obj);
    objc_destroyWeak(&obj1);
    

    通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束的时候用objc_destroyWeak释放该变量

    我们来下一下的具体的例子

    @property (nonatomic, strong) NSString *str1;
    @property (nonatomic, weak)   NSString *str2;
    

    执行如下的代码

    self.str1 = @"A";
    self.str2 = self.str1;
    self.str1 = nil;
    NSLog(@"self.str2 = %@",self.str2);
    

    结果是 self.str2 = null 为什么会是这个结果呢 分析一下,由于self.string1与self.str2指向同一地址,且str2没有retain内存地址,而 self.str1=nil释放了内存,所以string1为nil。
    所以这是符合第一条规则的,因为str2 指向的地址已经被释放了,那么会将 nil 赋值给 str2

    在这个过程中__weak帮我们干了什么呢

    (1) 从 weak表中获取废弃对象的地址为键值的记录
      (2) 将包含在记录中的所有附有__weak 修饰变量的地址,赋值为 nil
      (3) 从 weak 表中删除该记录
      (4) 从引用技术表中删除废弃对象的地址为键值的记录

    我们来看看 weak 的另外一个特性

    id __weak obj1 = obj/*obj 是 strong 的*/
    NSLog(%@"obj1 = %@",obj1);
    
    

    这段代码转换为系统编译器码为

    id obj1;
    objc_initWeak(&obj1,obj);
    id temp = objc_loadWeakRetained(&obj1);
    objc_autorelease(temp);
    NSLog(%"%@",temp);
    objc_destroyWeak(obj1);
    

    我们可以注意到有个objc_autorelease这个函数,所以正说明了第二点:使用附有__weak修饰符的变量,即是使用注册到__autoreleasepool 中的对象,因为附有__weak的变量所使用的对象都被注册到了__autoreleasepool里面,所以在@autoreleasepool块结束之前可以放心使用,所以在实际开发过程中,如果大量使用 weak 来修饰变量的话,是会非常耗费 CPU资源的,所以当只有会可能产生循环引用的地方使用 weak 是合适的。


    __unsafe_unretain顾名思义就是不安全的,其功能和 weak 差不多

    有如下的代码

    id __unsafe_unretain obj = [[NSObject alloc]init];
    

    这时候编译器报出警告,该代码对应的模拟代码为

    /*编译器模拟的代码*/
    id obj = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    objc_release(obj);
    

    objc_release函数立即释放了生成并持有的对象,这样对象的悬垂指针被赋值给了变量obj 当中

    我们来看如下的代码:

    @property (nonatomic, strong)             NSString *str1;
    @property (nonatomic,__unsafe_unretain)   NSString *str2;
    

    执行如下的代码

    self.str1 = @"A";
    self.str2 = self.str1;
    self.str1 = nil;
    NSLog(@"self.str2 = %@",self.str2);
    

    这次根本就不用输出,在没有输出前,程序已经崩溃了,其实就是野指针造成的,为何会造成野指针呢?同于用unsafe_unretained声明的指针,由于 self.str1=nil已将内存释放掉了,但是str2并不知道已被释放了,所以是野指针。然后访问野指针的内存就造成crash. 所以尽量少用unsafe_unretained关键字。

    __autoreleasing:将对象赋值给附有__autoreleasing修饰的变量等同于 ARC无效时调用对象的 autorelease 方法

    有如下代码:

    @autoreleasepool{
         id __autoreleasing obj = [[NSObject alloc]init];   
    }
    

    对应的编译器码:

    /*编译器的模拟代码*/
    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    objc_autorelease(obj);
    objc_autoreleasePoolPop(pool);
    - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
        NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
        NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
        NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
        *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
    }
    -(void)test
    {
        NSError *error = nil;
        [self generateErrorInVariable:&error];
        NSLog(@"Error = %@", error);
    }
    

    __autoreleasing则可以使对像延迟释放。比如你想传一个未初始 化地对像引用到一个方法当中,在此方法中实始化此对像,那么这种情况将是__autoreleasing表演的时候。看个示例:

    @autoreleasepool{
         id __autoreleasing obj = [[NSObject alloc]init];   
    }
    

    对应的编译器码:

    - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
        NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
        NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
        NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
        *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
    }
    -(void)test
    {
        NSError *error = nil;
        [self generateErrorInVariable:&error];
        NSLog(@"Error = %@", error);
    }
    

    符合内存管理规则:谁分配谁释放。

    小结:不管是__strong、__weak、__unsafe_unretain、__autoreleasing 这是为了让我们更好的理解内存管理,而且在必要的时候使用这些修饰符来管理我们的内存,而在实际开发种,一般很少使用到后两者,前两者在循环引用的时候可以用来解循环引用,我们在今后的学习中还会学习和使用到它们,别忘了哟~

    此文借鉴的文章链接
    iOS中 property中的属性strong 、weak、copy 、assign 、retain 、unsafe_unretained 与autoreleasing区别和作用详解
    书籍《Objective-C高级编程 iOS与OS X多线程和内存管理》

    相关文章

      网友评论

        本文标题:iOS 内存管理篇(三)---__strong/__weak/_

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