ios属性修饰符总结

ios属性修饰符总结



很多刚接触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对象的retainrelease操作代码,而不需要我们关心。

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

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

  • 声明变量的修饰符:__strong, __weak, __unsafe_unretained, __autoreleasing;
  • 声明属性的修饰符:strong, weak, unsafe_unretained。
  • 对象和Core Foundation-style对象直接的转换修饰符号:__bridge,__bridge_retained或CFBridgingRetain, __bridge_transfer或CFBridgingRelease。
  • 对于线程的安全,有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



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

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

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

我们修改了heheString,并不会影响到原来的hahaString。copy一个对象变成新的对象(新内存地址) 引用计数为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:这个是比较少用的,几乎没有使用到。在所引用的对象被释放后,该指针就成了野指针,不好控制。
  • _unsafeunretained:也是很少使用。同上。
  • __autoreleasing:如果要在循环过程中就释放,可以手动使用__autoreleasing来声明将之放到自动释放池。

参考资料



官方文档关于自动引用计数内存管理介绍:官方文档说明

关注我



微信公众号:iOSDevShares
有问必答QQ群:324400294

时间: 2024-08-22 15:01:27

ios属性修饰符总结的相关文章

Kotlin学习(四)—— 类和对象,继承,覆盖,抽象类,属性和字段,接口,可见性修饰符,扩展

一.类和对象 1. 类 Kotlin和java的类的声明都是一样的,用class表示,比如 class TestClass { } 如果是空类的话,大括号都可以省了 2.构造函数 一个主构造函数和多个次要函数,主函数在类名后,如 class TestClass { //主函数 class TestClass constructor(firstName: String) { init { print("这里初始化") } } } 如果主构造函数没有任何注解或者可⻅性修饰符,可以省略这个

OC基础之访问修饰符

做C#.Java的人都知道private.protect.public这些访问权限,在OC中看项目基本都没用到这些,昨天就好奇的仔细了解了下,其实OC中也是有这些访问权限的修饰.说实话有好多做过编程的也未必对这些基础知识了解. 1.分清属性和变量的区别 属性是对变量的封装,可以通过属性(get\set)方法来访问变量.例如有一些变量(例如年龄.性别等)在赋值或取值要满足一些条件,那如何解决呢? 于是属性就应运而生,用它的get.set方法来解决这个问题 ,这也是面向对象中封装的体现. 2.pri

块(Block),元素(Element),修饰符(Modifier)

文章简介:BEM代表块(Block),元素(Element),修饰符(Modifier).这些术语的含意将在本文进一步阐述. 什么是BEM? BEM代表块(Block),元素(Element),修饰符(Modifier).这些术语的含意将在本文进一步阐述. 编程方法论中一个最常见的例子就是面向对象编程(OOP).这一编程范例出现在许多语言中.在某种程度上,BEM和OOP是相似的.它是一种用代码和一系列模式来描述现实情况的方法,它只考虑程序实体而无所谓使用什么编程语言. 我们使用BEM的原则创建了

简介php的public、private和protected访问修饰符

PHP中有三种访问修饰符,分别是: public(公共的.默认) protected(受保护的) private(私有的) public(公共的.默认)在PHP5中如果类没有指定成员的访问修饰符,默认就是public的访问权限 . protected(受保护的)被声明为protected的成员,只允许该类的子类进行访问. private(私有的 ) 被定义为private的成员,对于类内部所有成员都可见,没有访问限制.对类 外部不允许访问. 图解 查看本栏目更多精彩内容:http://www.b

C#的基类和访问修饰符

C#的基类和访问修饰符 一. C#的基类System. Object 在面向对象编程过程中,所有的类都是超级父类System. Object类的子类.所有的类都自动继承System. Object,自然也就继承了System. Object类的所有方法,System. Object包含的方法有: public static bool Equals(object objA,object objB){}    public static bool ReferenceEquals(object obj

Java语言入门教程(六):Java语言中的static修饰符

在Java类中,我们常常会看到static关键字,往往被称为静态的.Static修 饰符可以用来修饰数据成员,方法成员,但是不能修饰类(此处指外部类),不 能修饰构造方法.如: package com.csst.vo; public class User { private String name; private static int count; public String getName() { return name; } public void setName(String name)

Effective C#原则29:仅在对基类进行强制更新时才使用new修饰符

你可以用new修饰符来重新定义一个从基类中继承来的非虚成员.你可以这样 做,但并不意味着需要这样做.重新定义非虚方法会导致方法含意的混乱.如果 两个相关的类是继承关系,那么很多开发人员可能会立即假设两段代码块是做完 全相同的事情,而且他们也会这么认为: object c = MakeObject( ); // Call through MyClass reference: MyClass cl = c as MyClass; cl.MagicMethod( ); // Call through

abstract修饰符的含义

abstract修饰符可以用于类.方法.属性.事件和索引指示器(indexer),表示其为抽象成员 abstract不可以和 static .virtual 一起使用 声明为 abstract 成员可以不包括实现代码,但只要类中还有未实现的抽象成员(即抽象类),那么它的对象就不能被实例化,通常用于强制继承类必须实现某一成员 示例: using System; using System.Collections.Generic; using System.Text; namespace Exampl

php面向对象public private protected 访问修饰符

本篇文章是对php面向对象public private protected 访问修饰符进行了详细的分析介绍,需要的朋友参考下   PHP中有三种访问修饰符,分别是: public(公共的.默认) protected(受保护的) private(私有的)public(公共的.默认)在PHP5中如果类没有指定成员的访问修饰符,默认就是public的访问权限. protected(受保护的)被声明为protected的成员,只允许该类的子类进行访问. private(私有的 ) 被定义为private