对象复制
OC提供了两种复制对象的方法,一种为copy,另一种为mutableCopy。
copy和mutable方法
两种方法都是用来复制对象的副本,区别在于
1.copy方法的返回对象总是不可以修改的副本,如果对其进行修改,那么就会报错,即使复制的是NSMutableString类型的字符串,也不可以修改副本。
2.mutableCopy方法的返回对象是一个可以修改的脚本,即使复制的是不可变字符串NSString,副本也可以进行修改,并且修改副本时,不会影响原来的字符串。
下面的这两种方法的一些代码实现:
//
// main.m
// NStry
//
// Created by 差不多先生 on 2021/6/5.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString* book = [NSMutableString stringWithString:@"疯狂iOS讲义"];
// 复制book字符串的可变副本
NSMutableString* bookCopy = [book mutableCopy];
// 修改副本对原字符串无任何影响
[bookCopy replaceCharactersInRange:NSMakeRange(2, 3) withString:@"Android"];
// 观察原字符串已经副本的变化
NSLog(@"%@" , book);
NSLog(@"%@" , bookCopy);
NSString* str = @"fkit";
// 复制不可变字符串的可变副本
NSMutableString* strCopy = [str mutableCopy];
// 1.向可变字符串后面追加字符串
[strCopy appendString:@".org"];
NSLog(@"%@" , strCopy);
NSMutableString* bookCopy2 = [book copy];
// [bookCopy2 appendString:@"aa"];
return 0;
}
}
在程序开始,对副本进行了修改,再分别打印原字符串和副本
得到的结果如下:
只是副本得到了修改。
最后注释的代码是报错的,因为他是使用了copy方法得到的字符串是不可以对其进行修改的。
NSString协议和NSMutableing协议
copy和mutableCopy方法复制对象十分方便,但他不可以复制自定义类。
这里定义了一个FKDog类。尝试复制自定义类。
接口部分:
#import <Foundation/Foundation.h>
@interface FKDog : NSObject
@property (nonatomic, strong) NSMutableString* name;
@property (nonatomic, assign) int age;
@end
主函数:
#import <Foundation/Foundation.h>
#import "FKDog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKDog* dog1 = [[FKDog alloc] init];
dog1.name = [NSMutableString stringWithString:@"旺财"];
dog1.age = 10;
// FKDog* dog1Copy = [dog1 copy];
return 0;
}
}
上面这段代码中注视的语句会进行报错,Xcode显示错误为: Thread 1: "-[FKDog copyWithZone:]: unrecognized selector sent to instance 0x100709ae0"
提示找不到copyWithZone方法,其实NSObject中提供的copy方法返回的就是copyWithZone方法的返回值,但不支持复制自定义类,若要复制自定义类,则需要在接口部分实现NSString协议,并在实现部分加上copyWithCopy方法,代码如下
实现部分:
#import "FKDog.h"
@implementation FKDog
@synthesize name = _name;
@synthesize age = _age;
- (id)copyWithZone:(NSZone*)zone {
NSLog(@"--执行copyWithZone:--");
FKDog* dog = [[[self class] allocWithZone:zone] init];
dog.name = self.name;
dog.age = self.age;
return dog;
}
@end
加入上述代码后,便可以实现使用copy方法对自定义类的复制。
FKDog* dog1Copy = [dog1 copy];
dog1Copy.age = 12;
dog1Copy.name = [NSMutableString stringWithString:@"snoopy"];
在上述的代码中,对复制的副本进行了修改,尽管我们使用了copy方法,按道理复制的对象应该不可以改变,但由于我们自定义的类没有不可变类,所以可以进行修改。
最后强调,如果重写父类时,其父类实现NSCopy协议并重写了copyWithZone:方法时,应该先使用父类的copy方法复制父类的成员变量再重写子类的方法,再对子类定义的成员变量赋值。
代码格式如下:
- (id)copyWithZone:(NSZone*)zone {
id cpy = [super copy];
// 对子类变量赋值
...........
return cpy;
}
浅复制与深复制
在上面的代码中如下修改副本的值
[dog1Copy.name replaceCharactersInRange:NSMakeRange(0, 2) withString:@"snoopy"];
再输出原字符串和副本,会发现他俩相同,这是因为在重写的方法中,只是让副本的name变量和原对象的name变量指向了同一个字符串,name存储的仅仅是地址,所以对其进行修改时,实际是对他实际指向的NSMutable字符串进行修改,输出的结果相同。
对于上述这种复制方式,称为浅复制:
程序仅仅复制了指针的地址,而并非指针指向的对象。
深复制则与其完全不同,不仅会复制指向的对象,还会递归复制每个指针类型的属性,直到两个对象没有公用的部分。
采用深复制可以重写上面的copyWithZone:方法,代码如下:
- (id)copyWithZone:(NSZone*)zone {
NSLog(@"--执行copyWithZone:--");
FKDog* dog = [[[self class] allocWithZone:zone] init];
// 将原对象的name实例变量赋值一份然后赋给新对象的name实例变量。
dog.name = [self.name mutableCopy];
dog.age = self.age;
return dog;
}
重写的方法并不是简单将原对象的属性赋给新对象,而是复制了一个新的可变副本赋值给新对象,这样就可以保证二者无公用部分,实现了深复制。
这样重新打印上面的两个对象,修改复制对象的值并不会影响原对象。
深复制的实现很复杂,尤其是当指针繁多的时候。