ios copy/strong/weak..使用总结

原文出自:微信公众号精彩技术文章

温馨提示

本博文不会在此处更新,更新的内容都会是在微信公众号原文,请尽量移步到微信公众号文章链接阅读,以保证文章新更新的内容可显示。

引言

很多刚接触iOS的朋友对属性的@property的可选参数如何使用,什么情况下使用哪种选项不了解,也问了我很多这方面的知识,虽然知道怎么用,但是有些说不出其区别。在这里,再次深入学习一遍,对copy/strong/weak/__weak/__strong/assign的使用场景总结总结。如果有说得不对的地方,请指出。如果有疑问,请私聊我,或者直接回复我。

自动引用计数

原文档关于自动引用说明:Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations, ARC allows you to concentrate on the interesting code, the object graphs, and the relationships between objects in your application.

翻译过来就是:Automatic Reference Counting (ARC)是一个编译器的特性,提供了对iOS对象的自动内存管理,ARC在编译期间自动在适当的地方添加ObjC对象的retain和release操作代码,而不需要我们关心。

ARC在编译期间,根据Objective-C对象的存活周期,在适当的位置添加retain和release代码。从概念上讲,ARC与手动引用计数内存管理遵循同样的内存管理规则,但是ARC也无法防止循环强引用

ARC还引入了新的修饰符来修饰变量和声明属性。

  • 声明变量的修饰符:__strong, __weak, __unsafe_unretained, __autoreleasing;
  • 声明属性的修饰符:strong, weak, unsafe_unretained
  • 对象和Core Foundation-style对象直接的转换修饰符号:__bridge__bridge_retainedCFBridgingRetain, __bridge_transferCFBridgingRelease
  • 对于线程的安全,有nonatomic,这样效率就更高了,但是不是线程的。如果要线程安全,可以使用atomic,这样在访问是就会有线程锁。

记住内存管理法则:谁使对象的引用计数+1,不再引用时,谁就负责将该对象的引用计数-1。

下面我们来声明一个Person类来学习:

@interface Person : NSObject

// 注意:苹果有命名规范的,命名属性时,不能以copy开头。
// 如果下面的属性声明为copyString,会编译不通过。
@property (nonatomic, copy) NSString *copiedString;

// 默认会是什么呢?
@property (nonatomic) NSString *name;
// 默认是strong类型
@property (nonatomic) NSArray *array;

@end

如果属性没有指定类型,默认是什么呢?其实是strong。如果证明呢?验证方法:分别将array属性的类型分别设置为weak, assign,strong,不设置,这四种情况的结果分别是:第一种打印为空,第二种直接直接崩溃,第三种和最后一种是可以正常使用。如下面的验证代码:

  Person *lili = [[Person alloc] init];
  lili.name = @"LiLi";
  lili.copiedString = @"LiLi\' father is LLL";
  lili.array = @[@"谢谢", @"感谢"];

  NSArray *otherArray = lili.array;
  lili = nil;
  NSLog(@"%@", otherArray);

再继续添加下面的代码。默认声明变量的类型为__strong类型,因此上面的NSArray *otherArray = lili.array;__strong NSArray *otherArray = lili.array;是一样的。如果我们要使用弱引用,特别是在解决循环强引用时就特别重要了。我们可以使用__weak声明变量为弱引用,这样就不会增加引用计数值。

  __strong NSArray *strongArray = otherArray;
  otherArray = nil;
  // 打印出来正常的结果。
  NSLog(@"strongArray = %@", strongArray);

  __weak NSArray * weakArray = strongArray;
  strongArray = nil;
  // 打印出来:null
  NSLog(@"weakArray: %@", weakArray);

xib/storybard连接的对象为什么可以使用weak

@property (nonatomic, weak) IBOutlet UIButton *button;

像上面这行代码一样,在连接时自动生成为weak。因为这个button已经放到view上了,因此只要这个View不被释放,这个button的引用计数都不会为0,因此这里可以使用weak引用。

如果我们不使用xib/storyboard,而是使用纯代码创建呢?

@property (nonatomic, weak) UIButton *button;

补充:有朋友反映这里说得不够详细,感谢这位朋友的反馈。

使用weak时,由于button在创建时,没有任何强引用,因此就有可能提前释放。Xcode编译器会告诉我们,这里不能使用weak。因此我们需要记住,只要我们在创建以后需要使用它,我们必须保证至少有一个强引用,否则引用计数为0,就会被释放掉。对于上面的代码,就是由于在创建时使用了weak引用,因此button的引用计数仍然为0,也就是会被释放,编译器在编译时会检测出来的。

这样写,在创建时通过self.button = ...就是出现错误,因为这是弱引用。所以我们需要声明为强引用,也就是这样:

@property (nonatomic, strong) UIButton *button;

block声明使用copy

在使用block时,尽量使用typedef来起一个别名,这样更容易阅读。使block作为属性时,使用copy

typedef void (^HYBTestBlock)(NSString *name);

@property (nonatomic, copy) HYBTestBlock testBlock;

字符串

对于字符串,通常都是使用copy的方式。虽然使用strong似乎也没有没有问题,但是事实上在开发中都会使用copy。为什么这么做?因为对于字符串,我们希望是一次内容的拷贝,外部修改也不会影响我们的原来的值,而且NSString类遵守了NSCopying, NSMutableCopying, NSSecureCoding协议。

下面时使用copy的方式,验证如下:

  NSString *hahaString = @"哈哈";
  NSString *heheString = [hahaString copy];
  // 哈哈, 哈哈
  NSLog(@"%@, %@", hahaString, heheString);
  heheString = @"呵呵";
  // 哈哈, 呵呵
  NSLog(@"%@, %@", hahaString, heheString);

我们修改了heheString,并不会影响到原来的hahaStringcopy一个对象变成新的对象(新内存地址) 引用计数为1 原来对象计数不变。

属性声明修饰符

属性声明修饰符有:strong, weak, unsafe_unretained, readWrite,默认strong, readWrite的。

  • strong:strong和retain相似,只要有一个strong指针指向对象,该对象就不会被销毁
  • weak:声明为weak的指针,weak指针指向的对象一旦被释放,weak的指针都将被赋值为nil;
  • unsafe_unretained:用unsafe_unretained声明的指针,指针指向的对象一旦被释放,这些指针将成为野指针。
@property (nonatomic, copy) NSString *name;
// 一旦所指向的对象被释放,就会成为野指针
@property (nonatomic, unsafe_unretained) NSString *unsafeName;

lili.name = @"Lili";
lili.unsafeName = lili.name;
lili.name = nil;
// unsafeName就变成了野指针。这里不会崩溃,因为为nil.
NSLog(@"%@", lili.unsafeName);

深拷贝与浅拷贝

关于浅拷贝,简单来说,就像是人与人的影子一样。而深拷贝就像是梦幻西游中的龙宫有很多个长得一样的龙宫,但是他们都是不同的精灵,因此他们各自都是独立的。

我相信还有不少朋友有这样一种误解:浅拷贝就是用copy,深拷贝就是用mutableCopy。如果有这样的误解,一定要更正过来。copy只是不可变拷贝,而mutableCopy是可变拷贝。比如,NSArray *arr = [modelsArray copy],那么arr是不可变的。而NSMutableArray *ma = [modelsArray mutableCopy],那么ma是可变的。

lili.array = [@[@"谢谢", @"感谢"] mutableCopy];
NSMutableArray *otherArray = [lili.array copy];
lili.array[0] = @"修改了谢谢";
// 打印: 谢谢 修改了谢谢
// 说明数组里面是字符串时,直接使用copy是相当于深拷贝的。
NSLog(@"%@ %@", otherArray[0], lili.array[0]);

这里就是浅拷贝,但是由于数组中的元素都是字符串,因此不会影响原来的值。

数组中是对象时:

  NSMutableArray *personArray = [[NSMutableArray alloc] init];
  Person *person1 = [[Person alloc] init];
  person1.name = @"lili";
  [personArray addObject:person1];

  Person *person2 = [[Person alloc] init];
  person2.name = @"lisa";
  [personArray addObject:person2];

  // 浅拷贝
  NSArray *newArray = [personArray copy];
  Person *p = newArray[0];
  p.name = @"lili的名字被修改了";

  // 打印结果:lili的名字被修改了
  // 说明这边修改了,原来的数组对象的值也被修改了。虽然newArray和personArray不是同一个数组,不是同一块内存,
  // 但是实际上两个数组的元素都是指向同一块内存。
  NSLog(@"%@", ((Person *)(personArray[0])).name);

深拷贝,其实就是对数组中的所有对象都创建一个新的对象:

  NSMutableArray *personArray = [[NSMutableArray alloc] init];
  Person *person1 = [[Person alloc] init];
  person1.name = @"lili";
  [personArray addObject:person1];

  Person *person2 = [[Person alloc] init];
  person2.name = @"lisa";
  [personArray addObject:person2];

  // 深拷贝
  NSMutableArray *newArray = [[NSMutableArray alloc] init];
  for (Person *p in personArray) {
    Person *newPerson = [[Person alloc] init];
    newPerson.name = p.name;

    [newArray addObject:newPerson];
  }
  Person *p = newArray[0];
  p.name = @"lili的名字被修改了";

  // 打印结果:lili
  NSLog(@"%@", ((Person *)(personArray[0])).name);

Getter/Setter

在ARC下,getter/setter的写法与MRC的不同了。我面试过一些朋友,笔试这关就写得很糟(不包括算法)。通常在笔试时都会让重写一个属性的Getter/Setter方法。

@property (nonatomic, strong) NSMutableArray *array;

- (void)setArray:(NSMutableArray *)array {
  if (_array != array) {
    _array = nil;

    _array = array;
  }
}

如果是要重写getter就去呢?就得增加一个变量了,如果同时重写getter/setter方法,就不会自动生成_array变量,因此我们可以声明一个变量为_array:

- (void)setArray:(NSMutableArray *)array {
  if (_array != array) {
    _array = nil;

    _array = array;
  }
}

- (NSMutableArray *)array {
  return _array;
}

总结

关于属性的这些选项的学习,做一下总结:

  • 所有的属性,都尽可能使用nonatomic,以提高效率,除非真的有必要考虑线程安全。
  • NSString:通常都使用copy,以得到新的内存分配,而不只是原来的引用。
  • strong:对于继承于NSObject类型的对象,若要声明为强使用,使用strong,若要使用弱引用,使用__weak来引用,用于解决循环强引用的问题。
  • weak:对于xib上的控件引用,可以使用weak,也可以使用strong。
  • __weak:对于变量的声明,如果要使用弱引用,可以使用__weak,如:__weak typeof(Model) weakModel = model;就可以直接使用weakModel了。
  • __strong:对于变量的声明,如果要使用强引用,可以使用__strong,默认就是__strong,因此不写与写__strong声明都是一样的。
  • unsafe_unretained:这个是比较少用的,几乎没有使用到。在所引用的对象被释放后,该指针就成了野指针,不好控制。
  • __unsafe_unretained:也是很少使用。同上。
  • __autoreleasing:如果要在循环过程中就释放,可以手动使用__autoreleasing来声明将之放到自动释放池。

参考资料

官方文档关于自动引用计数内存管理介绍:https://developer.apple.com/library/mac/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

公众号搜索「iOS开发技术分享」快速关注微信号:iOSDevShares QQ群:324400294

时间: 2024-11-19 00:03:32

ios copy/strong/weak..使用总结的相关文章

NSString属性何时用strong何时用copy?_IOS

前言 我们在声明一个NSString属性时,对于其内存相关特性,通常有两种选择(基于ARC环境):strong与copy.那这两者有什么区别呢?什么时候该用strong,什么时候该用copy呢?让我们先来看个例子. 代码验证 @property (nonatomic, strong) NSString *myStrongString; @property (nonatomic, copy) NSString *myCopyString; - (void)stringTest { NSMutabl

ios属性修饰符总结

ios属性修饰符总结 很多刚接触iOS的朋友对属性的@property的可选参数如何使用,什么情况下使用哪种选项不了解,也问了我很多这方面的知识,虽然知道怎么用,但是有些说不出其区别.在这里,再次深入学习一遍,对copy/strong/weak/__weak/__strong/assign的使用场景总结总结.如果有说得不对的地方,请指出.如果有疑问,请私聊我,或者直接回复我. 自动引用计数 原文档关于自动引用说明: Automatic Reference Counting (ARC) is a

iOS property 用法

形式: @property(para1,para2,para3)ClassType object;参数:有3种类型参数                para1 : atomic / nonatomic                para2 :                                                 retain/assign/copy                                                  strong/we

iOS开发:strong、weak等详解

  现在我们看看iOS5中新的关键字strong, weak, unsafe_unretained. 可以与以前的关键字对应学习strong与retain类似,weak与unsafe_unretained功能差不多(有点区别,等下会介绍,这两个新 关键字与assign类似).在iOS5中用这些新的关键字,就可以不用手动管理内存了,从java等其它语言转过来的程序员非常受用. strong关键字与retain关似,用了它,引用计数自动+1,用实例更能说明一切 @property (nonatomi

iOS面试题

ObjectiveC的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答:Objective-C的类不可以多重继承:可以实现多个接口,通过实现多个接口可以完成C++的多重继承:Category是类别,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系.重写类的方式,是使用继承还是使用分类要看重写的目的而定,不是固定哪个的. #import 跟#include 又什么区别,@class呢, #im

iOS - OC 面向对象语法

1.类 1)根类:因为类 NSObject 是层次结构的最顶层,因此称为根类. 可以将类称为子类(subclass)和父类(superclass),也可以将类称为子类和超类. 2)分类/类别(category):允许以模块的方式向现有类定义添加新的方法(默认不能添加实例变量).扩展自己或他人以前实现的类,使它适合自己的需要. 分类的名称括在类名之后的一对圆括号"( )"中. @interface QCStudent (Print) @end @implementation QCStud

iOS网络编程入门:蓝牙对等网络通信实例讲解

基于蓝牙对等网络通信就是使用Game Kit中的GKSession.GKSessionDelegate. GKPeerPickerController和GKPeerPickerControllerDelegate来实现.开发过程分为3个步骤:连接.发 送数据和接收数据. 下面我们通过一个实例介绍一下基于蓝牙对等网络通信过程.用户点击"连接"按钮,建立连接过程中会出现连接对话框,根据具体情况也会弹出其它的对话 框.这些都是针对蓝牙对等网络标准对话框,而Wifi对等网络没有标准对话框可以使

iOS开发中的单元测试(三)URLManager中的测试用例解析

URLManager是一个基于UINavigationController和UIViewController,以URL Scheme为设计基础的导航控件,目的是实现ViewController的松耦合,不依赖. 准备框架,定义基类 首先按照之前的两篇文章介绍的方法导入单元测试框架和匹配引擎框架,建立好测试Target,并配置编译选项. 定义测试用例基类:UMTestCase(代码1),其他用例全部继承自UMTestCase. #import <GHUnitIOS/GHTestCase.h> @

iOS应用开发:小画板Demo

还有很多功能可以加,记在这里留着慢慢加吧. 代码部分 TouchView.h #import <UIKit/UIKit.h> @interface TouchView : UIView { NSMutableArray *points; NSArray *points_all; CGContextRef context; UIColor *paint_clr; } @property (strong,nonatomic) NSMutableArray *points; @property (s