property

1、@property 后面可以有哪些修饰符?

分为原子性、存取方法、内存管理、重写方法四组:

1、nonatomic / atomic(默认)
2、readonly / readwrite(默认)
3、retain / copy / strong /weak / assign / unsafe_unretained
4、getter和setter对存取方法重命名
5、nonull / nullable

详细解释:
1、atomic/nonatomic
指定合成存取方法是否为原子操作,可以理解为是否线程安全。可以发现几乎所有代码的属性设置都会使用nonatomic,这样能够提高访问性能,在iOS中使用锁机制的开销较大,会损耗性能。

2、readwrite/readonly

readwrite是编译器的默认选项,表示自动生成getter和setter,如果需要getter和setter不写即可。
readonly表示只合成getter而不合成setter。

3、assign
assign表示对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,常用于标量类型,如NSInteger,NSUInteger,CGFloat,NSTimeInterval等。

assign也可以修饰对象如NSString等类型对象,上面说过使用assign修饰不会更改所赋的新值的引用计数,也不改变旧值的引用计数,如果当所赋的新值引用计数为0对象被销毁时属性并不知道,编译器不会将该属性置为nil,指针仍旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃,因此使用assign修饰的类型一定要为标量类型。 非常危险。

4、weak
weak表示对所赋的值对象持有弱引用表示一种“非拥有关系”(nonowning relationship),weak修饰的时候同样不会增加所赋的新值的引用计数,也不减少旧值的引用计数,但当该值被销毁时,weak修饰的属性会被自动赋值为nil,这样就可以避免野指针错误。
weak常用在修饰delegate等防止循环引用的场景。

5、unsafe_unretained
使用unsafe_unretained修饰时效果与assign相同,不会增加引用计数,当所赋的值被销毁时不会被置为nil可能会发生野指针错误。unsafe_unretained与assign的区别在于,unsafe_unretained只能修饰对象,不能修饰标量类型,而assign两者均可修饰。

在使用中它的用处主要有下面几点:

兼容性考虑。iOS4 以及之前还没有引入 weak,这种情况想表达弱引用的语义只能使用 unsafe_unretained。这种情况现在已经很少见了。

性能考虑。使用 weak 对性能有一些影响,因此对性能要求高的地方可以考虑使用 unsafe_unretained 替换 weak。一个例子是 YYModel 的实现,为了追求更高的性能,其中大量使用 unsafe_unretained 作为变量标识符。

6、strong
strong表示属性对所赋的值持有强引用表示一种“拥有关系”(owning relationship),会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。如果对一些对象需要保持强引用则使用strong。

7、copy
copy修饰的属性会在内存里拷贝一份对象,两个指针指向不同的内存地址。
一般用来修饰有对应可变类型子类的对象。
如:NSString/NSMutableString,NSArray/NSMutableArray,NSDictionary/NSMutableDictionary等。
为确保这些不可变对象因为可变子类对象影响,需要copy一份备份,如果不使用copy修饰,使用strong或assign等修饰则会因为多态导致属性值被修改。
copy还被用来修饰block。

对于可变对象类型,如NSMutableString、NSMutableArray等则不可以使用copy修饰,因为Foundation框架提供的这些类都实现了NSCopying协议,使用copy方法返回的都是不可变对象,如果使用copy修饰符在对可变对象赋值时则会获取一个不可变对象,接下来如果对这个对象进行可变对象的操作则会产生异常,因为OC没有提供mutableCopy修饰符,对于可变对象使用strong修饰符即可

8、retain
在ARC环境下使用较少,在MRC下使用效果与strong一致。

2、使用 property 相比 直接使用变量有什么好处?

1、自动生成 getter 和 setter方法。当声明一个属性(property)的时候编译器默认情况下会自动生成相关的getter和setter方法
2、这些方法的声明的更规范,更容易看出用处
3、能够传递额外的行为信息 ,上面列出的各种修饰符,可以控制额外的行为

3、当给一个用copy修饰的NSString 赋值时,会在内存中拷贝一份吗?

答案:不一定会,看赋值时否是可变类型
原因:有时候我们需要copy一个对象,或是mutableCopy一个对象,这时需要遵守NSCopying和NSMutableCopying协议,来实现copyWithZone:和mutableCopyWithZone:两个方法,而不是重写copy和mutableCopy两个方法。
Foundation框架中的很多数据类型已经帮我们实现了上述两个方法,因此我们可以使用copy方法和mutableCopy方法来复制一个对象,两者的区别在于copy的返回值仍未不可变对象,mutableCopy的返回值为可变对象。

type copy mutableCopy
NS* 浅拷贝,只拷贝指针,地址相同 单层深拷贝,拷贝内容,地址不同
NSMutable* 单层深拷贝,拷贝内容,地址不同 单层深拷贝,拷贝内容,地址不同

由上述表格可以看出,对于不可变类型,使用copy方法时是浅拷贝,只拷贝指针,因为内容是不会变化的。使用mutableCopy时由于返回可变对象因此需要一份拷贝,供其他对象使用。对于可变类型,不管是copy还是mutableCopy均会进行深拷贝,所指向指针不同。

所以,在修饰NSString这样的不可变对象的时候使用copy修饰,但其实当给对象赋一个NSString时仍旧只复制了指针而不是拷贝内容。

#3、为什么block要用copy来修饰?

在MRC内存管理时代,block内存默认是是分配在栈上的(不考虑全局block的情况),可能随时被回收掉。当方法执行完之后,就会被回收掉。指向他的指针也会变成野指针。
如果想在超出block定义时的生命周期范围之外使用,那么需要执行block的copy方法将其复制到堆上。
所以在对block变量赋值的时候,需要把block通过copy拷贝到堆上,然后一直强引用着。
在MRC下对block类型的成员属性修饰最好用copy,而不要用retain,由于使用retain修饰只会改变引用计数而不会执行copy方法将block复制到堆上。此外block是一个对象就更不可能用assign修饰了。

在ARC环境下,只要将block赋值就会自动拷贝到堆上。那么ARC环境下什么情况block会被copy到堆上呢?
1.执行copy方法。
2.作为方法返回值。
3.将Block赋值给非weak修饰的变量
4.作为UsingBlock或者GCD的方法入参时。(暂无法验证)
所以:一般来说我们使用block都是会有赋值操作的,由于有上述条件的存在,所以基本上不会遇到在栈上的block的情况。所以在ARC环境下,block类型的成员属性使用strong或copy修饰其实都可以,但是为了延续MRC的习惯,另外避免真的出现一些奇怪问题的情况,通常还是使用copy修饰。

3、常见使用场景

1、懒加载的实现
在很多情况下很常用,比如:创建一个比较大的而又不一定会使用的对象 ,可以再get方法中去创建。

4、@synthesize和@dynamic分别有什么作用?

1、@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
2、@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
3、@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

5、ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?

1、对应基本数据类型默认关键字是
atomic,readwrite,assign

  1. 对于普通的 Objective-C 对象
    atomic,readwrite,strong

6、在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

回答这个问题前,我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?

1、同时重写了 setter 和 getter 时
2、重写了只读属性的 getter 时
3、使用了 @dynamic 时
4、在 @protocol 中定义的所有属性
5、在 category 中定义的所有属性
6、重载的属性

当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成ivar。

除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理 @property 的所有内容时,你就会尝试通过实现 @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic 来达到这个目的,这时编译器就会认为你打算手动管理 @property,于是编译器就禁用了 autosynthesis(自动合成)。

因为有了 autosynthesis(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于 autosynthesis(自动合成),但是一旦你需要使用ivar,而 autosynthesis(自动合成)又失效了,如果不去手动定义ivar,那么你就得借助 @synthesize 来手动合成 ivar。

其实,@synthesize 语法还有一个应用场景,但是不太建议大家使用:

可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字:

1
2
3
4
5
6
7
8
@implementation CYLPerson 
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
```
上述语法会将生成的实例变量命名为 _myFirstName 与 _myLastName,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。

举例说明:应用场景:

//
// .m文件
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 打开第14行和第17行中任意一行,就可编译成功

@import Foundation;

@interface CYLObject : NSObject
@property (nonatomic, copy) NSString *title;
@end

@implementation CYLObject {
// NSString *_title;
}

//@synthesize title = _title;

  • (instancetype)init
    {
    self = [super init];
    if (self) {

    _title = @"微博@iOS程序犭袁";
    

    }
    return self;
    }

  • (NSString *)title {
    return _title;
    }

  • (void)setTitle:(NSString *)title {
    _title = [title copy];
    }

@end
`

结果编译器报错: enter image description here

当你同时重写了 setter 和 getter 时,系统就不会生成 ivar(实例变量/成员变量)。这时候有两种选择:

要么如第14行:手动创建 ivar
要么如第17行:使用@synthesize foo = _foo; ,关联 @property 与 ivar。

7、 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

不能向编译后得到的类中增加实例变量;
能向运行时创建的类中添加实例变量;
解释下:

因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;

运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。

#参考资料
iOS @property探究(一) 基础详解
iOS @property探究(二) 深入理解
block的理解与研究