引导
关于浅复制和深复制的概念,让我感觉有点绕口,以及定义NSString是使用copy还是使用strong那?花费一天的时间,我对这模块做了概念理解和代码验证(有详细的分析过程),最后总结了这篇文章。由众-不知名开发者,原创文章。对内容有疑问可留言交流。
代码块不能滚动,不妨来这里阅读下
- 对以下内容验证
- 对isa指针简单释义一下;
- 浅复制和深复制的概念彻底理解;
- 非集合类(NSString)对象进行copy、mutableCopy操作;
- 面试题:为什么定义NSString要使用copy而不建议使用strong,使用strong会有什么影响❓
- 集合类对象(NSArray)进行copy、mutableCopy操作,单层深复制和真正深复制,集合类内部包含对象是指针复制;
- 面试题:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓
- 面试题:定义NSMutableArray类型的属性,修饰词把strong换成copy有什么影响❓
引用计数
引用计数释义
如今进入ARC的屎蛋,就无需再次输入retain
或者release
代码。下面很形象一示例来释义什么是对象的引用计数。
照明对比引用计数 | 对照明设备所做的动作 | 对oc对象所做的动作 | 引用技数 | oc方法 |
---|---|---|---|---|
第一个进入办公室的人 | 开灯 | 生成并持有对象 | 0 --- >1 | alloc,new,copy,mutableCopy等方法 |
之后每当有人进入办公室 | 需要照明 | 持有对象 | 1 --- >2 | retain方法 |
每当有人下班离开办公室 | 不需要照明 | 释放对象 | 2 --- > 1 | release方法 |
最后一个人下班离开办公室 | 关灯 | 废弃对象 | 1 ----> 0 | dealloc方法 |
总结:
1.在oc的对象中存有引用计数这一整数值。
2.调用alloc
或retain
方法后,引用计数值加1。
3.调用release
后,引用计数值减1。
4.引用计数值为0时,调用dealloc
方法废弃对象。
5.当然不管引用计数是否为0,你也可以主动在dealloc
方法中废弃对象。
引用计数示例代码
说明:
1.以下所有示例代码打印用的Log宏,打印:内存地址,指针,真实类型,引用计数,值
#define LNLog(description,obj) NSLog(@"%@: 内存地址:%p, 指针地址:%p, 真实类型:%@, 引用计数:%lu, 值:%@", (description),(obj),&(obj),(obj).class,(unsigned long)(obj).retainCount,(obj));
2.打印retainCount
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)obj));//ARC_桥接字方式
NSLog(@"Retain count %@", [obj valueForKey:@"retainCount"]);//ARC_kvc方式
NSLog(@"retain count = %ld",obj.retainCount);//MRC
3.打印对象地址有两种情况
对象指针的地址;打印方式:`NSLog(@"%p",&b) - 0x7ffeebf79a78、NSLog(@"%x",&b) - ebf79a78`;
指针指向对象的内存地址(即保存的内容);打印方式:`NSLog(@"%p",b) - 0x60400022b740`;
- (void)testSringOrMutableStringRetainCount
{
// 不可变字符串创建几种方式
NSString * str1 = @"PublicCoderLN";
LNLog(@"直接复制", str1);
NSString * str2 = [NSString stringWithString:@"PublicCoderLN"];//会有警告
LNLog(@"WithString1", str2);
NSString * str3 = [[NSString alloc] initWithString:@"PublicCoderLN"];
LNLog(@"WithString2", str3);
NSString * str4 = [NSString stringWithFormat:@"PublicCoderLN"];
LNLog(@"WithFormat1", str4);
NSString * str5 = [[NSString alloc] initWithFormat:@"PublicCoderLN"];
LNLog(@"WithFormat2", str5);
/
打印:
直接复制: 内存地址:0x104516290, 指针地址:0x7ffeeb6eba78, 真实类型:__NSCFConstantString, 引用计数:18446744073709551615, 值:PublicCoderLN
WithString1: 内存地址:0x104516290, 指针地址:0x7ffeeb6eba70, 真实类型:__NSCFConstantString, 引用计数:18446744073709551615, 值:PublicCoderLN
WithString2: 内存地址:0x104516290, 指针地址:0x7ffeeb6eba68, 真实类型:__NSCFConstantString, 引用计数:18446744073709551615, 值:PublicCoderLN
WithFormat1: 内存地址:0x604000235dc0, 指针地址:0x7ffeeb6eba60, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
WithFormat2: 内存地址:0x604000237f00, 指针地址:0x7ffeeb6eba58, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
*/
NSMutableString * strM1 = [[NSMutableString alloc] initWithString:@"PublicCoderLN"];
LNLog(@"strM1", strM1);
NSMutableString * strM2 = [[NSMutableString alloc] initWithFormat:@"PublicCoderLN"];
LNLog(@"strM2", strM2);
/
打印:
strM1: 内存地址:0x6040004412c0, 指针地址:0x7ffeeb6eba50, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
strM2: 内存地址:0x6040004413b0, 指针地址:0x7ffeeb6eba48, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
*/
}
总结:
- 1.NSString前三种创建出来的isa是NSCFConstantString,引用计数是个无限大的数,如果用int类型打印引用计数都为-1,这表示__NSCFConstantString不会被释放.
- 2.后两种和创建其他object是一样的在堆中占有内存,引用计数为1;
注解:
- 1.这里对isa指针简单释义一下 runtime.h
isa:是一个Class 类型的指针。可Xcode(cmd+shift+O)快捷搜索objc.h-Line38 和 runtime.h-Line55,验证查看。
- 每一个对象本质上都是一个类的实例。其中类定义了成员变量和成员方法的列表。对象通过对象的isa指针指向所属类。
- 每一个类本质上都是一个对象,类其实是元类(meteClass)的实例。元类定义了类方法的列表。类通过类的isa指针指向元类。
- 元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类通过isa指针最终指向的是一个根元类(root meteClass)。
- 根元类的isa指针指向本身,这样形成了一个封闭的内循环。
- 2.问题:怎么判断对象copy和mutableCopy后返回的是不可变类型还是可变类型❓
分析:看到这样一个结论,在runtime下NSString的真实类型是"__NSCFConstantString",而NSMutableString的真实类型是"__NSCFString"。通过上面对NSString和NSMutableString打印结果,根据NSString初始化方式不同,打印的对象真实类型有"__NSCFConstantString"和"__NSCFString"都存在的情况。
浅复制和深复制
相关概念释义
-
非集合类对象指的是NSString,NSNumber等对象。
集合类对象指的是NSArray,NSDictionary,NSSet等对象。 -
在Objective-C中,必须遵守
<NSCopying, NSMutableCopying>
,才能通过两个方法 copy和mutableCopy可以执行拷贝操作,其中copy是获得一个不可变对象,而mutableCopy是获得一个可变对象。并且两个方法分别调用copyWithZone和mutableCopyWithZone两个方法来进行拷贝操作。如果对copy返回对象使用mutable对象接口就会crash,或者强制调用这两个方法会发生crash,如:对UILabel(继承UIView,只遵守了NSCopying协议)进行mutableCopy操作,结果会报reason: '-[UILabel mutableCopyWithZone:]: unrecognized selector sent to instance 0x7fbaecf1e880'
。 -
浅复制和深复制
- 1.浅复制:只复制了对象的指针,指针指向的内存地址还是和源对象指针指向的内存地址一样,对象的引用计数+1;
- 2.深复制:即复制了对象的指针,也复制了指针指向的内存地址,生成新的指针和内存地址且引用计数为+1,对源对象的引用计数没有影响;
4、图解复制概念
非集合类对象的copy与mutableCopy
定义NSString类型的属性(copy/strong修饰词)指向不可变对象
@property (nonatomic, copy) NSString * stringCopy;
@property (nonatomic, strong) NSString * stringStrong;
- (void)testStringUseRetainOrCopyOrMutableCopy
{
NSString * string = [NSString stringWithFormat:@"publicCoderLN"];
LNLog(@"originString", string);
NSLog(@"--------");
// 场景1:定义NSString类型的属性(copy/strong修饰词)指向不可变对象.
self.stringCopy = string;
LNLog(@"_stringCopy", _stringCopy);
self.stringStrong = string;
LNLog(@"_stringStrong", _stringStrong);
NSLog(@"--------");
// 场景2:对不可变类型NSString,进行retain、copy、mutableCopy操作
NSString * strRetain1 = [string retain];
LNLog(@"strRetain1", strRetain1);
NSString * strCopy1 = [string copy];
LNLog(@"strCopy1", strCopy1);
NSString * strMCopy1 = [string mutableCopy];
LNLog(@"strMCopy1", strMCopy1);
/
打印:
originString: 内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba78, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
_stringCopy: 内存地址:0x60400003e320, 指针地址:0x7fa7bd609770, 真实类型:__NSCFString, 引用计数:2, 值:publicCoderLN
_stringStrong: 内存地址:0x60400003e320, 指针地址:0x7fa7bd609778, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN
--------
strRetain1: 内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba70, 真实类型:__NSCFString, 引用计数:4, 值:publicCoderLN
strCopy1: 内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba68, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
strMCopy1: 内存地址:0x600000251e50, 指针地址:0x7ffee5e6ba60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
*/
}
分析:
1.从打印结果可以看出,copy、strong、retain均使对象的retainCount +1;
2.copy不可变对象,只是复制了对象的指针,和原对象string指针地址不同(文中其实做了2次copy),但指针指向对象的内存地址还是同一个,所以是浅复制;
3.mutableCopy不可变对象,即复制了对象的指针,也复制了指针指向对象的内存地址,生成了新的指针和新的内存地址,所以是深复制;
4.提醒:这里的显示地址不是不变的,地址是系统分配的,可能你代码验证的时候就不是这里显示的地址了,但概念是对的;
// 场景3:定义NSString类型的属性,修饰词用把copy换成strong后,再修改原对象有什么影响❓
string = [string substringToIndex:3];
LNLog(@"originString", string);
LNLog(@"_stringCopy", _stringCopy);
LNLog(@"_stringStrong", _stringStrong);
LNLog(@"strRetain1", strRetain1);
LNLog(@"strCopy1", strCopy1);
LNLog(@"strMCopy1", strMCopy1);
/
打印:
originString: 内存地址:0xa000000006275703, 指针地址:0x7ffee5e6ba78, 真实类型:NSTaggedPointerString, 引用计数:18446744073709551615, 值:pub【改原对象】
_stringCopy: 内存地址:0x60400003e320, 指针地址:0x7fa7bd609770, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
_stringStrong: 内存地址:0x60400003e320, 指针地址:0x7fa7bd609778, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
- - -
strRetain1: 内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba70, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
strCopy1: 内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba68, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
strMCopy1: 内存地址:0x600000251e50, 指针地址:0x7ffee5e6ba60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
分析:
1、原对象string的类型本身就是不可变类型,再对其进行操作,系统自动分配了新的内存地址(只是对象的指针和没有修改前原对象的指针一样),这里对原对象string操作后生成的内存地址和其他类型(copy/strong/retain)都不一样,所以只有原对象string操作后改变。
2、【提醒】:有厂友这时候可能会说,从上面修改前和修改后的打印结果来看,我定义NSString使用copy修饰和使用strong,内存地址和值都是一样的,那我为什么不使用strong修饰NSString那❓
分析:一些情况下从性能考虑定义不可变NSString类型的属性,修饰词也可以使用strong。
copy的set方法内部会对传入的字符串进行判断是不可变的还是可变的,如果是不可变字符串,就直接给属性进行赋值,不会生成新的内存地址;
如果是可变字符串对string属性赋值,会进行Copy操作,重新生成一份新的内存地址。
这里会对copy修饰的string会进行判断,几个不会影响性能,但是如果很多就会有了吧。
开发中很多都是定死的NSString不可变字符串的,使用strong也可以。
// copy的set方法
- (void)setStringCopy:(NSString *)stringCopy
{
_stringCopy = [stringCopy copy];
}
*/
注解:
上面打印出现了NSTaggedPointerString
类型,想更多了解的可参考采用Tagged Pointer的字符串。
简单点说,在字符串小于长度12且为immutable对象时,Apple会让其共用同一地址来节省内存的开销。但中如果出现了中文,则会另外开辟新的内存空间进行存储。
总结:对于不可变对象
1、retain/strong/copy,都会使原对象的引用计数+1,指针指向对象的内存地址和原对象一样;
2、copy操作为浅复制,没有生成新的内存地址;mutableCopy操作为深复制,生成了新的内存地址,新生成的对象引用计数为1;
3、修改原对象string的值,对copy和strong修饰定义的属性目标对象_stringCopy 没有影响,还是原来的值(内存地址和值都是原来的);
定义NSString类型的属性(copy/strong修饰词)指向可变对象
@property (nonatomic, copy) NSMutableString * stringMCopy;
@property (nonatomic, strong) NSMutableString * stringMStrong;
- (void)testMStringUseRetainOrCopyOrMutableCopy
{
NSMutableString * stringM = [NSMutableString stringWithFormat:@"%@", @"publicCoderLN"];
LNLog(@"originString", stringM);
NSLog(@"--------");
// 场景1:定义NSString类型的属性(copy/strong修饰词)指向可变对象.
self.stringCopy = stringM;
LNLog(@"_stringCopy", _stringCopy);
self.stringStrong = stringM;
LNLog(@"_stringStrong", _stringStrong);
NSLog(@"--------");
// 场景2:对可变类型NSMutableString,进行retain、copy、mutableCopy操作
NSMutableString * stringMRetain = [stringM retain];
LNLog(@"stringMRetain", stringMRetain);
NSMutableString * stringMCopy = [stringM copy];
LNLog(@"stringMCopy", stringMCopy);
NSMutableString * stringMMutableCopy = [stringM mutableCopy];
LNLog(@"stringMMutableCopy", stringMMutableCopy);
/
打印:
originString: 内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a78, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
_stringCopy: 内存地址:0x604000236da0, 指针地址:0x7f94d4603980, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
_stringStrong: 内存地址:0x604000245cd0, 指针地址:0x7f94d4603988, 真实类型:__NSCFString, 引用计数:2, 值:publicCoderLN
--------
stringMRetain: 内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a70, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN
stringMCopy: 内存地址:0x60000003b080, 指针地址:0x7ffee1d38a68, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
stringMMutableCopy: 内存地址:0x600000456860, 指针地址:0x7ffee1d38a60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
--------
分析:
1、定义NSString类型的属性(copy/strong修饰词)指向可变对象时,可以看到copy修饰的生成了新的内存地址。
2、对可变对象stringM进行操作,retain操作引用计数+1,而copy/mutableCopy操作均产生了新指针和新的指针指向的内存地址,都为深复制,。
*/
NSLog(@"--------");
// 场景4:定义NSString类型的属性,修饰词用把copy换成strong后,再修改原对象有什么影响❓
[stringM appendString:@"+CoderLN"];
LNLog(@"originString", stringM);
LNLog(@"_stringCopy", _stringCopy);
LNLog(@"_stringStrong", _stringStrong);
LNLog(@"stringMRetain", stringMRetain);
LNLog(@"stringMCopy", stringMCopy);
LNLog(@"stringMMutableCopy", stringMMutableCopy);
/
打印:
originString: 内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a78, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN+CoderLN【改原对象】
_stringCopy: 内存地址:0x604000236da0, 指针地址:0x7f94d4603980, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
_stringStrong: 内存地址:0x604000245cd0, 指针地址:0x7f94d4603988, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN+CoderLN
- - -
stringMRetain: 内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a70, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN+CoderLN
stringMCopy: 内存地址:0x60000003b080, 指针地址:0x7ffee1d38a68, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
stringMMutableCopy: 内存地址:0x600000456860, 指针地址:0x7ffee1d38a60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
分析:
1.上面copy修饰的指向可变对象,生成了新的内存地址,而strong修饰的和原对象stringM的内存地址一样,所以原对象的修改,strong修饰的也被改变了。
2.对NSMutableString属性进行copy、mutableCopy操作,均生成了新的内存地址(指向另一个对象),都为深复制,所以原对象改变不会对他们有影响;
*/
}
面试题:为什么定义NSString要使用copy而不建议使用strong❓
分析:将对象声明为NSString不可变类型时,都不希望它改变(外界修改了,不影响自身的值),从上面打印结果可以看出,strong修饰的NSString类型属性遇到赋值可变类型时,修改原对象的值,_stringStrong也同样改变了,而copy修饰的string的值没有改变。
回答:定义NSString使用copy,为了防止遇到把一个可变字符串在未使用copy方法时赋值给这个字符串对象后,再修改原字符串时(可变字符),本字符串也会被修改的情况发生(就用strong的情况)。
总结:对可变对象
1、strong、retain会使原对象的引用计数+1,指针指向的内存地址和原对象还是一样的;copy、mutableCopy对原对象的引用计数没有影响,使新生成的对象的引用计数为1;
2、进行操作copy、mutableCopy均为深复制,即复制了对象的指针,也复制了指针指向的内存地址;
定义NSMutableString类型的属性,如果把修饰词strong换成copy有什么影响。
@property (nonatomic, copy) NSMutableString * stringMCopy;
@property (nonatomic, strong) NSMutableString * stringMStrong;
- (void)testMutableStringCopyOrStrong
{
NSMutableString *stringM = [[NSMutableString alloc] initWithFormat:@"%@",@"publicCoderLN"];
LNLog(@"stringM", stringM);
self.stringMCopy = stringM;
LNLog(@"_stringMCopy", _stringMCopy);
self.stringMStrong = stringM;
LNLog(@"_stringMStrong", _stringMStrong);
// 面试题:定义NSMutableString类型的属性,如果把修饰词strong换成copy有什么影响❓
//[self.stringMCopy appendString:@"+CoderLN"];// 会crash
//[self.stringMStrong appendString:@"+CoderLN"];// strong修饰的正常运行.
//[[self.stringMCopy mutableCopy] appendString:@"+CoderLN"];// 返回的可变类型.
/
打印:
stringM: 内存地址:0x60400025f2f0, 指针地址:0x7ffee4502a78, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
_stringMCopy: 内存地址:0x60000022bda0, 指针地址:0x7f7f90525de0, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
_stringMStrong: 内存地址:0x60400025f2f0, 指针地址:0x7f7f90525de8, 真实类型:__NSCFString, 引用计数:2, 值:publicCoderLN
总结:
1.定义NSMutableString使用copy,copy修饰后的对象生成了新的内存地址,为深复制。对新生成的对象进行appendString:操作会发生crash,说明其实copy修饰后返回的是不可变类型。
2.定义NSMutableString使用strong,strong修饰后对象的内存地址和原对象的内存地址相同,为浅复制,可以进行appendString:操作,说明其实strong修饰后返回的还是是可变类型。
3.回答:定义NSMutableString使用修饰词strong换成copy,如果对copy后的不可变类型,再进行可变的修改操作,就会造成崩溃。
*/
}
集合类对象的copy与mutableCopy
定义NSArray类型的属性(copy/strong修饰词)指向不可变对象
@property (nonatomic ,copy) NSArray *arrayCopy;
@property (nonatomic ,strong) NSArray *arrayStrong;
- (void)testArrayUseCopyOrMutableCopy
{
NSArray *array = @[@"A", @"B"];
LNLog(@"originAry", array);
NSLog(@"--------");
// 场景1:定义NSArray类型的属性(copy|strong修饰)指向不可变对象
self.arrayCopy = array;
LNLog(@"_arrayCopy", _arrayCopy);
self.arrayStrong = array;
LNLog(@"_arrayStrong", _arrayStrong);
NSLog(@"--------");
// 场景2:对不可变类型数组,进行copy|mutableCopy操作
NSArray * arrayCopy = [array copy];// 浅
LNLog(@"arrayCopy", arrayCopy);
NSMutableArray * arrayMCopy = [array mutableCopy];// 深
LNLog(@"arrayMCopy", arrayMCopy);
NSLog(@"--------");
// 场景3:对数组不可变类型,进行浅深复制
NSArray * arrayCopy1 = [array copyWithZone:nil];// 浅
LNLog(@"arrayCopy1", arrayCopy1);
NSArray * arrayCopy2 = [[NSArray alloc] initWithArray:array copyItems:NO];// 浅
LNLog(@"arrayCopy2", arrayCopy2);
NSArray * arrayCopy3 = [[NSArray alloc] initWithArray:array copyItems:YES];// 单层深复制
LNLog(@"arrayCopy3", arrayCopy3);
// 真正的深复制:先归档再解档
NSArray * arrayCopy4 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:array]];
LNLog(@"arrayCopy4", arrayCopy4);
NSLog(@"--------");
// 场景4:验证数组中的元素均为指针复制
NSLog(@"%p",array[0]);
NSLog(@"%p",arrayCopy[0]);
NSLog(@"%p",arrayMCopy[0]);
NSLog(@"--------");
// 面试题:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓
// 指向不可变对象,外界修改原对象array
array = [array arrayByAddingObject:@"Public-CoderLN"];
LNLog(@"originArray", array);
LNLog(@"_arrayCopy", _arrayCopy);
LNLog(@"_arrayStrong", _arrayStrong);
NSLog(@"--------");
// 场景6:定义NSArray类型的属性(copy|strong修饰词)指向可变对象
NSMutableArray * arrayM = [[NSMutableArray alloc] initWithArray:@[@"a",@"b"]];
self.arrayCopy = arrayM;
self.arrayStrong = arrayM;
// 指向可变对象,外界修改原对象arrayM
[arrayM removeAllObjects];
LNLog(@"originArrayM", arrayM);
LNLog(@"_arrayCopy", _arrayCopy);
LNLog(@"_arrayStrong", _arrayStrong);
}
/
打印:
originAry: 内存地址:0x604000037f00, 指针地址:0x7ffee8599a50, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
--------
场景1:
_arrayCopy: 内存地址:0x604000037f00, 指针地址:0x7fa41771edf0, 真实类型:__NSArrayI, 引用计数:2, 值:(A,B)
_arrayStrong: 内存地址:0x604000037f00, 指针地址:0x7fa41771edf8, 真实类型:__NSArrayI, 引用计数:3, 值:(A,B)
分析:定义NSArray类型的属性,指向不可变类型,copy和strong修饰均没有生成新的内存地址,只是复制了对象的指针且引用计数+1,copy修饰为浅复制;
--------
场景2:
arrayCopy: 内存地址:0x604000037f00, 指针地址:0x7ffee8599a48, 真实类型:__NSArrayI, 引用计数:4, 值:(A,B)
rrayMCopy: 内存地址:0x60000044c090, 指针地址:0x7ffee8599a40, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
分析:对原对象array进行Copy操作,没有生成新的内存地址且引用计数+1,为浅复制;进行mutableCopy操作,生成了新的内存地址,所以为深复制。
--------
场景3:
arrayCopy1: 内存地址:0x604000037f00, 指针地址:0x7ffee8599a38, 真实类型:__NSArrayI, 引用计数:5, 值:(A,B)
arrayCopy2: 内存地址:0x604000037f00, 指针地址:0x7ffee8599a30, 真实类型:__NSArrayI, 引用计数:6, 值:(A,B)
arrayCopy3: 内存地址:0x6000004200c0, 指针地址:0x7ffee8599a28, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
arrayCopy4: 内存地址:0x604000038060, 指针地址:0x7ffee8599a20, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
分析:对原对象array进行 copyWithZone:和initWithArray:copyItems:参数为NO 没有生成新的内存地址,都为浅复制,
对原对象array进行 initWithArray:copyItems:参数为YES 和 先归档再解档操作,均生成了新的内存地址,都为深复制,
--------
场景4:
0x10766a230// 原数组array[0]
0x10766a230// arrayCopy[0]
0x10766a230// arrayMCopy[0]
分析:打印数组中的元素内存地址都是同一个,不管是对原数组做copy/mutableCopy操作,数组中的元素均为浅复制。
--------
【面试题】:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓
//指向不可变对象,外界修改原对象array
originArray: 内存地址:0x60000044c060, 指针地址:0x7ffee8599a50, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B,"Public-CoderLN")【改原对象】
_arrayCopy: 内存地址:0x604000037f00, 指针地址:0x7fa41771edf0, 真实类型:__NSArrayI, 引用计数:6, 值:(A,B)
_arrayStrong: 内存地址:0x604000037f00, 指针地址:0x7f9395d0a6f8, 真实类型:__NSArrayI, 引用计数:6, 值:(A,B)
--------
//指向可变对象,外界修改原对象arrayM
originArrayM: 内存地址:0x60000044c1b0, 指针地址:0x7ffeea6c7a18, 真实类型:__NSArrayM, 引用计数:2, 值:( )
_arrayCopy: 内存地址:0x600000420160, 指针地址:0x7fa41771edf0, 真实类型:__NSArrayI, 引用计数:1, 值:(a,b)
_arrayStrong: 内存地址:0x60000044c1b0, 指针地址:0x7fa41771edf8, 真实类型:__NSArrayM, 引用计数:2, 值:( )
*/
面试题:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓
分析:
1、指向不可变对象,外界修改原对象array,这里原对象本身为不可变对象,对它进行修改,系统又重新分配了内存地址与_arrayCopy的内存地址不一样,所以只有原对象改变。
2、指向可变对象,外界修改原对象arrayM,这里原对象本身为可变对象,对它进行修改,由于copy修饰做了深复制生成了新的内存地址,而strong修饰内存地址和原对象arrayM相同,所以原对象改变,strong修饰的也跟着改变了。
回答:定义NSArray使用copy,为了防止遇到把一个可变数组在未使用copy方法时赋值给这个对象后,再修改原数组时,这个对象也会被修改的情况发生(就如strong的情况会被修改)。
定义NSMutableArray类型的属性(copy/strong修饰词)指向可变对象
@property (nonatomic ,copy) NSMutableArray *arrayMCopy;
@property (nonatomic ,strong) NSMutableArray *arrayMStrong;
- (void)testMutableArrayUseCopyOrMutableCopy
{
NSMutableArray * arrayM = [NSMutableArray arrayWithArray:@[@"A",@"B"]];
LNLog(@"originAry", arrayM);
// 场景1:给定义NSMutableArray类型的属性(copy/strong修饰词)赋值可变对象
self.arrayMCopy = arrayM;
LNLog(@"_arrayCopy", _arrayCopy);
self.arrayMStrong = arrayM;
LNLog(@"_arrayMStrong", _arrayMStrong);
NSLog(@"--------");
// 场景2:对可变类型数组,进行copy、mutableCopy操作
NSArray * arrayC1 = [arrayM copy];
LNLog(@"arrayC1", arrayC1);
NSArray * arrayC2 = [arrayM mutableCopy];
LNLog(@"arrayC2", arrayC2);
NSMutableArray * arrayM1 = [arrayM copy];
LNLog(@"arrayM1", arrayM1);
NSMutableArray * arrayM2 = [arrayM mutableCopy];
LNLog(@"arrayM2", arrayM2);
NSLog(@"--------");
// 场景3:NSCopying和NSMutableCopying
NSMutableArray * shallowCopyArrayM = [arrayM mutableCopyWithZone:nil];// 浅复制
LNLog(@"shallowCopyArrayM", shallowCopyArrayM);
NSMutableArray * deepCopyArrayM1 = [[NSMutableArray alloc] initWithArray:arrayM copyItems:NO];
LNLog(@"deepCopyArrayM1", deepCopyArrayM1);
NSMutableArray * deepCopyArrayM2 = [[NSMutableArray alloc] initWithArray:arrayM copyItems:YES];
LNLog(@"deepCopyArrayM2", deepCopyArrayM2);
// 面试题:定义NSMutableArray类型的属性,修饰词把strong换成copy有什么影响❓
//[self.arrayMCopy removeLastObject];// 会crash
[self.arrayMStrong removeLastObject];// 正常运行
}
/
打印:
originAry: 内存地址:0x604000253740, 指针地址:0x7ffee591da60, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
_arrayCopy: 内存地址:0x0, 指针地址:0x7fa029f2b400, 真实类型:(null), 引用计数:0, 值:(null)
_arrayMStrong: 内存地址:0x604000253740, 指针地址:0x7fa029f2b418, 真实类型:__NSArrayM, 引用计数:2, 值:(A,B)
--------
arrayC1: 内存地址:0x60000022d780, 指针地址:0x7ffee591da58, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
arrayC2: 内存地址:0x6000004573d0, 指针地址:0x7ffee591da50, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
arrayM1: 内存地址:0x60000022d680, 指针地址:0x7ffee591da48, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
arrayM2: 内存地址:0x600000457520, 指针地址:0x7ffee591da40, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
--------
shallowCopyArrayM: 内存地址:0x604000253a70, 指针地址:0x7ffee591da38, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
deepCopyArrayM1: 内存地址:0x604000253d10, 指针地址:0x7ffee591da30, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
deepCopyArrayM2: 内存地址:0x600000457430, 指针地址:0x7ffee591da28, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
分析:
1.对NSMutableArray类型的属性,copy和mutableCopy操作都是深复制。
*/
面试题:定义NSMutableArray类型的属性,修饰词把strong换成copy有什么影响❓
回答:定义NSMutableArray类型的属性,修饰词用把strong换成copy,遇到可变对象赋值,再对_arrayMCopy做可变操作会崩溃,因为copy后返回的是不可变类型。reason: '-[__NSArrayI removeLastObject]: unrecognized selector sent to instance 0x600000426f00'
整体总结一下:不管是非集合类还是集合类
浅复制深复制示例.png
-
对于不可变对象,使用copy是浅复制
(指向不可变对象为浅复制;指向可变对象时生成了新的内存地址,为深复制)
,使用mutableCopy是深复制。 - 对于可变对象,不管使用copy或者mutableCopy都是深复制。
不知名开发者
- 各位厂友,由于「时间 & 知识」有限,总结的文章难免有「未全、不足」,该模块将系统化学习,后替换、补充文章内容 ~
- 熬夜写者不易,学会交流和分享。
网友评论