OC属性,类型修饰符

修饰符是编译器的行为:

类型 类型关键字
原子性 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:

  1. 修饰值类型
  2. 如果修饰对象类型不会报错,使用的时候会编译器会警告,警告信息如下:
    ⚠️Assigning retained object to unsafe property; object will be released after assignment
    对象创建完立刻会被系统自动回收,会变成悬垂指针,奔溃

weak:

  1. ARC模式下使用
  2. 修饰对象类型
  3. 修饰非对象类型报错,报错信息如下:
    ❗️Property with 'weak' attribute must be of object type
  4. 修饰block属性,编译器警告,警告信息如下:
    ⚠️Assigning block literal to a weak property; object will be released after assignment

retain:

  1. MRC下使用
  2. 修饰对象类型
  3. 引用计数+1
  4. 修饰非对象类型报错,报错信息如下:
    ❗️Property with 'retain (or strong)' attribute must be of object type
  5. 修饰block,警告如下:
    ⚠️Retain'ed block property does not copy the block - use copy attribute instead

strong:

  1. ARC模式下使用(代替retain)
  2. 修饰对象类型,block
  3. 引用计数+1
  4. 修饰非对象类型报错,报错信息如下:
    ❗️Property with 'retain (or strong)' attribute must be of object type

copy:

  1. 修饰字符串,block 引用计数不会+1
  2. 修饰,集合类型会有不同的结果:

详情参考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。

参考文档

你可能感兴趣的:(OC属性,类型修饰符)