美文网首页iOS面试iosiOS开发常用知识点
copy修饰NSArray strong修饰NSMutableA

copy修饰NSArray strong修饰NSMutableA

作者: 笨笨编程 | 来源:发表于2017-09-22 00:35 被阅读459次
假装很认真的样子

阴差阳错,前两天和一个小伙伴在一起聊天。聊到关于 copystrong 的问题。这个在ARC[Automatic Reference Counting)]下慢慢淡化的一个东东。交流之中让我受益匪浅,原来 copystrong 还可以这么玩。


以下内容在 demo 中均有体现

1.首先我们先看一下到底出现了什么问题

使用 copy 修饰这个可变数组
@property (copy, nonatomic) NSMutableArray *copAry;

// 直接崩溃测试
- (void)testCash {
    NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
    self.copAry = arr;
    [self.copAry removeObject:@1]; // 直接崩溃
    NSLog(@"self.copyAry = %@", self.copAry);
}
报错reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'

那么为什么使用 strong 来修饰就不会有这个问题呢。
我们来看一个例子:

  • 定义一个CJPerson类,.m 中先不实现 setterr 方法

    CJPerson.h
  • 在控制器看一下打印结果

    NSMutableArray *names = [@[@"zhangsan"] mutableCopy];
    CJPerson *person = [[CJPerson alloc] init];
    person.sAry = names;// strong
    person.cAry = names;// copy
    
    [names addObject:@"lisi"];
    NSLog(@"sAry = %@, cAry = %@", person.sAry, person.cAry);
    /* 输出结果:
     sAry = (
         zhangsan,
         lisi
     ), cAry = (
         zhangsan
     )
     */
  • 归根结底之所以出现这样问题,那是因为 ARC 情况下 strongcopy 对属性 setter 方法重写的区别。
    strong: setter 时调用了 [sAry retain] 方法,实现了指针拷贝,也就是浅拷贝。
    copy: setter 时调用了 [cAry copy] 方法,实现了内容拷贝,也就是深拷贝。
    也就是在 .m 文件中系统默认帮我们实现的是:
    CJPerson.m
  • 具体可以看内存地址图详解:(简单粗糙,欢迎大神指正)


    内存地址分析.png

2.那么这个与 copy 修饰 NSMutableArray 导致崩溃有什么关系呢?

原来不管是集合类对象(NSArray,NSDictionary,NSSet...),还是非集合类对象(NSString),接收到copy或者mutableCopy消息时,都需遵循以下准则:

  • copy 返回的都是不可变对象,所以如果对 copy 返回值去调用可变对象的接口就会 crash
  • mutableCopy 返回的都是可变对象
    所以在 - (void)testCash 方法中执行到 self.copAry = arr; ARC 环境下setter 方法执行了 copy 方法,导致原本 NSMutableArray类型数组变成 NSArray 类型,在调用removeObject:方法时,自然会出现这个错误reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'

3.那么 NSArray 类型为什么需要使用 copy 来修饰

  • 我们继续使用 copystrong 来定义变量
@property (strong, nonatomic) NSArray *arr1;
@property (copy, nonatomic) NSArray *arr2;
// 为什么 NSArray 需要使用 copy
- (void)testUserCopyWithAry {
    NSMutableArray *arr = [NSMutableArray arrayWithObjects:@(1), @(2), @(3), nil];
    self.arr1 = arr;
    self.arr2 = arr;
    
    [arr addObject:@(4)];
    NSLog(@"arr1 = %@, arr2 = %@", self.arr1, self.arr2);
    
    /* 输出结果:
     arr1 = (
         1,
         2,
         3,
         4
     ), arr2 = (
         1,
         2,
         3
     )
     */
}

简直白的说就是:如果定义一个数组,使用 strong 来修饰的话,如果这个数组在外界被修改的话,那么这个用 strong 修改的数组变量的值也会跟着变化。为什么?还是因为 strong 进行了指针拷贝。在内存中,两个变量指向的是同一块内存地址。所以为了避免值在外接发生改变而影响自身值的变化,我们通常选择使用 copy 进行修饰。

4.接着再上两个测试例子,对比看输出结果

- (void)test01 {
    NSArray *array = @[@1,@2,@3,@4];
    
    NSArray *copyArr = [array copy];
    NSArray *mCopyArr = [array mutableCopy];
    
    NSMutableArray *mcArr = [array copy];
    NSMutableArray *mmCopyArr = [array mutableCopy];
    
    NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
    
    /*  输出结果
     array:     0x60000024ce10
     copyArr:   0x60000024ce10
     mCopyArr:  0x60000024cd80
     mcArr:     0x60000024ce10
     mmCopyArr: 0x60000024ce70
     */
    
}

- (void)test02 {
    NSArray *tarray = @[@1,@2,@3,@4];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    [array addObjectsFromArray:tarray];
    
    NSArray *copyArr = [array copy];
    NSArray *mCopyArr = [array mutableCopy];
    
    NSMutableArray *mcArr = [array copy];
    NSMutableArray *mmCopyArr = [array mutableCopy];
    
    NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
    /* 输出结果
     array:     0x60000024cd20
     copyArr:   0x60000024cc90
     mCopyArr:  0x60000024ce40
     mcArr:     0x60000024cde0
     mmCopyArr: 0x60000024d020
     */
}

小结一下:

  • NSArray的copy ---->指针拷贝---->浅拷贝
  • NSArray的mutableCopy,NSMutableArray的copy, NSMutableArray的mutableCopy 均为深拷贝,即内容拷贝。
  • 关于NSString(非集合类对象),NSDictionary及其对应的可变类型都可以此类推。

5.浅拷贝、深拷贝

全篇在讲浅拷贝、深拷贝,追究他们追究到底是什么。
段子手的理解:浅复制好比你和你的影子,你完蛋,你的影子也完蛋。深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。
浅拷贝

  • 浅拷贝就是对内存地址的复制,目标对象和原对象指向同一片内存地址。
    注意
    多个对象共用一块地址时,当内存销毁的时候,指向这片内存地址的几个指针需要重新定义才可以使用,否则出现野指针现象。
    iOS的浅拷贝 中,通常会使用 retain 关键字进行引用计数。因为他既可以让几个指针共同指向同一内存地址,也可以在 release 的时候 由于计数的存在,不会让内存销毁,从而出现野指针的现象。

深拷贝

  • 深拷贝也就是内容拷贝。目标对象虽然和原对象的值一样,但是所指向的内存地址不一样。可以说深拷贝把原对象地址也拷贝了,而内存地址是自主分配的。因内存地址不一样,两个对象也就互不影响、互不干涉了。
    iOS的深拷贝 中,通常会使用 copymutableCopy 方法
// 深拷贝
- (void)testDeepCopy {
    NSString *str = @"abcdefg";
    NSString *cStr = [str copy];
    NSMutableString *mStr = [str mutableCopy];
    [mStr appendString:@"!!"];
    NSLog(@"\n str = %@ = %p,\n cStr = %@ = %p,\n mStr = %@ = %p", str, str, cStr, cStr, mStr, mStr);
}
/* 输出结果:
 str = abcdefg = 0x109dd8090,
 cStr = abcdefg = 0x109dd8090,
 mStr = abcdefg!! = 0x604000057e50
*/

再次验证了第 4 个模块


番外篇
浅拷贝的 retain 和 深拷贝中提到的 copy 有什么区别呢
可以观看这篇文章:copy 和 retain 到底有啥区别

6.拷贝构造

当然在 iOS 中并不是所有的对象都支持copymutableCopy,遵守 NSCopying 协议的类可以发送 copy 消息,遵守NSMutableCopying 协议的类才可以发送 mutableCopy 消息。
假如发送了一个没有遵守上诉两协议而发送 copy 或者 mutableCopy, 那么就会发生异常。但是默认的 iOS 类并没有遵守这两个协议。如果想自定义一下 copy 那么就必须遵守 NSCopying, 并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守 NSMutableCopying, 并且实现 mutableCopyWithZone: 方法。

  • 自定义 model,遵守 NSCopyingNSMutableCopying 协议
@interface CJObj : NSObject<NSCopying, NSMutableCopying>
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *icon;
@end
  • 实现:


    CJObj.m
  • ViewController 中打印结果

// 拷贝构造
- (void)copyConstruct {
    CJObj *obj = [[CJObj alloc] init];
    obj.name = @"zhangsan";
    obj.icon = @"icon";
    
    NSMutableArray *arr = [NSMutableArray array];
    NSMutableArray *copyAry = [NSMutableArray array];
    [arr addObject:obj];
    
    [copyAry addObject:[arr[0] copy]];
    //[copyAry addObject:[arr[0] mutableCopy]];

    CJObj *obj2 = arr[0];
    obj2.name = @"lisi";
    obj2.icon = @"obj2_icon";
    
    NSLog(@"arr.name = %@, arr.icon = %@", ((CJObj *)arr[0]).name, ((CJObj *)arr[0]).icon);
    NSLog(@"copyArr.name = %@, copyArr.icon = %@", ((CJObj *)[copyAry objectAtIndex:0]).name, ((CJObj *)[copyAry objectAtIndex:0]).icon);
    /* 输出结果:
         arr.name = lisi, arr.icon = obj2_icon
         copyArr.name = zhangsan, copyArr.icon = icon
     */
}
  • 内存地址图详解:(简单粗糙,欢迎大神指正)


    拷贝构造

以上内容在 demo 中均有体现

感谢:

相关阅读:

相关文章

网友评论

    本文标题:copy修饰NSArray strong修饰NSMutableA

    本文链接:https://www.haomeiwen.com/subject/jenzsxtx.html