iOS 属性修饰词和权限控制词
Swift 权限控制词
概述
swift3.0中,新增了fileprivate和open权限。swift4.0中,对fileprivate和private的访问范围做了调整。访问控制权限,限制了不同源文件和module之间的访问。这个特性使你可以隐藏一些代码的实现,和明确一些可以访问和使用的接口。
module 和 源文件
swift的访问控制模型是基于module和源文件的。
一个module是一个独立的代码建造单元,例如一个framework或者一个可以被其他module通过swift关键字import的单元。
一个源文件是在一个module中单独的swift源码(或是APP或framework里单独的文件)。虽然常见方式,是在分开的源文件里定义单独的类型,但一个源文件也可以包含多个类型、方法等的定义。
访问级别
swift提供了5个不同的访问级别,权限最高的是open,其次依次是public、internal、fileprivate,最低是private。默认使用的是internal。
open & public
使用open和public定义的实体,在它们定义的module的任意文件中皆可使用。其他module如果import了此实体,则其他module的源文件也可使用。一般在framework中指定的公开接口里,使用open和public级别。
** open 和 public 的区别**
- public或更低权限的类,只能在其定义的module中子类化
- public或更低权限的类的成员,只能在其定义的module中被重写或子类化
- open权限的类,可在其定义的module,或者import了其的module中子类化
- open权限的类的成员,可在其定义的module,或者import了其的module中重写或子类化
internal
intenal修饰的实体在其定义的module中皆可使用,但是在其他module中无法使用。一般定义APP或者framework内部结构的时候,会使用internal级别
fileprivate
fileprivate定义的实体,只能在其源文件中使用,当其只在整个源文件中使用的时候,使用fileprivate级别
private
private定义的实体,只能在定义的范围内,和其在同一文件的extension中使用,当其只在当前声明范围内使用的时候,使用private级别
使用原则
子类:子类的访问级别不可以高于父类,但子类重写的方法的访问级别可以高于父类
枚举:枚举的每个值都和它们所属枚举有相同的权限,且不能单独为某个值定义权限
协议:协议可以指定访问级别,并且对于协议的每一项来说,都和协议的访问级别相同,不能单独为协议的某个方法指定不同的访问级别
参考:
Swift访问控制权限
OC 属性修饰词
OC的属性修饰词可分为以下几类:
- 原子性:nonatomic和atomic
- 内存管理:assign、strong、weak、copy、retain和unsafe_unretained
- 读写属性:setter、getter、readwrite、readonly
- 可空性:nonnull、nullable、null_unspecified
1. 原子性:nonatomic和atomic
nonatomic
非原子性,一般属性都用 nonatomic 进行修饰,因为 atomic 非常耗时。
atomic
原子性,会在getter和setter时自动加互斥锁,来保证属性的赋值和取值是线程安全的。但不保证操作和访问。
比如说 atomic 修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在 atomic 的负责范围之内的,所以给被 atomic 修饰的数组添加对象或者移除对象是没办法保证线程安全的。
2. 内存管理:assign、retain、strong、weak、copy和unsafe_unretained
assign
1)既可以修饰基本数据类型,也可以修饰对象类型;
2)setter方法的实现是直接赋值,用于基本数据类型;
3)修饰基本数据类型,NSInteger, CGFloat, int, float, BOOL等;
4)修饰对象类型时,引用计数不加一;
5)会产生悬垂指针(悬垂指针:assign 修饰的对象在被释放之后,指针仍然指向原对象地址,该指针变为悬垂指针。这时候如果继续通过该指针访问原对象的话,就可能导致程序崩溃);
retain
1)MRC下使用,ARC下使用strong;
2)修饰强引用,将指针原来指向的旧对象释放掉,并指向新的对象。同时将新对象的引用计数加1;
3)setter方法的实现是,release旧值,retain新值,用于OC对象类型;
strong
1)ARC下才能使用;
2)原理同retain;
3)在修饰block时,strong相当于copy,而retain相当于assign;
weak
1)ARC下才能使用;
2)只能修饰对象类型;
3)修饰弱引用,不增加引用计数。用于避免循环引用;
4)weak修饰的对象在被释放之后,会自动将指针置为nil,不会产生悬垂指针;
copy
1)setter方法的实现是,release旧值,copy新值,用于NSString,block等类型;
2)当修饰可变类型的属性时,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong;
3)当修饰不可变类型的属性时,如NSArray、NSDictionary、NSString,用copy;
unsafe_unretained
1)既可以修饰基本数据类型,也可以修饰对象类型;
2)MRC下经常使用,ARC下基本不用;
3)原理同weak,区别是unsafe_unretained会产生悬垂指针;
3. 读写属性:readwrite、readonly、setter、getter
readwrite
可读可写(默认),同时生成 setter 方法和 getter 方法的声明和实现。
readonly
只读,只生成 getter 方法的声明和实现。
setter
可以指定生成的 setter 方法名,如 setter = setName。
getter
可以指定生成的 getter 方法名,如 getter = getName。
4. 可空性
苹果在 Xcode 6.3 引入的一个 Objective-C 的新特性nullability annotations
。这些关键字可以用于属性、方法返回值和参数中,来指定对象的可空性,这样编写代码的时候就会智能提示。在 Swift 中可以使用?
和!
来表示一个对象是optional
的还是non-optional
,如UIView?
和UIView!
。而在 Objective-C 中则没有这一区分,UIView
即可表示这个对象是optional
,也可表示是non-optioanl
。这样就会造成一个问题:在 Swift 与 Objective-C 混编时,Swift 编译器并不知道一个 Objective-C 对象到底是optional
还是non-optional
,因此这种情况下编译器会隐式地将 Objective-C 的对象当成是non-optional。引入nullability annotations
一方面为了让 iOS 程序员平滑地从 Objective-C 过渡到 Swift,另一方面也促使开发者在编写 Objective-C 代码时更加规范,减少同事之间的沟通成本。
关键字__nullable
和__nonnull
是苹果在 Xcode 6.3 中发行的。由于与第三方库的潜在冲突,苹果在 Xcode 7 中将它们更改为_Nullable
和_Nonnull
。但是,为了与 Xcode 6.3 兼容,苹果预定义了宏__nullable
和__nonnull
来扩展为新名称。同时苹果同样还支持没有下划线的写法nullable
和nonnull
,它们的区别在与放置位置不同。
注意:此类关键词仅仅提供警告,并不会报编译错误。只能用于声明对象类型,不能声明基本数据类型。
nullable、_Nullable 、__nullable
对象可以为空,区别在于放置位置不同
nonnull、_Nonnull、__nonnull
对象不能为空,区别在于放置位置不同
null_unspecified、_Null_unspecified 、__null_unspecified
未指定是否可为空,区别在于放置位置不同
null_resettable
1)getter 方法不能返回为空,setter 方法可以为空;
2)必须重写 setter 或 getter 方法做非空处理。否则会报警告Synthesized setter 'setName:' for null_resettable property 'name' does not handle nil
使用示例
- 声明属性
@property (nonatomic, copy, nullable) NSString * param;
@property (nonatomic, copy) NSString * _Nullable param;
@property (nonatomic, copy) NSString * __nullable param;
- 修饰方法返回值
- (nullable NSString *)method;
- (NSString * _Nullable)method;
- (NSString * __nullable)method;
- 修饰方法参数
- (void)methodWithParam:(nullable NSString *) param;
- (void)methodWithParam:(NSString * _Nullable) param;
- (void)methodWithParam:(NSString * __nullable) param;
- 例外情况:对于
双指针类型对象
、Block 的返回值
、Block 的参数
等,这时候就不能用nonnull/nullable
修饰,只能用带下划线的__nonnull/__nullable
或者_Nonnull/_Nullable
:
- (void)methodWithError:(NSError * _Nullable * _Nullable)error
- (void)methodWithError:(NSError * __nullable * __nullable)error;
- (void)methodWithBlock:(nullable id __nonnull (^)(id __nullable params))block;
Audited Regions:Nonnull 区域设置
如果每个属性或每个方法都去指定nonnull和nullable,将是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针类型都被假定为nonnull,因此我们只需要去指定那些nullable指针类型即可。示例代码如下:
NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;
@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END
// --------------
self.list.name = nil; // okay
AAPLListItem *matchingItem = [self.list itemWithName:nil]; // warning!
使用规范
1)对于属性、方法返回值、方法参数的修饰,使用:nonnull/nullable
;
2)对于 C 函数的参数、Block 的参数、Block 返回值的修饰,使用:_Nonnull/_Nullable
,建议弃用__nonnull/__nullable
;
为了安全起见,苹果还制定了以下几条规则:
1)typedef
类型的的可空性通常依赖于上下文,即使在 Audited Regions 中也不能假定它为nonnull
;
2)对于复杂的指针类型(如id *
)必须明确指定它的可空性。例如,指定一个指向nullable
对象的nonnull
指针,可以使用_Nullable id * _Nonnull
;
3)特殊类型的NSError **
经常用于通过方法参数返回错误,因此始终假定它是指向nullable
的NSError
对象的nullable
的指针。
5. 所有权修饰符:__strong、__weak、__unsafe_unretained、__autoreleasing
__strong
1)强引用持有对象,可以对应 strong、retain、copy 关键字;
2)编译器将为 strong、retain、copy 修饰的属性生成带 __strong 所有权修饰符的实例变量。
__weak
1)弱引用持有对象,对应 weak 关键字,ARC下用来防止循环引用。
2)编译器将为 weak 修饰的属性生成带 __weak 所有权修饰符的实例变量。
__unsafe_unretained
1)弱引用持有对象,对应 unsafe_unretained、assign 关键字,MRC下用来防止循环引用。
2)编译器将为 unsafe_unretained 修饰的属性生成带 __unsafe_unretained 所有权修饰符的实例变量。
3)与 __weak 相比,它不需要遍历 weak 表来检查对象是否 nil,性能上要更好一些。但是它会产生悬垂指针。
__autoreleasing
1)在 MRC 中我们可以给对象发送 autorelease 消息来将它注册到 autoreleasepool 中,而在 ARC 中我们可以使用 __autoreleasing 修饰符修饰对象将对象注册到 autoreleasepool 中。
相关面试题
Q:atomic 修饰的属性是怎么样保存线程安全的?
答: 编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁,可以保证属性的赋值和取值原子性操作是线程安全的,但不包括操作和访问。比如说atomic修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在atomic的负责范围之内的,所以给被atomic修饰的数组添加对象或者移除对象是没办法保证线程安全的。
Q:什么时候使用 weak/__weak 关键字?
1)ARC 中为了避免循环引用而使用,可以让相互引用的对象中的一个使用weak/__weak弱引用修饰,常用于对delegate和block的修饰;
2)Interface Builder 中 IBOutlet 修饰的控件一般也是用weak。
Q:assign 和 weak 关键字的区别有哪些?
1)weak只能修饰对象,而assign既可以修饰对象也可以修饰基本数据类型;
2)assign修饰的对象在被释放后,指针仍然指向原对象地址;而weak修饰的对象在被释放之后会自动置指针为 nil;
3)相同点:在修饰对象的时候,assign和weak都不改变对象的引用计数。
Q:assign可以用于OC对象吗?
答:不可以,assign一般用于修饰基本数据类型和oc数据类型。
基本数据类型和oc数据类型的内存被分配在栈上,而对象的内存被分配到堆上,栈的内存是由系统分配和释放的。堆的内存则由程序员管理。
所以,如果对象被释放了,该指针仍然指向对象的内存地址,不会设置为nil,而当这个内存地址再被使用时,就有可能会产生崩溃。
Q:weak如何实现自动赋nil?
答:runtime维护了一个weak表。用来存储某个对象的所有弱引用指针。weak表本质是一个哈希表,key是对象的内存地址,value是该对象的所有弱引用指针的地址的数组。当对象被释放时,会根据对象的内存地址,从weak表中获取所有弱引用指针的地址数组,然后遍历数组,将其地址中的数据置为nil,最后再将此keyvalue对从weak表移除。
Q:以下代码会出现什么问题?(深浅拷贝)
@property (copy) NSMutableArray *array;
答: 不论赋值过来的是NSMutableArray还是NSArray对象,进行copy操作后都是NSArray对象(深拷贝)。由于属性被声明为NSMutableArray类型,就不可避免的会有调用方去调用它的添加对象、移除对象等一些方法,此时由于copy的结果是NSArray不可变对象,对NSArray对象调用添加对象、移除对象等方法,就会产生程序异常。
参考:
OC - 属性关键字和所有权修饰符
iOS内存管理(6)--NSArray与NSMutableArray用copy修饰还是strong
OC中的属性关键字