OC - 属性关键字和所有权修饰符

2020-04-16 17:26:58 浏览数 (1)

网络配图

目录

  • 1. 属性关键字  1.1 原子性  1.2 读写权限  1.3 内存管理  1.4 可空性
  • 2. 所有权修饰符
  • 3. 相关面试题
  • 参考

1. 属性关键字有哪些?

分类

属性关键字

原子性

atomic、nonatomic

读写权限

readwrite、readonly、setter、getter

内存管理

assign、weak、unsafe_unretained、retain、strong、copy

可空性

(nullable、_Nullable 、__nullable)、(nonnull、_Nonnull、__nonnull)、(null_unspecified、_Null_unspecified 、__null_unspecified)、null_resettable

1.1 原子性

属性关键字

用法

atomic

原子性(默认),编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁,可以保证属性的赋值和取值的原子性操作是线程安全的,但不包括操作和访问。比如说 atomic 修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在 atomic 的负责范围之内的,所以给被 atomic 修饰的数组添加对象或者移除对象是没办法保证线程安全的。

nonatomic

非原子性,一般属性都用 nonatomic 进行修饰,因为 atomic 非常耗时。

1.2 读写权限

属性关键字

用法

readwrite

可读可写(默认),同时生成 setter 方法和 getter 方法的声明和实现。

readonly

只读,只生成 getter 方法的声明和实现。

setter

可以指定生成的 setter 方法名,如 setter = setName。

getter

可以指定生成的 getter 方法名,如 getter = getName。

1.3 内存管理

属性关键字

用法

assign

1.setter 方法的实现是直接赋值,一般用于基本数据类型 。2.修饰基本数据类型,如 NSInteger、BOOL、int、float 等;3.修饰对象类型时,不增加其引用计数;4.会产生悬垂指针(悬垂指针:assign 修饰的对象在被释放之后,指针仍然指向原对象地址。这时候如果继续通过指针访问原对象的话,会由于悬垂指针的原因产生内存泄漏或程序异常)。

weak

1.ARC 下才能使用。2.修饰弱引用,不增加对象引用计数,主要可以用于避免循环引用。3.weak 修饰的对象在被释放之后,会自动将指针置为 nil,不会产生悬垂指针。

unsafe_unretained

1.MRC 下经常使用,ARC 下基本不用。2.同 weak,区别就在于 unsafe_unretained 会产生悬垂指针。

retain

1.MRC 下使用,ARC 下基本使用 strong。2.修饰强引用,将指针原来指向的旧对象释放掉,然后指向新对象,同时将新对象的引用计数加1。3.setter 方法的实现是 release 旧值,retain 新值,用于OC对象类型。

strong

1.ARC 下才能使用。2.原理同 retain。

copy

setter 方法的实现是 release 旧值,copy 新值,用于 NSString、block 等类型。

1.4 可空性

Apple Blog:Nullability and Objective-C

苹果在 Xcode 6.3 引入的一个 Objective-C 的新特性nullability annotations。这些关键字可以用于属性、方法返回值和参数中,来指定对象的可空性,这样编写代码的时候就会智能提示。在 Swift 中可以使用!?来表示一个对象是optional的还是non-optional,如button?button!。而在 Objective-C 中则没有这一区分,button即可表示这个对象是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来扩展为新名称。同时苹果同样还支持没有下划线的写法nullablenonnull,它们的区别在与放置位置不同。

注意:此类关键字仅仅提供警告,并不会报编译错误。只能用于声明对象类型,不能声明基本数据类型。

属性关键字

用法

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

使用示例
  • 声明属性
代码语言:javascript复制
@property (nonatomic, copy, nullable) NSString * param;
@property (nonatomic, copy) NSString * _Nullable param;
@property (nonatomic, copy) NSString * __nullable param;
  • 修饰方法返回值
代码语言:javascript复制
- (nullable NSString *)method;
- (NSString * _Nullable)method;
- (NSString * __nullable)method;
  • 修饰方法参数
代码语言:javascript复制
- (void)methodWithParam:(nullable NSString *) param;
- (void)methodWithParam:(NSString * _Nullable) param;
- (void)methodWithParam:(NSString * __nullable) param;
  • 例外情况:对于双指针类型对象Block 的返回值Block 的参数等,这时候就不能用nonnull/nullable修饰,只能用带下划线的__nonnull/__nullable或者 _Nonnull/_Nullable
代码语言:javascript复制
- (void)methodWithError:(NSError * _Nullable * _Nullable)error
- (void)methodWithError:(NSError * __nullable * __nullable)error;
代码语言:javascript复制
- (void)methodWithBlock:(nullable id __nonnull  (^)(id __nullable params))block;
使用效果
代码语言:javascript复制
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (AAPLListItem * _Nullable)itemWithName:(NSString * _Nonnull)name;
@property (copy, readonly) NSArray * _Nonnull allItems;
// ...
@end

// --------------

[self.list itemWithName:nil]; // warning!
Audited Regions:Nonnull 区域设置

如果每个属性或每个方法都去指定nonnullnullable,将是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针类型都被假定为nonnull,因此我们只需要去指定那些nullable指针类型即可。示例代码如下:

代码语言:javascript复制
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!
使用规范:
  • 对于属性、方法返回值、方法参数的修饰,使用:nonnull/nullable
  • 对于 C 函数的参数、Block 的参数、Block 返回值的修饰,使用:_Nonnull/_Nullable,建议弃用__nonnull/__nullable

为了安全起见,苹果还制定了以下几条规则:

  • typedef类型的的可空性通常依赖于上下文,即使在 Audited Regions 中也不能假定它为nonnull
  • 对于复杂的指针类型(如id *)必须明确指定它的可空性。例如,指定一个指向nullable对象的nonnull指针,可以使用_Nullable id * _Nonnull
  • 特殊类型的NSError **经常用于通过方法参数返回错误,因此始终假定它是指向nullableNSError对象的nullable的指针。

2. 所有权修饰符

所有权修饰符

用法

__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

在 MRC 中我们可以给对象发生 autorelease 消息来将它注册到 autoreleasepool 中,而在 ARC 中我们可以使用 __autoreleasing 修饰符修饰对象将对象注册到 autoreleasepool 中。

3. 相关面试题

Q:atomic 修饰的属性是怎么样保存线程安全的?

答:编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁,可以保证属性的赋值和取值原子性操作是线程安全的,但不包括操作和访问。 比如说atomic修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在atomic的负责范围之内的,所以给被atomic修饰的数组添加对象或者移除对象是没办法保证线程安全的。

Q:什么时候使用 weak/__weak 关键字?
  • 1.ARC 中为了避免循环引用而使用,可以让相互引用的对象中的一个使用weak/__weak弱引用修饰,常用于对delegateblock的修饰;
  • 2.Interface Builder 中 IBOutlet 修饰的控件一般也是用weak
Q:assign 和 weak 关键字的区别有哪些?
  • 1.weak可以修饰对象,而assign既可以修饰对象也可以修饰基本数据类型;
  • 2.assign修饰的对象在被释放后,指针仍然指向原对象地址;而weak修饰的对象在被释放之后会自动置指针为 nil;
  • 3.相同点:在修饰对象的时候,assignweak都不改变对象的引用计数。
Q:以下代码会出现什么问题?(深浅拷贝)
代码语言:javascript复制
@property (copy) NSMutableArray *array;

答:不论赋值过来的是NSMutableArray还是NSArray对象,进行copy操作后都是NSArray对象(深拷贝)。由于属性被声明为NSMutableArray类型,就不避免的会有调用方去调用它的添加对象、移除对象等一些方法,此时由于copy的结果是NSArray不可变对象,对NSArray对象调用添加对象、移除对象等方法,就会产生程序异常。

参考

Nullability and Objective-C(Apple Blog) iOS关键字:nullable,nonnull,null_resettable,null_unspecified详解(简书)

0 人点赞