@property的作用
当我们写下@property NSObject *foo时,编译器帮我们做了以下几件事:
- 创建实例变量_foo
- 声明foo属性的setter、getter方法
- 实现foo属性的setter、getter方法
@property 的本质是什么?
@property = ivar + getter + setter;
实例变量+get方法+set方法,也就是说使用@property 系统会自动生成setter和getter方法;
@property的属性关键字详解
atomic和nonatomic
atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。
-
atomic:系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。getter 还是能得到一个完好无损的对象(可以保证数据的完整性),但这个对象在多线程的情况下是不能确定的。
- 举个🌰
如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,有3种 可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性 的值,可能是 B set 的值,也有可能是 C set 的值。所以atomic可并不能保证对象的线程安全。
也就是说:如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,每次只能有一个线程调用对象的setter方法,所以可以保证数据的完整性。
atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。
nonatomic:就没有这个保证了,nonatomic返回你的对象可能就不是完整的value。因此,在多线程的环境下原子操作是非常必要的,否则有可能会引起错误的结果。但仅仅使用atomic并不会使得对象线程安全,我们还要为对象线程添加lock来确保线程的安全。
nonatomic的速度要比atomic的快。atomic是Objc使用的一种线程保护技术,这种机制是耗费系统资源的,所以在iPhone这种小型设备上,我们基本上都是使用nonatomic,而对象的线程安全问题则由程序员代码控制。
atomic与nonatomic的本质区别其实也就是在setter方法上的操作不同
nonatomic对象、atomic对象setter和getter方法的实现:
/// nonatomic对象
- (void)setCurrentImage:(UIImage *)currentImage
{
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
- (UIImage *)currentImage
{
return _currentImage;
}
/// atomic对象
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
}
- (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
assign
1.这个修饰词是直接赋值的意思 , 整型/浮点型等数据类型都用这个词修饰 .
2.如果没有使用 weak strong retain copy 修饰 , 那么默认就是使用 assign 了.
3.当然其实对象也可以用 assign 修饰 , 只是对象的计数器不会+1 . ( 与 strong 的区别 )
4.如果用来修饰对象属性 , 那么当对象被销毁后指针是不会指向 nil 的 . 所以会出现野指针错误 . ( 与weak的区别 )
1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
IBOutlet连出来的视图属性为什么可以被设置成weak?
因为父控件的subViews数组已经对它有一个强引用。
不同点:
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空 nil。
weak
weak是弱引用,用weak描述修饰或者所引用对象的计数器不会加一,并且会在引用的对象被释放的时候自动被设置为nil,大大避免了野指针访问坏内存引起崩溃的情况,另外weak还可以用于解决循环引用。
weak原理概括
weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组。weak的底层实现的原理是什么?
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash表,Key是所指对象的地址,value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
为什么value是数组?因为一个对象可能被多个弱引用指针指向
weak原理实现步骤
1. 初始化时
runtime会调用objc_initWeak函数,objc_initWeak函数会初始化一个新的weak指针指向对象的地址。
示例代码:
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数。
这个函数在Clang中的声明如下:
id objc_initWeak(id *object, id value);
而对于 objc_initWeak() 方法的实现如下:
id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效,无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
// 这里传递了三个 bool 数值
// 使用 template 进行常量参数传递是为了优化性能
return storeWeakfalse/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
这个函数仅仅是一个深层函数的调用入口,而一般的入口函数中,都会做一些简单的判断(例如 objc_msgSend 中的缓存判断),这里判断了其指针指向的类对象是否有效,无效直接释放,不再往深层调用函数。否则,object将被注册为一个指向value的__weak对象。而这事应该是objc_storeWeak函数干的。
注意:objc_initWeak函数有一个前提条件:就是object必须是一个没有被注册为__weak对象的有效指针。而value则可以是null,或者指向一个有效的对象。
2. 添加引用时:
objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
objc_storeWeak的函数声明如下:
id objc_storeWeak(id *location, id value);
objc_storeWeak() 的具体实现,请参考weak弱引用实现的方式
3. 释放时:
调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:
调用objc_release
因为对象的引用计数为0,所以执行dealloc
在dealloc中,调用了 _ objc _ rootDealloc函数
在 _ objc _ rootDealloc 中,调用了object _ dispose函数
调用objc_destructInstance
最后调用objc _ clear _ deallocating
objc _ clear _ deallocating该函数的动作如下:
从weak表中获取废弃对象的地址为键值的记录
将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
将weak表中该记录删除
从引用计数表中删除废弃对象的地址为键值的记录
strong
在ARC环境下,只要某一对象被一个strong指针指向,该对象就不会被销毁。如果对象没有被任何strong指针指向,那么就会被销毁。在默认情况下,所有的实例变量和局部变量都是strong类型的。可以说strong类型的指针在行为上跟非ARC下的retain是比较相似的
copy
预备知识
-
浅拷贝
- 只是将对象内存地址多了一个引用,也就是说,拷贝结束之后,两个对象的值不仅相同,而且对象所指的内存地址都是一样的。
-
深拷贝
- 拷贝一个对象的具体内容,拷贝结束之后,两个对象的值虽然是相同的,但是指向的内存地址是不同的。两个对象之间也互不影响,互不干扰。
copy的作用
在非集合类对象中,对不可变对象进行copy操作,只仅仅是指针复制——浅复制,进行mutableCopy操作,是内容复制——深复制。
对于不可变的集合类对象进行copy操作,只是改变了指针,其内存地址并没有发生变化;进行mutableCopy操作,内存地址发生了变化,但是其中的元素内存地址并没有发生变化。
对于可变集合类对象,不管是进行copy操作还是mutableCopy操作,其内存地址都发生了变化,但是其中的元素内存地址都没有发生变化,属于单层深拷贝。
使用注意:
- 当将一个可变对象分别赋值给两个使用不同修饰词的属性后,改变可变对象的内容,使用strong修饰的会跟随着改变,但使用copy修饰的没有改变内容。
那么,是不是NSMutableString等这些可变对象是不是也需要copy来修饰呢?答案是千万不要这么干,我们可以测试一下:
@interface test()
@property (nonatomic, copy) NSMutableString *strCopy;
@end
/********************* test.m **********************/
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
self.strCopy = string;
[self.strCopy appendString:@"def"]; // 在这一行会crash
因为copy是复制出一个不可变的对象,在不可变对象上运行可变对象的方法,就会找不到执行方法