前言
最近看了一些copyWithZone:这个方法相关的一些东西.没想到从这里可以延伸出来很多内容,包括深拷贝,浅拷贝,copy,mutableCopy,NSCopying协议,NSMutableCoping协议,单例等等东西.他们之间要么相互关联,要么环环相扣.也许拿出其中一点来可以说的比较清晰,但是全部合在一起又很乱了.因此写一篇文章来梳理一下这些知识脉络.
单例模式
首先从单例开始说起,因为单例里面涉及到上面比较多的东西.
单例指的是在app生命周期内只会存在一个实例的对象.无论实例化多少次,都只会在内存中存在一个地址.现在看一个简单的单例模式.
1 | ///.h |
通过dispatch_once_t这种方式让初始化代码只执行一次.因此做到了多次实例化只得到一份内存的效果,但是这么写的缺点在于如果调用的人不使用暴露出来的shareManager方法,而是通过init或者new的方式进行初始化的话,得到的对象就不在是一个单例,因此需要把这两个入口也堵住.另外还了解到,无论是通过init的方式,还是通过new的方式创建对象,最终都会调用到allocWithZone:这个方法(经过验证确实是这样子的),因此我们可以少写一些代码,直接堵住allocWithZone:这个初始化方法.因此修改后的代码如下:
1 | + (instancetype)shareManager { |
shareManager内部不在执行dispatch方法,而是放到allocWithZone:中执行.个人认为到这一步的话已经可以算是完整的单例了,但是还是有一些场景需要考虑下.
- 如果调用方使用copy来对单例进行复制.
- 如果调用方方使用mutableCopy来对单例进行复制.
copy和mutableCopy都是定义在NSCopying和NSMutableCopying协议中的方法,如果不继承这两个协议直接调用copy方法的话,会直接调用copyWithZone:这个方法从而造成crash.crash信息如下: - Thread 1: “-[SJPSingletonManager copyWithZone:]: unrecognized selector sent to instance 0x600001328980”
此处我发现了一个华点,如果不继承NSCopying协议,直接实现copyWithZone:这个方法,在实例对象调用copy方法时也不会crash,因此推测NSObject内部的实现代码大致是这样的:
1 | ///没有前面的if条件判断 |
增加了copyWithZone:和mutableCopyWithZone:方法后,整体的代码如下:
1 | @implementation SJPSingletonManager |
到这一步应该已经算是一个完整的单例了.涵盖了可能想到的所有场景.
copy,mutableCopy
oc提供的集合对象:array,dictionary,string等内部都是实现了<NSCopying,NSMutableCopying>两个协议的,有一点需要明确,以上这些类型通过调用mutableCopy方法得到的一定是可变对象,通过copy方法调用得到的一定是immutable对象.考虑如下代码:
1 | ///.h |
通过上面的方式定义了copyWithZone和mutableCopyWithZone方法之后,我们可以对自定义的对象进行拷贝.得到的地址不同的对象.增加这个方法之后就可以对自定义的对象使用copy和mutableCopy方法.
深拷贝,浅拷贝
前面说过,执行copy后得到的是不可变对象,执行mutableCopy方法之后得到的时候可变对象.这在深拷贝和浅拷贝上表现出来是这样的.上面代码运行后,可以得到log输出
2022-01-07 17:50:05.046960+0800 multi-thread-demo[90687:1165918] copy–0x10260c5b8,0x10260c5b8,—array:0x6000008670f0,0x6000004fab00,
分析:
- str是一个不可变的string对象,使用copy是浅拷贝,因此得到的是一份地址的拷贝且内容相同.以前曾经疑问一个点,虽然地址相同,但是对其中一个重新赋值之后为什么地址又不同呢,指向的内容也不同了呢,现在才发现,不可变对象本身是不可修改的,重新赋值只会重新申请一块内存存储新的对象.
- array是一个可变对象,使用copy之后获得一个不可变对象,不过也会生成新的数据地址.同时,使用mutableCopy是深拷贝,这里有一个点,一般开发过程中建议把对象声明为不可变的,如果在运行过程中一定需要修改对象内的元素的话,可以通过如下方式:
1
2
3
4
5_iArray = @[@"111",@"222"];
NSMutableArray *mArray = [_iArray mutableCopy];
[mArray setValue:@"222" forKey:@"1111"];
_iArray = mArray.copy;
/// 通过借助第三个变量来对不可变的变量进行修改.