善恶众相

  • 首页

  • 分类

  • 归档

iOS Copy操作那些坑

发表于 2016-08-01 更新于 2019-08-07 分类于 iOS
copy与mutableCopy操作在面试过程中经常被问到,而要想充分的理解这两种操作,不仅要从方法本身入手,还有区分操作对象的类型:容器类还是字符串类型;可变类型还是不可变类型。

容器类行NSArray等

操作对象为不可变数组

由于对于NSString苹果做了一些特殊的优化,我们先从未被优化的容器类NSArray(或NSDictionary、NSSet等)入手,看如下代码:

1
2
3
4
5
6
7
8
{
NSArray *array = [NSArray arrayWithObject:@"1"];
NSArray *array1 = [array copy];
NSArray *array2 = [array mutableCopy];
NSMutableArray *array3 = [array copy];
NSMutableArray *array4 = [array mutableCopy];
NSLog(@"\narrayWithObject array:\n%@:%p\n%@:%p\n%@:%p\n%@:%p\n%@:%p", array.class, array, array1.class, array1, array2.class, array2, array3.class, array3, array4.class, array4);
}
1
2
3
4
5
6
7
对应输出为:
arrayWithObject array:
__NSArrayI:0x7faee1500290
__NSArrayI:0x7faee1500290
__NSArrayM:0x7faee15225d0
__NSArrayI:0x7faee1500290
__NSArrayM:0x7faee1522240

对于非mutable类型的NSArray进行copy操作只是对引用计数进行+1操作,类似于retain,也就是所谓的浅拷贝,返回的类型是非mutable类型的NSArray;进行mutableCopy操作才会重新申请内存创建对象,也就是所谓的深拷贝,返回的类型是mutable类型的NSArray;

操作对象为可变数组

1
2
3
4
5
6
7
8
{
NSMutableArray *array = [NSMutableArray arrayWithObject:@"1"];
NSArray *array1 = [array copy];
NSArray *array2 = [array mutableCopy];
NSMutableArray *array3 = [array copy];
NSMutableArray *array4 = [array mutableCopy];
NSLog(@"\narrayWithObject mutable array:\n%@:%p\n%@:%p\n%@:%p\n%@:%p\n%@:%p", array.class, array, array1.class, array1, array2.class, array2, array3.class, array3, array4.class, array4);
}
1
2
3
4
5
6
7
对应输出为:
arrayWithObject mutable array:
__NSArrayM:0x7faee15225d0
__NSArrayI:0x7faee151a540
__NSArrayM:0x7faee1522240
__NSArrayI:0x7faee150fb80
__NSArrayM:0x7faee15222d0

对于mutable类型的NSArray进行copy操作时,会重新申请内存创建对象,进行深拷贝,返回的类型是非mutable类型的NSArray;进行mutableCopy操作时,也会重新申请内存创建对象,进行深拷贝,返回的类型是mutable类型的NSArray。

根据以上结论,我们观察如下属性声明:

1
2
3
4
5
6
7
8
9
@property(nonatomic, copy)NSMutableArray *array;
//该属性对应的setter方法为:
- (void)setArray:(NSMutableArray *)array
{
if (_array != array){
[_array release];
_array = [array copy];
}
}

此处需要说明的是,无论你用什么类型的NSArray进行赋值,最终执行的都是copy操作,返回的都是非mutable类型的NSArray,而不是声明的mutable类型,进行赋值操作后不能再调用mutable类型的remove/add方法,否则会因为未找到消息而出现崩溃。

字符串类型NSString

接下来我们看一下对NSString类型进行copy/mutableCopy操作,会出现什么结果,我们逐一对一下情况进行分析:

操作对象为常亮字符串

1
2
3
4
5
6
7
8
{
NSString *string = @"1";
NSString *string1 = [string copy];
NSString *string2 = [string mutableCopy];
NSMutableString *string3 = [string copy];
NSMutableString *string4 = [string mutableCopy];
NSLog(@"\nconst string:\n%@:%p\n%@:%p\n%@:%p\n%@:%p\n%@:%p", string.class, string, string1.class, string1, string2.class, string2, string3.class, string3, string4.class ,string4);
}
1
2
3
4
5
6
7
对应输出为:
const string:
__NSCFConstantString:0x103713318
__NSCFConstantString:0x103713318
__NSCFString:0x7febf348ff70
__NSCFConstantString:0x103713318
__NSCFString:0x7febf340fe00

通过@""初始化的NSString的真正类型是__NSCFConstantString,我们暂且称该类型的字符串为常量字符串,同时观察该类型的内存地址0x103713318,我们可以猜测他说被分配在了常量区(类似于C语言中的const常量),而对常量字符串进行copy操作时,只是进行了简单的指针指向操作,不会引起计数+1,也不会重新创建对象,这是因为常量字符串只有在程序结束的时候才会被释放(至于苹果对该类型的常量字符串做了何等的优化有待我们继续深入探究),所以当我们用__weak声明的对象指向常量字符串时,不会因为常量字符串没有被强引用而引起编译器警告;而对常量字符串进行mutableCopy操作时,会重新申请内存创建对象,进行深拷贝,返回的类型是__NSCFString类型的字符串,即为NSMutableString类型。

操作对象为非可变字符串

1
2
3
4
5
6
7
8
{
NSString *string = [NSString stringWithFormat:@"%i", 1];
NSString *string1 = [string copy];
NSString *string2 = [string mutableCopy];
NSMutableString *string3 = [string copy];
NSMutableString *string4 = [string mutableCopy];
NSLog(@"\nstringWithFormat string:\n%@:%p\n%@:%p\n%@:%p\n%@:%p\n%@:%p", string.class, string, string1.class, string1, string2.class, string2, string3.class, string3, string4.class ,string4);
}
1
2
3
4
5
6
7
对应输出为:
stringWithFormat string:
NSTaggedPointerString:0xa000000000000311
NSTaggedPointerString:0xa000000000000311
__NSCFString:0x7febf481d320
NSTaggedPointerString:0xa000000000000311
__NSCFString:0x7febf481c320

通过stringWithFormat创建的字符串的真实类型是NSTaggedPointerString,观察该类型字符串的内存地址为0xa000000000000311,这是一个很高位的地址,我们暂且将该种类型的字符串成为非mutable类型字符串;

在对非mutable类型字符串(即NSTaggedPointerString)进行copy操作时,会引起引用计数+1,并返回非mutable类型的字符串;对其进行mutableCopy操作时,会重新申请内存创建对象,并返回mutable类型的字符串;

操作对象为可变字符串

1
2
3
4
5
6
7
8
{
NSMutableString *string = [NSMutableString stringWithFormat:@"%i", 1];
NSString *string1 = [string copy];
NSString *string2 = [string mutableCopy];
NSMutableString *string3 = [string copy];
NSMutableString *string4 = [string mutableCopy];
NSLog(@"\nstringWithFormat mutable string:\n%@:%p\n%@:%p\n%@:%p\n%@:%p\n%@:%p", string.class, string, string1.class, string1, string2.class, string2, string3.class, string3, string4.class ,string4);
}
1
2
3
4
5
6
7
对应输出为:
stringWithFormat mutable string:
__NSCFString:0x7febf4900120
NSTaggedPointerString:0xa000000000000311
__NSCFString:0x7febf49001e0
NSTaggedPointerString:0xa000000000000311
__NSCFString:0x7febf4900220

通过stringWithFormat创建的字符串的真实类型是__NSCFString,观察该类型字符串内存地址为0x7febf4900120,更像是被分配到了堆区,我们暂且称该类字符串为mutable类型字符串;

在对mutable类型的字符串(即__NSCFString)进行copy操作时,也只会引起引用计数+1,并返回非mutable类型的字符串(注意此时与容器类型的区别);而对其进行mutableCopy操作时,会重新申请内存创建对象,并返回mutable类型的字符串;

iOS应用性能调优
Sqlite3
  • 文章目录
  • 站点概览
Zrongl

Zrongl

23 日志
3 分类
GitHub E-Mail
  1. 1. 容器类行NSArray等
    1. 1.1. 操作对象为不可变数组
    2. 1.2. 操作对象为可变数组
  2. 2. 字符串类型NSString
    1. 2.1. 操作对象为常亮字符串
    2. 2.2. 操作对象为非可变字符串
    3. 2.3. 操作对象为可变字符串
© 2019 Zrongl
不争无尤
|
主题 – NexT.Mist v7.3.0
0%