修饰符是编译器的行为:
类型 | 类型关键字 |
---|---|
原子性 | 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 |
允许空值 | (nullable,nonnull) ,( _Nullable, _Nonnull) ,(__nullable, __nonnull) |
原子性:
atomic : 原子性,线程安全的,但不包括操作和访问(比如集合的相关操作) 性能不好
nonatomic : 非原子性,不写默认也是非原子性线程不安全,性能好 |
例如:
@property(atomic) NSString *name
@property(nonatomic) NSString *name
集合类型的增删不是线程安全的,因为被atomic修饰的属性只是对get/set方法加锁,可参考源码:
setter:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
//...
//源码太长,删掉了不相关的代码
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
//...
getter:
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
- 可读写:
readwrite : 可读写,默认行为,编译器会生成对应的get/set方法
readonly : 只读,编译器只会生成get方法
setter : 用指定的方法名替换系统生成的set方法
getter : 用指定的方法名替换系统生成的set方法
例如:
@property(readwrite) NSString *name;
@property(nonatomic,setter=setWithName:) NSString *str
@property(atomic,getter=isAppStr)NSString *appStr
可以通过clang -rewrite-objc xxx.m 查看编译后的的cpp文件
内存语义:
assign : 修饰值类型,如果用来修饰对象类型在使用处编译器警告,该对象的空间释被放了,会变成悬垂指针|
weak : ARC下才能使用,修饰对象类型,不会使引用计数+1,如果修饰值类型编译器会报错⚠️
retain : 修饰对象类型,会使引用计数+1,如果修饰值类型编译器会报错⚠️
strong : ARC下才能使用,修饰对象类型
copy : 修饰block,字符串,集合类型
例如:@property(copy) NSString *name
assign:
- 修饰值类型
- 如果修饰对象类型不会报错,使用的时候会编译器会警告,警告信息如下:
⚠️
Assigning retained object to unsafe property; object will be released after assignment
对象创建完立刻会被系统自动回收,会变成悬垂指针,奔溃
weak:
- ARC模式下使用
- 修饰对象类型
- 修饰非对象类型报错,报错信息如下:
❗️Property with 'weak' attribute must be of object type - 修饰block属性,编译器警告,警告信息如下:
⚠️Assigning block literal to a weak property; object will be released after assignment
retain:
- MRC下使用
- 修饰对象类型
- 引用计数+1
- 修饰非对象类型报错,报错信息如下:
❗️Property with 'retain (or strong)' attribute must be of object type - 修饰block,警告如下:
⚠️Retain'ed block property does not copy the block - use copy attribute instead
strong:
- ARC模式下使用(代替retain)
- 修饰对象类型,block
- 引用计数+1
- 修饰非对象类型报错,报错信息如下:
❗️Property with 'retain (or strong)' attribute must be of object type
copy:
- 修饰字符串,block 引用计数不会+1
- 修饰,集合类型会有不同的结果:
详情参考iOS浅拷贝和深拷贝
历史原因: MRC模式下,retain修饰block 相当于assign,所以必须用copy修饰block
ARC模式下:strong修饰block没有问题,相当于用copy修饰
你应该用copy来修饰block属性。为了追踪原始作用域捕获的状态,block需要被拷贝。当使用Automatic Reference Counting你就不需要担心这些,因为这会自动发生,但是属性attribute命名的最佳实践应该是能够说明它的必然行为。
是否允许空值
以下三组修饰词效果都一样,区别是放的位置不一样 |
---|
nullable,nonnull |
_Nullable, _Nonnull |
__nullable, __nonnull |
例如,修饰属性:
@property(nonatomic,copy,nullable)NSString * stringA;
@property(nonatomic,copy)NSString * _Nullable stringB;
@property(nonatomic,copy)NSString * __nullable stringC;
修饰方法参数:
-(nullable NSString *)stringWithApplist:(nonnull NSString *)string;
-(NSString * _Nullable)stringWithAppInfo:(NSString * _Nullable)string;
-(NSString * __nullable)stringWithAppMemory:(NSString * __nullable)string;
- 以下情况不能使用nullable,nonnull
二级指针(id*),C类型的指针(char *) 只能用__nullable, _Nullable
-(nullable NSError *)error:( NSError * __nullable *)error;
char *__nullable appNameWithCString(char * _Nullable name);// C 函数
局部作用域声明变量:
NSString * _Nullable name;//函数中声明的局部变量
成员变量:
@interface APPList (){
NSString * _Nonnull _appName;//成员变量
}
@end
为了方便起见,建议都用_Nullable, _Nonnull
- 区域审核:
NS_ASSUME_NONNULL_BEGIN
...
在这些区域内,任何简单的指针类型都将被假定为nonnull,您只需要具体指定那些允许空值
NS_ASSUME_NONNULL_END`
文档中的例子:
NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject
// ...
- (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, _Nonnull, __nonnull当我们设置nil值的时候编译器会警告:
⚠️Null passed to a callee that requires a non-null argument
至于__nullable, __nonnull历史原因,详情参考文档
此功能最初是在Xcode 6.3中使用关键字__nullable和__nonnull发行的。由于与第三方库的潜在冲突,我们已在Xcode 7中将它们更改为您在此处看到的_Nullable和_Nonnull。但是,为了与Xcode 6.3兼容,我们预定义了宏__nullable和__nonnull来扩展为新名称。
变量限定符
__strong , __weak, __unsafe_unretained, __autoreleasing |
---|
在ARC模式下,对象默认是strong类型,以下用于修饰变量(不带下划线的strong用在属性中)
__strong:声明一个变量时默认是__strong类型,当对象被强引用时对象会一直存在
__weak:当对象没有强引用时,弱引用被设置为nil。
__unsafe_unretained:指向一个引用,该引用不会使被强引用,并且该对象没有强引用时不会设置为nil。如果它引用的对象被释放,指针将悬空。通常在MRC环境下使用
__autoreleasing:用于表示通过引用(id*)传递并在返回时自动释放的参数。
__autoreleasing 修饰的变量指向的对象相当于调用 autorelease,会注册到自动释放池。
例如,声明变量(局部变量,成员变量,属性均可)时这样写:
@interface APPList : NSObject{
id __weak delegate;
NSObject * __strong object;
}
//...
@property(nonatomic)APPList * __strong list;//编译也没问题
@property(nonatomic,nullable)id __weak obj;// nonnull 和 weak互斥
//...
NSObject * __strong strongObj;
NSObject * __weak weakObj;
NSObject * __unsafe_unretained unsafeObj;
由于strong是默认的,所以通常不用写__strong:
NSObject * strongObj1
这种形式,编译器会转换成下面这种形式
NSObject * __strong strongObj2;
__strong, __weak一般成对出现在block中避免循环引用
block详解
__autoreleasing:通常,方法的声明是这样的:
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
而error的声明是隐式的:
NSError * __strong e;
因此编译器会重写代码:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// Report the error.
// ...
本地变量声明( __strong)和参数( __autoreleasing)之间的区别导致编译器创建临时变量。在获取__strong变量的地址时你可以通过将参数声明为 id __storng*来获得其原始指针。或者你可以将变量声明为 __autoreleasing。
参考文档