在iOS5学习过程中,表视图一章节中最难的莫过于搜索栏实现过程中的深拷贝浅拷贝一环节。现在讨论一下iOS开发中表视图搜索栏实现中的Objective-C 深浅拷贝问题
要为一个tableView实现搜索功能的时候,遇到了一个问题,学习了好长时间终于想通,现在将问题以及我的理解总结一下.
(相关实例《iOS5开发基础教程》最新版的“08 - Sections2”下载地址:http://vdisk.weibo.com/s/hBHg6)
文中实例运行的编译环境为:
在ObjC中,什么是深浅拷贝?
深浅拷贝分别指深拷贝和浅拷贝,即mutableCopy和copy方法。
copy复制一个不可变对象,而mutableCopy复制一个mutable可变对象。
什么时候用到深浅拷贝?下面举几个例子说明。
如NSString,NSNumber等一类对象
示例1:
// 非容器类对象 NSString *str = @"Chen_Yilong"; NSString *strCopy = [str copy]; NSMutableString *mstrCopy = [str mutableCopy]; [mstrCopy appendString:@"的CSDN博客专栏"]; NSLog(@"%@",mstrCopy); NSLog(@"%@",str); NSLog(@"%@",strCopy);运行结果:
Chen_Yilong的CSDN博客专栏
Chen_Yilong
Chen_Yilong
如果我们试图向strCopy 追加字符串时,编译器xcode会报错:由此可见copy复制后的不可变对象的副本不可变。
查看内存可以发现,str和strCopy指向的是同一块内存区域,我们称之为弱引用(weak reference)。而mstrCopy是真正的复制,系统为其分配了新内存空间,保存从str复制过来的字符串值。从最后一行代码中修改这些值而不影响str和strCopy中可证明。
示例2:
NSMutableString *mstr = [NSMutableString stringWithString:@"Chen_Yilong"]; NSString *strCopy = [mstr copy]; NSMutableString *mstrCopy = [mstr copy]; NSMutableString *mstrMCopy = [mstr mutableCopy]; //[mstrCopy appendString:@"的CSDN专栏http://blog.csdn.net/Chen_Yilong"]; //error [mstr appendString:@"的新浪微博http://weibo.com/luohanchenyilong"]; [mstrMCopy appendString:@"的新浪微博帐号为:南阳理工微博校园"]; NSLog(strCopy); NSLog(mstr); NSLog(mstrCopy); NSLog(mstrMCopy);第一次运行出现错误:在
[mstrCopy appendString:@"的CSDN专栏http://blog.csdn.net/Chen_Yilong"]; //error
处出错,原因是如果是对可变对象复制,都是深拷贝,但copy复制返回的对象是不可变的。注释此行,运行成功:
结果为
Chen_Yilong的CSDN博客[718:f803] Chen_Yilong
Chen_Yilong的CSDN博客[718:f803] Chen_Yilong的新浪微博http://weibo.com/luohanchenyilong
Chen_Yilong的CSDN博客[718:f803] Chen_Yilong
Chen_Yilong的CSDN博客[718:f803] Chen_Yilong的新浪微博帐号为:南阳理工微博校园
以上四个对象所分配的内存都是不一样的。而且对于mstrCopy,它所指向的其实是一个imutable对象,是不可改变的,所以会出错。这点要注意,好好理解。
非容器类复制情况小结:
对于非容器类对象,有:
示例3
/* copy返回不可变对象,mutablecopy返回可变对象 */ NSArray *array1 = [NSArray arrayWithObjects:@"Chen",@"Chen_Yi",@"long",nil]; NSArray *arrayCopy1 = [array1 copy]; //arrayCopy1是和array同一个NSArray对象(指向相同的对象),包括array里面的元素也是指向相同的指针 NSLog(@"array1 retain count: %d",[array1 retainCount]); NSLog(@"array1 retain count: %d",[arrayCopy1 retainCount]); NSMutableArray *mArrayCopy1 = [array1 mutableCopy]; //mArrayCopy1是array1的可变副本,指向的对象和array1不同,但是其中的元素和array1中的元素指向的还是同一个对象。mArrayCopy1还可以修改自己的对象 [mArrayCopy1 addObject:@"的新浪微博是:南阳理工微博校园"]; [mArrayCopy1 removeObjectAtIndex:0]; int count=[mArrayCopy1 count]; int i; for(i=0;i<count;i++) { printf("%i: %s\n",i,[[mArrayCopy1 objectAtIndex:i] UTF8String]); }运行结果:
array1 retain count: 2
array1 retain count: 2
0: Chen_Yi
1: long
2: 的新浪微博是:南阳理工微博校园
示例4
NSArray *mArray1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"Chen_Yilong"],@"Chen",@"_Yilong",nil]; NSLog(@"mArray1 retain count: %d",[mArray1 retainCount]); NSArray *mArrayCopy2 = [mArray1 copy]; NSLog(@"mArray1 retain count: %d",[mArray1 retainCount]); // mArray1和mArrayCopy2指向同一对象,retain值+1。 NSMutableArray *mArrayMCopy1 = [mArray1 mutableCopy]; NSLog(@"mArray1 retain count: %d",[mArray1 retainCount]); //mArrayCopy2和mArray1指向的是不一样的对象,但是其中的元素都是一样的对象——同一个指针 NSMutableString *testString = [mArray1 objectAtIndex:0]; //testString = @"Chen_Yilong";//这样会改变testString的指针,其实是将@“Chen_Yilong”临时对象赋给了testString [testString appendString:@" 的新浪微博是:南阳理工微博校园"];//这样以上三个数组的首元素都被改变了 NSLog(@"%@",testString); NSLog(@"%@",[mArray1 objectAtIndex:0]); NSLog(@"%@",[mArrayCopy2 objectAtIndex:0]); NSLog(@"%@",[mArrayMCopy1 objectAtIndex:0]);运行结果:
mArray1 retain count: 1
mArray1 retain count: 2
mArray1 retain count: 2
Chen_Yilong 的新浪微博是:南阳理工微博校园
Chen_Yilong 的新浪微博是:南阳理工微博校园
Chen_Yilong 的新浪微博是:南阳理工微博校园
Chen_Yilong 的新浪微博是:南阳理工微博校园
由此可见,对于容器而言,其元素对象始终是指针复制。如果需要元素对象也是对象复制,就需要实现深拷贝。http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Collections/Articles/Copying.html
示例5
NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"first"],[NSString stringWithString:@"b"],@"c",nil]; NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES]; NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject: array]];
trueDeepCopyArray是完全意义上的深拷贝,而deepCopyArray则不是,对于deepCopyArray内的不可变元素其还是指针复制。
或者我们自己实现深拷贝的方法。因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。
举个例子,[[array objectAtIndex:0] appendstring:@”sd”]后其他的容器内对象并不会受影响。[[array objectAtIndex:1]和[[deepCopyArray objectAtIndex:0]尽管是指向同一块内存,但是我们没有办法对其进行修改——因为它是不可改变的。所以指针复制已经足够。所以这并不是完全意义上的深拷贝。
用下面的代码两个例子来分别验证可变元素与不可变元素在两种方法的拷贝后的区别:
验证一:可变元素深层复制
NSString *str = @"Chen_Yilong"; NSMutableString *mstrCopy = [str mutableCopy]; NSLog(@"%@",mstrCopy); NSLog(@"%@",str); NSMutableArray *array = [NSMutableArray arrayWithObjects: mstrCopy,str,nil]; NSArray *deepCopyArray=[[NSArray alloc] initWithArray:array copyItems: YES]; NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject:array]]; [[array objectAtIndex:0] appendString:@"的CSDN博客专栏"]; NSLog(@"%@",mstrCopy); int i; int count = [deepCopyArray count]; for (i=0; i<count; i++) { printf("The num%i of deepCopyArray is : %s\n",i,[[deepCopyArray objectAtIndex:i] UTF8String]); printf("The num%i of trueDeepCopyArray is : %s\n",i,[[trueDeepCopyArray objectAtIndex:i] UTF8String]); }
Chen_Yilong
Chen_Yilong
Chen_Yilong的CSDN博客专栏
The num0 of deepCopyArray is : Chen_Yilong
The num0 of trueDeepCopyArray is : Chen_Yilong
The num1 of deepCopyArray is : Chen_Yilong
The num1 of trueDeepCopyArray is : Chen_Yilong
先复制,再改变mstrCopy的内容,两个复制对象都没有收到影响,于是我们知道:
deepCopyArray内的可变元素其还是深复制。
即不可变容器类对象中的可变元素接收deepCopyArray复制之后,是可变的,属于深复制。
验证二:不可变元素指针复制
NSArray *mArray= [NSArray arrayWithObjects:@"Chen",@"_Yilong",nil]; NSLog(@"Original Array retain count is: %d",[mArray retainCount]); NSMutableArray *array = [NSMutableArray arrayWithObjects: mArray,nil]; NSLog(@"NeverCopyied Array retain count is: %d",[mArray retainCount]); NSArray *deepCopyArray=[[NSArray alloc] initWithArray:array copyItems: YES]; NSLog(@"deepCopyied Array retain count is: %d",[mArray retainCount]); NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject:array]]; NSLog(@"trueDeepCopyied Array retain count is: %d",[mArray retainCount]);
运行结果:
Original Array retain count is: 1
NeverCopyied Array retain count is: 2
deepCopyied Array retain count is: 3
trueDeepCopyied Array retain count is: 3
mArray在deepCopy后引用加一,由2变3,说明是指针复制;
在trueDeepCopy后引用没变,原来是3,现在还是3,说明是深层复制;
容器类复制情况总结:
将以上的总结制成一张表,其中我们的研究对象分两类,一类:第一行:容器类;另一类:容器类中的成员;我在这里为了说明这四种复制方法的区别,我引入一个新的概念“深拷贝效率”,也就是“蓝色个数/(蓝色+黄色)个数”,也就是“复制后成员为深复制(深副本)的个数/原成员个数“。
即如下图所示:
NSDictionaryMutableDeepCopy.h
#import <foundation /Foundation.h> @interface NSDictionary(MutableDeepCopy) - (NSMutableDictionary *)mutableDeepCopy; @end </foundation>
#import "NSDictionaryMutableDeepCopy.h" @implementation NSDictionary(MutableDeepCopy) - (NSMutableDictionary *)mutableDeepCopy { NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithCapacity:[self count]]; NSArray *keys = [self allKeys]; for (id key in keys) { id oneValue = [self valueForKey:key]; id oneCopy = nil; if ([oneValue respondsToSelector:@selector(mutableDeepCopy)]) { oneCopy = [oneValue mutableDeepCopy]; } else if ([oneValue respondsToSelector:@selector(mutableCopy)]) { oneCopy = [oneValue mutableCopy]; } if (oneCopy == nil) { oneCopy = [oneValue copy]; } [ret setValue:oneCopy forKey:key]; } return ret; } @end
NSDictionary-MutableDeepCopy.h
#import <Foundation/Foundation.h> @interface NSDictionary(MutableDeepCopy) -(NSMutableDictionary *)mutableDeepCopy; @end
#import "NSDictionary-MutableDeepCopy.h" @implementation NSDictionary(MutableDeepCopy) - (NSMutableDictionary *)mutableDeepCopy { NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]]; NSArray *keys = [self allKeys]; for (id key in keys) { id oneValue = [self valueForKey:key]; id oneCopy = nil; if ([oneValue respondsToSelector:@selector(mutableDeepCopy)]) oneCopy = [oneValue mutableDeepCopy]; else if ([oneValue respondsToSelector:@selector(mutableCopy)]) oneCopy = [oneValue mutableCopy]; if (oneCopy == nil) oneCopy = [oneValue copy]; [returnDict setValue:oneCopy forKey:key]; } return returnDict; } @end
而如果你要复制的对象是一个plist文件,并且严格遵守一个键对应一个数组,并且数组中均为字符串,就像这本书中的例子一样,我们也可以这样写.m文件
#import "NSDictionary-MutableDeepCopy.h" @implementation NSDictionary(MutableDeepCopy) - (NSMutableDictionary *)mutableDeepCopy { NSMutableDictionary *returnDict = [NSMutableDictionary dictionaryWithCapacity:[self count]]; NSArray *keys = [self allKeys]; for (id key in keys) { id oneValue = [self valueForKey:key]; id oneCopy = nil; // if ([oneValue respondsToSelector:@selector(mutableDeepCopy)]) //本实例数组中没有字典类型所以不需要对字典进行遍历 // oneCopy = [oneValue mutableDeepCopy]; // else if ([oneValue respondsToSelector:@selector(mutableCopy)]) if ([oneValue respondsToSelector:@selector(mutableCopy)]) oneCopy = [oneValue mutableCopy]; // if (oneCopy == nil) //数组均不为空,所以不需要考虑这种情况 // oneCopy = [oneValue copy]; [returnDict setValue:oneCopy forKey:key]; } return returnDict; }
而这一种mutableCopy方法如何确保:
a的副本被删除或者改变的时候不会影响到a自己的内容呢?
答:NSString NSArray 经mutableCopy方法复制后就变成了可变副本。
if ([oneValue respondsToSelector:@selector(mutableDeepCopy)]) oneCopy = [oneValue mutableDeepCopy]; else if ([oneValue respondsToSelector:@selector(mutableCopy)]) oneCopy = [oneValue mutableCopy]; if (oneCopy == nil) oneCopy = [oneValue copy];可是我们却只会用到其中一种,大可将其余两种多余的删除掉,
因为书本复制的对象是一个plist文件,并且严格遵守一个键对应一个数组,并且数组中均为字符串,所以我们也可以这样写.m文件
// if ([oneValue respondsToSelector:@selector(mutableDeepCopy)])//本实例数组中没有字典类型所以不需要对字典进行遍历 // oneCopy = [oneValue mutableDeepCopy]; // else if ([oneValue respondsToSelector:@selector(mutableCopy)]) if ([oneValue respondsToSelector:@selector(mutableCopy)]) oneCopy = [oneValue mutableCopy]; // if (oneCopy == nil)//数组均不为空,所以不需要考虑这种情况 // oneCopy = [oneValue copy];
当数组为空(nil)时会用到
// if (oneCopy == nil)//数组均不为空,所以不需要考虑这种情况 // oneCopy = [oneValue copy];
当字典的值--数组中还有字典的时候会用到
// if ([oneValue respondsToSelector:@selector(mutableDeepCopy)])//本实例数组中没有字典类型所以不需要对字典进行遍历 // oneCopy = [oneValue mutableDeepCopy];
这个方法是针对字典的而设立的,并且只有字典才能响应这个方法。为什么呢?
NSString 和NSArray 要由mutableCopy进行处理。为什么呢?
下面对这两个问题做一下解答:
观察一下.m文件的开头,
@implementation NSDictionary(MutableDeepCopy) - (NSMutableDictionary *)mutableDeepCopy ;于是就知道,这不是一般的方法,在NSDictionary 后面加了一个“()”,这是什么意思?这是一种全新的方法,叫分类方法。就是将NSDictionary 额外地添加了一种函数,即扩展出了一种功能,所说的就是这个mutableDeepCopy函数,所以mutableDeepCopy方法是隶属于NSDictionary类的,是 NSDictionary的分类方法,所以是针对 NSDictionary类型数据进行的深拷贝。(关于分类方法可以参考:
并且这也是一个递归方法,可能不好理解,为什么我们自定义了一个函数,在自己定义的函数中还要去调用自己呢?而这就叫递归。
为了理解什么叫做递归,我们做一个比喻,我们将需要深复制的字典比作一个压缩包,深复制方法就好像解压缩软件,字典中的值相当于压缩包中的文件,但压缩包中的文件,也可能出现这种情况:压缩包中的文件还是压缩包。也就是说:已经把压缩包用解压缩软件解压了,可是为了解压“缩压缩包中的压缩包”,我们还是得用刚用到的解压缩软件,就好像我们为了深复制字典中出现的字典,甚至字典中的字典的字典,还得用同一个方法:mutableDeepCopy。而这,就叫递归!
那为什么说只需要下面的一种方法就足够了呢?
if ([oneValue respondsToSelector:@selector(mutableCopy)]) oneCopy = [oneValue mutableCopy];首先,你要了解Foundation类,NSArray NSMutableArray NSMutableDictionary NSDctionary NSString都是Foundation类。而Foundation类已经遵守了<NSCopying>和 <NSMutableCopying>协议,即实现了copy和mutableCopy方法,因此Foundation对象可以使用这些方法创建对象的副本或可变副本。(更多Foundation类信息,可参照 点击打开链接)
使用类别方法来实现。
如果是我们定义的对象,那么我们自己要实现NSCopying,NSMutableCopying这样就能调用copy和mutablecopy了。举个例子:
@interface MyObj : NSObject<nscopying ,NSMutableCopying> { NSMutableString *name; NSString *imutableStr; int age; } @property (nonatomic, retain) NSMutableString *name; @property (nonatomic, retain) NSString *imutableStr; @property (nonatomic) int age; @end @implementation MyObj @synthesize name; @synthesize age; @synthesize imutableStr; - (id)init { if (self = [super init]) { self.name = [[NSMutableString alloc]init]; self.imutableStr = [[NSString alloc]init]; age = -1; } return self; } - (void)dealloc { [name release]; [imutableStr release]; [super dealloc]; } - (id)copyWithZone:(NSZone *)zone { MyObj *copy = [[[self class] allocWithZone:zone] init]; copy->name = [name copy]; copy->imutableStr = [imutableStr copy]; // copy->name = [name copyWithZone:zone];; // copy->imutableStr = [name copyWithZone:zone];// copy->age = age; return copy; } - (id)mutableCopyWithZone:(NSZone *)zone { MyObj *copy = NSCopyObject(self, 0, zone); copy->name = [self.name mutableCopy]; copy->age = age; return copy; } @end
原来不是所有的对象都支持 copy
只有遵守NSCopying 协议的类才可以发送copy消息
只有遵守 NSMutableCopying 协议的类才可以发送mutableCopy消息
假如发送了一个没有遵守上诉两协议而发送 copy或者 mutableCopy,那么就会发生异常
默认 nsobject没有遵守这两个协议
但是 copy和mutableCopy这两个方法是nsobject定义的
如果想自定义一下copy 那么就必须遵守NSCopying,并且实现 copyWithZone: 方法
如果想自定义一下mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法
看了一下几个遵守 NSCopying协议的基本上是一些基础核心类
比如 NSString NSNumber
copy以后,就是返回一个新的类, 你要负责释放掉,原先被拷贝的retaincount没有+1 所以,不需要负责释放
copy和mutableCopy 就是copy返回后的是不能修改的对象, mutableCopy返回后是可以修改的对象
下面是实现一个copyWithZone的例子
@interface BankAccount: NSObject <NSCopying> { double accountBalance; long accountNumber; } -(void) setAccount: (long) y andBalance: (double) x; -(double) getAccountBalance; -(long) getAccountNumber; -(void) setAccountBalance: (double) x; -(void) setAccountNumber: (long) y; -(void) displayAccountInfo; -(id) copyWithZone: (NSZone *) zone; @end -(id) copyWithZone: (NSZone *) zone { BankAccount *accountCopy = [[BankAccount allocWithZone: zone] init]; [accountCopy setAccount: accountNumber andBalance: accountBalance]; return accountCopy; }
NSArray *myArray1; NSArray *myArray2; NSMutableString *tmpStr; NSMutableString *string1; NSMutableString *string2; NSMutableString *string3; NSData *buffer; string1 = [NSMutableString stringWithString: @"Red"]; string2 = [NSMutableString stringWithString: @"Green"]; string3 = [NSMutableString stringWithString: @"Blue"]; myArray1 = [NSMutableArray arrayWithObjects: string1, string2, string3, nil]; buffer = [NSKeyedArchiver archivedDataWithRootObject: myArray1]; myArray2 = [NSKeyedUnarchiver unarchiveObjectWithData: buffer]; tmpStr = [myArray1 objectAtIndex: 0]; [tmpStr setString: @"Yellow"]; NSLog (@"First element of myArray1 = %@", [myArray1 objectAtIndex: 0]); NSLog (@"First element of myArray2 = %@", [myArray2 objectAtIndex: 0]);
tmpStr = [myArray1 objectAtIndex: 0];
属不属于浅复制?不属于,这是简单的使指针[myArray1 objectAtIndex: 0]的指向与 tmpStr 指向相同内存。结果就是造成了上面的alloc下动态分配。这就引发一个问题:复制对象有实际作用吗?
有的人可能会说:
复制对象有实际作用吗?比如说上面的语句就是:
两个对象a,b;要把a复制给b;直接用b=a;不就好了?
如两个对象
NSString *str1=[[NSString alloc] initWithString:@"string1"];
NSString *str2=[[NSString alloc] initWithString:@"string2"];
str2=str1; //使指针str2的指向与str1指向相同内存。结果就是造成了上面的alloc下动态分配
的内存,内存泄露.当调用[str1 release]; [str2 release];内存的释放最终调用的是[str1 dealloc];
或者是[str2 dealloc],什么情况下才会调用这个释放内存的dealloc方法呢?当创建的对象其引用计数(retainCount)为0时,就会调用。
而哪些方法会造成引用计数改变呢?
1.调用alloc方法
2.调用copy,retain 特性
3.relase方法
str2=[str1 copy]; 引用计数加1,所以当调用[str1 release];时str2与str1指向的共同对象并未析构,就是还存在。如果用直接复制str2=str1就不存在了。
引用计数变化为: 1.调用alloc使引用计数加1,变为了1.
2.调用copy,引用计数加1,变为2.
3.调用release,引用计数减1,变为1;所以引用计数不为0,不会调用dealloc释放内存
的函数,所以内存还存在,如果要释放内存,必须再release一次。
所以这才是两者之间根本区别。
用C语言解释就是,新分配一块内存空间来存储要复制的值。