天天看点

iOS中copy和mutableCopy的详细分析

copy和mutableCopy在实际开发中常用到,同时,copy属性修饰符也是我们在定义NSString、NSArray和block等需要用到的,要较好地掌握它们,需要从原理上去分析于理解,本文侧重于分析copy和mutableCopy是如何实现的,进一步讲解使用copy和mutableCopy时应该注意的细节问题

本文分成两个部分来讲解说明,第一个是copy和mutableCopy方法的使用,第二个部分是copy作为属性修饰符 1、copy和mutableCopy方法 对象如果要使用这两个方法,应该遵守NSCopying和NSMutableCopying方法,并且实现copyWithZone和mutableCopyWithZone方法。自定义一个需要使用copy的类的时候需要进行上述操作。这里我们通过代码来进行理解和学习,就使用已经默认进行了上述操作的NSArray和NSMutableArray类来进行说明。下面进行copy功能的测试:(所建的项目是Single-View模板) (1) 首先进行的是以NSMutableArray对象为源的测试:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 以可变的NSMutableArray作为对象源
    NSMutableArray *arrayM = [NSMutableArray arrayWithObjects:@"copy",@"mutableCopy", nil];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",arrayM,arrayM,arrayM.class);
    
    // 将对象源copy到可变对象
    NSMutableArray *array1 = [arrayM copy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array1,array1,array1.class);
    
    // 将对象源mutableCopy到可变对象
    NSMutableArray *array2 = [arrayM mutableCopy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array2,array2,array2.class);
    
    // 将对象源copy到不可变对象
    NSArray *array3 = [arrayM copy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array3,array3,array3.class);
    
    // 将对象源mutablCopy到不可变对象
    NSArray *array4 = [arrayM mutableCopy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array4,array4,array4.class);
    
    NSLog(@"-----------------------------------------------------------------------");

    // 修改对象源,然后再次对这五个对象进行打印分析
    [arrayM addObject:@"test"];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",arrayM,arrayM,arrayM.class);
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array1,array1,array1.class);
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array2,array2,array2.class);
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array3,array3,array3.class);
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array4,array4,array4.class);
}
           

通过上面的代码测试,我们得到的结果是:

iOS中copy和mutableCopy的详细分析

(2)其次进行的是以NSArray为源的测试

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 以不可变的NSArray作为对象源
    NSArray  *arrayM = [NSArray arrayWithObjects:@"copy",@"mutableCopy", nil];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",arrayM,arrayM,arrayM.class);
    
    // 将对象源copy到可变对象
    NSMutableArray *array1 = [arrayM copy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array1,array1,array1.class);
    
    // 将对象源mutableCopy到可变对象
    NSMutableArray *array2 = [arrayM mutableCopy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array2,array2,array2.class);
    
    // 将对象源copy到不可变对象
    NSArray *array3 = [arrayM copy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array3,array3,array3.class);
    
    // 将对象源mutablCopy到不可变对象
    NSArray *array4 = [arrayM mutableCopy];
    NSLog(@"内容:%@ 对象地址:%p 对象所属类:%@",array4,array4,array4.class);
}
           

通过上面的测试代码得到的结果是:

iOS中copy和mutableCopy的详细分析

从上面我们可以分析到几点: 1、通过对对象的所属类的打印,我们可以看到,当使用copy的时候,所得到的对象是__NSArrayI类型的,也就是不可变类型,使用mutableCopy的时候,所得到的对象是__NSArrayM,也就是可变类型,从这可以得出,使用copy将获得不可变对象,使用mutableCopy将获得可变对象。 2、通过两种对象源的测试,我们发现, 几乎所有copy和mutableCopy出来的对象的地址都和对象源的地址不同,只有一种情况例外,就是当不可变的对象源使用copy操作所获得的对象是和对象源的地址相同的,那么在这个点我们可以这么理解:通过第一点我们知道,copy出来的对象类型为不可变的,那么这种特殊情况就是不可变对象到不可变对象的时候,copy所产生的效果被称为“浅复制”,“浅复制”其实就是指针的复制,我们可以这么理解为何只有这种情况进行的是”浅复制“:对象源和赋值的对象都是不可变的,这两个对象是不可能不相同的,所以在系统优化的角度来说,没必要多创建一个对象。其他情况下copy或者是mutableCopy所产生的效果叫“深复制”,由于这些情况下都存在可变的对象,也就是在copy或者mutableCopy后我们可以通过代码来改变其中之一(或者两者)的值,这样的操作就会导致两个对象不一样。

2、copy属性修饰符 当我们要定义一个字符串属性的时候,大多数时候都是使用copy来修饰,下面将会通过代码进行详细分析和说明: (1)属性为不可变对象的时候    

@interface ViewController ()
@property (nonatomic,copy) NSArray *names;
@end
           
- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableArray *arrayM = [NSMutableArray arrayWithObjects:@"1",@"2", nil];
    NSLog(@"内容:%@ 对象的地址:%p 对象的所属类:%@",arrayM,arrayM,arrayM.class);
    // 将可变类型的对象赋值给属性names
    self.names = arrayM;
    NSLog(@"内容:%@ 对象的地址:%p 对象的所属类:%@",self.names,self.names,self.names.class);
    // 改变原对象arrayM,然后再打印两个对象进行研究
    [arrayM addObject:@"3"];
    NSLog(@"内容:%@ 对象的地址:%p 对象的所属类:%@",arrayM,arrayM,arrayM.class);
    NSLog(@"内容:%@ 对象的地址:%p 对象的所属类:%@",self.names,self.names,self.names.class);
}
           

代码所对应的打印结果为:

iOS中copy和mutableCopy的详细分析

通过打印结果我们可以发现: 1、无论一个对象是可变还是不可变的,赋值给使用copy定义的属性后,属性所指向的对象都是不可变的,和我们在讨论的第一个知识点中所描述的一样,copy操作所获得的都是不可变的对象。 2、改变了原来的对象后,对属性是没有影响的,因为复制出来一个新的对象赋值给了属性。

(2)属性为可变的 这时候我们只需要改变属性为NSMutableArray即可 测试后所打印的结果为:

iOS中copy和mutableCopy的详细分析

在这里我们发现一个很奇怪的现象,本来我们定义的names是NSMutableArray的类型,但是打印出来却是NSArray的类型,这个原因是我们定义属性使用的是copy,使用copy所获得的对象都是不可变的对象,在代码中,赋值语句实际上是先将原来的对象复制一份,然后才赋值给了属性,而复制得到的对象由于是copy操作得来的,自然是不可变类型,所以,无论我们定义属性为可变还是不可变,最后真实的类型都是不可变的,这就发生了一个问题了:如果我们定义一个可变类型(例如NSMutableString和NSMutableArray)用的是copy,那么我们在后续的操作中就会犯错,例如,使用属性去进行可变类型的操作(添加对象,删除对象,插入对象等),表面上看是符合逻辑的,同时xcode也会提示出方法,但是到运行时候就会崩溃,因为实际的类型不是可变的。 为了防止这种现象的发生,我们在定义这种可变对象的时候,都不能使用copy,大多数情况下使用strong。相反,对于不可变的对象,大多数应该是用copy,不使用strong(strong相当于retain的作用效果)下面就利用代码进行分析和说明: 代码于上述一致,只需要改变copy为strong 打印出来的结果为:

iOS中copy和mutableCopy的详细分析

可以发现赋值过后,如果去改动原来的对象,那么属性也是会随着原来的对象的改变而改变的,也就是使用strong,那么指向的是同一个对象。这是我们不希望看到的现象。 在实际开发中,还有很多地方需要我们使用copy去定义,只要我们从原理上去分析,都能够很好地准确定位。