【iOS基础篇】---@property 后面的修饰符们

虽然写了很多年的iOS代码,但是很多东西没有深入理解,或者当时理解了,后来不用又慢慢又忘了。所以抽空整理一份资料,以备自己以后查找。也希望看到的小伙伴批评指正。
这篇文章主要写@property后面的修饰符。

1、assign、unsafe_unretained和weak

assign:既可以修饰对象,也可以修饰基本类型。unsafe_unretained与assign同义。assign修饰对象会产生野指针的问题,修饰的对象释放后,指针不会自动置成nil,此时再向对象发消息程序会崩溃。

weak:只能修饰对象,如果修饰基本数据类型会报错:Property with ‘weak’ attribute must be of object type. weak修饰的对象释放后(引用计数变为0),指针会被自动置为nil,之后再向该对象发消息也不会崩溃(向nil发送任何消息都不会崩溃)。weak适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。

总结:assign、weak和unsafe_unretained,修饰的属性,再调用set方法时是不会增加引用计数的。可以理解为set方法只是简单的赋值。也就是栈上的变量的赋值。验证代码如下:

@property(nonatomic,assign) NSObject *property;
//在代码实现中
NSObject *object = [[NSObject alloc] init];
NSLog(@"\n 引用计数 = %lu \n ", (unsigned long)[object retainCount]);
self.property = object;
NSLog(@"\n 引用计数 = %lu \n ", (unsigned long)[object retainCount]);

输出结果
2019-08-05 02:02:08.475192+0800 test4[89384:3597709]
引用计数 = 1
2019-08-05 02:02:08.475320+0800 test4[89384:3597709]
引用计数 = 1

所以assign、weak和unsafe_unretained修饰的对象,默认生成的set方法,应该如下方法一所示:
-(void)setProperty:(NSObject *)property{

_property = property;
//weak可能还有其他一些操作

}

而retain和strong修饰的对象,大概生成的set方法如下方法二所示:
-(void)setProperty:(NSObject *)property{

[property retain];
[_property release];
_property = property;

}

注意:如果自己实现了set方法,则这些关于存取方法的修饰符就不再起作用了。比如,你虽然用assign修饰了对象,但是自己实现了set方法二,则assign会被忽略。

2、retain和strong

苹果的文档里有这样一句话:
// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;

所以在ARC下他们是等价的。

通常在ARC环境里,不会用assign修饰对象,因为有了weak,再用assign就是自找麻烦。总之在ARC环境里用strong代替retain强引用对象,用weak弱引用对象,用assign修饰基本数据类型。

3、atomic和nonatomic

atomic和nonatomic的区别在于:系统自动生成的 getter/setter 方法不一样。(如果你自己写 getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,写不写都一样)

对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。

而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。
nonatomic系统合成的方法大概如下:

@property(nonatomic, retain) UITextField *userName;
//系统生成的代码如下:

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}

atomic系统合成的方法大概如下:

//@property(retain) UITextField *userName;
//系统生成的代码如下:

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName release];
      userName = [userName_ retain];
    }
}

假设有一个 atomic 的属性 “userName”,如果线程 A 调[self setUserName:@”A”],线程 B 调[self setUserName:@”B”],线程 C 调[self userName],那么所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。因此,属性 setUserName 是读/写安全的。
但是,如果有另一个线程 D 同时在调[userName release],那可能就会crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。
如果 userName 属性是 nonatomic 的,那么上面例子里的所有线程 A、B、C、D 都可以同时执行,可能导致无法预料的结果。如果是 atomic 的,那么 A、B、C 会串行,而 D 还是并行的。

4、readwrite和readonly

readwrite:默认的属性修饰符。会合成get和set方法。

readonly:只会生成get方法。所以如果obj用readonly修饰,则self.obj = xxx;编译时会报错提示:“Assignment to readonly property”。

5、copy属性修饰符

copy:只能修饰对象类型,不能修饰基础数据类型(用了会报编译时错误)。用copy修饰的对象,必须实现
NSCopying协议也就是实现方法-(id)copyWithZone:(nullable NSZone *)zone。合成的set方法中会调用这个方法。这里也能看出copy,和retain不一样的地方。

至于copy是深拷贝还是浅拷贝完全是看copyWithZone的实现方式。也就是说copy和深拷贝和浅拷贝没有关系!
另外对象的mutableCopy方法,也是要对象满足NSMutableCopying协议,实现- (id)mutableCopyWithZone:(nullable NSZone *)zone方法。如果不实现次方法会运行时报错!

总结

修饰符其实就是告诉编译器怎么来合成存取方法的。有些修饰符需要自己另外实现一些方法,比如copy,这里就能定制一些自己的东西,比如实现真正的多层深拷贝等等。

另外,还有一些新的修饰符nullable、nonnull、null_resettable、null_unspecified、getter=method、setter=method等,我们下一篇文章里再总结.

参考:
https://www.jianshu.com/p/728…

    原文作者:大鱼
    原文地址: https://segmentfault.com/a/1190000019975967
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞