目录
二、NSCopying和NSmutableCopying协议
对象复制
一、copy与mutableCopy方法
copy方法用于复制对象的副本,复制下来的该副本是不可修改的,哪怕是调用NSMutableString的copy方法也不可修改。
而mutableCopy方法复制下来的副本是可修改的,即使被复制的对象原本是不可修改的。例如调用mutableCopy方法复制NSString的,返回的是一个NSMutableString对象。
以下用代码演示copy和mutableCopy方法的功能:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//copy与mutableCopy
NSMutableString *book = [NSMutableString stringWithString: @"疯狂iOS讲义"];
NSMutableString *bookCopy = [book mutableCopy]
[bookCopy replaceCharactersInRange: NSMakeRange(2, 3) withString: @"Android"];//复制后的bookCopy副本是可以修改的,这里做个修改,对原字符串的值也没有影响
NSLog(@"book的值为:%@",book);//原值
NSLog(@"bookCopy的值为:%@",bookCopy);//副本修改后的值
NSString *str = @"fkit";//定义一个str字符串
NSMutableString *strCopy = [str mutableCopy];//用mutableCopy给str复制一个副本
[strCopy appendString:@".org"];//向可变字符串后面追加字符串
NSLog(@"%@",strCopy);
NSMutableString *bookCopy2 = [book copy];//用copy方法复制一个book的副本(这个副本不可变)
[bookCopy2 appendString:@"aa"];//这里会报错,因为copy创建的副本不可变,修改了就崩了
}
return 0;
}
二、NSCopying和NSmutableCopying协议
当我们想将自定义类用上一节的两个方法复制副本时,我们可能会直接创建完对象后用”类名* 对象2 = [对象1 copy];“这样的格式来复制副本,但实际上直接这样复制是不对的,会报错说找不到copyWithZone:方法,mutableCopy也是一样。因此我们可以看出,自定义类是不能直接调用这两个方法来复制自身的。
这是为什么呢?是因为当程序调用copy/mutableCopy方法复制时,程序底层需要调用copyWithZone:/mutableCopyWithZone:方法来完成复制的工作,并返回这两个方法的值。因此为了保证可以复制,需要在自定义类的接口部分声明NSCopying/NSMutableCopying协议,然后再类的实现部分增加copyWithZone:/mutableCopyWithZone:方法,因此,对自定义对象的复制应该如下所示:
接下来,我们以一个自定义的Person类为例,支持copy和mutablecopy:
#import <Foundation/Foundation.h>
@interface Person : NSObject <NSCopying, NSMutableCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
// 实现 copy(返回不可变副本)
- (id)copyWithZone:(NSZone *)zone {
Person *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copy];
copy.age = self.age;
return copy;
}
// 实现 mutableCopy(返回可变副本)
- (id)mutableCopyWithZone:(NSZone *)zone {
Person *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name mutableCopy]; // 注意生成可变副本
copy.age = self.age;
return copy;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p1 = [[Person alloc] init];
p1.name = @"Tom";
p1.age = 18;
Person *p2 = [p1 copy]; // 调用 copyWithZone
Person *p3 = [p1 mutableCopy]; // 调用 mutableCopyWithZone
NSLog(@"原始:%@ %ld", p1.name, p1.age);
NSLog(@"copy:%@ %ld", p2.name, p2.age);
NSLog(@"mutableCopy:%@ %ld", p3.name, p3.age);
}
return 0;
}
三、深复制与浅复制
深复制和浅复制是面向对象编程中非常重要的概念。
浅复制:仅复制对象的指针地址,多个变量共享同一个对象。
深复制: 不仅复制指针,还会复制整个对象内容,使得原对象和副本完全独立。
举个例子:
NSMutableString *str1 = [NSMutableString stringWithString:@"Hello"];
NSMutableString *str2 = str1; // 浅复制(赋值)
// 修改 str1,str2 也变了
[str1 appendString:@" World"];
因此总而言之,浅拷贝就是创建一个副本,对内存地址的复制。深拷贝就是创建一个副本,对内容完全复制。原始对象与副本对象内存地址不同。
按照类型说明:
非容器类对象的深拷贝与浅拷贝
不可变字符串
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString* str1 = @"dddddd";
NSString* str2 = [str1 copy];
NSString* str3 = [str1 mutableCopy];
NSMutableString* str4 = [str1 copy];
NSMutableString* str5 = [str1 mutableCopy];
NSLog(@"str1:%p", str1);
NSLog(@"str2:%p", str2);
NSLog(@"str3:%p", str3);
NSLog(@"str4:%p", str4);
NSLog(@"str5:%p", str5);
}
return 0;
}
得出结论: 不可变字符串,只要是copy就是浅拷贝,mutableCopy是深拷贝。tips:我们用NSString stringWithstring 方式创建的是一个常量区字符串。
可变类型字符串
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString* str1 = [NSMutableString stringWithString:@"helloworld"];
NSMutableString* str2 = [str1 copy];
NSMutableString* str3 = [str1 mutableCopy];
NSString* str4 = [str1 copy];
NSString* str5 = [str1 mutableCopy];
NSLog(@"str1:%p", str1);
NSLog(@"str2:%p", str2);
NSLog(@"str3:%p", str3);
NSLog(@"str4:%p", str4);
NSLog(@"str5:%p", str5);
}
return 0;
}
打印结果:
这里我们发现这里的两种拷贝都是深拷贝,它都重新创建了一块新的区域储存这一部分内容
因此:
可变对象copy后的对象是不可变的,mutableCopy后的对象是可变的
对于可变对象的复制都是深拷贝
容器类对象的深浅拷贝
NSArray:NSArray 是 Objective-C 中的 不可变数组类,它在创建之后 元素数量和顺序都不能更改。
NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", nil];
NSArray *copyArray01 = [array01 copy];
NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
NSLog(@"array01 = %p,copyArray01 = %p",array01,copyArray01);
NSLog(@"array01 = %p,mutableCopyArray01 = %p",array01,mutableCopyArray01);
//-----------------------------------------------------
NSLog(@"array01[0] = %p,array01[1] = %p,array01[2] = %p",array01[0],array01[1],array01[2]);
NSLog(@"copyArray01[0] = %p,copyArray01[1] = %p,copyArray01[2] = %p",copyArray01[0],copyArray01[1],copyArray01[2]);
NSLog(@"mutableCopyArray01[0] = %p,mutableCopyArray01[1] = %p,mutableCopyArray01[2] = %p",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);
可以得出下列结论:
copyArray01和array01指向的是同一个对象,包括里面的元素也是指向相同的地址。
mutableCopyArray01和array01指向的是不同的对象,但是里面的元素指向相同的对象,mutableCopyArray01可以修改自己的对象。
copyArray01是对array01的指针复制(浅复制),而mutableCopyArray01是内容复制。
所以这就是不可变的容器对象 copy是浅拷贝 mutablecopy是深拷贝
NSMutableArray:NSMutableArray 是 Objective-C 中的可变数组类,可以动态添加、删除、替换数组中的元素。
NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", nil];
NSArray *copyArray01 = [array01 copy];
NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
NSLog(@"array01 = %p,copyArray01 = %p",array01,copyArray01);
NSLog(@"array01 = %p,mutableCopyArray01 = %p",array01,mutableCopyArray01);
NSLog(@"array01[0] = %p,array01[1] = %p,array01[2] = %p",array01[0],array01[1],array01[2]);
NSLog(@"copyArray01[0] = %p,copyArray01[1] = %p,copyArray01[2] = %p",copyArray01[0],copyArray01[1],copyArray01[2]);
NSLog(@"mutableCopyArray01[0] = %p,mutableCopyArray01[1] = %p,mutableCopyArray01[2] = %p",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);
我们不难发现,对于可变对象的拷贝都是一个新对象,深拷贝。
但是,集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制
用更为严谨的表达来说:
集合的 copy/mutableCopy 是容器级别的拷贝,里面的元素对象不会被复制,而是指针指向原对象。
自定义类型的拷贝
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@interface Person()<NSCopying,NSMutableCopying>
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *person = [[Person allocWithZone:zone]init];
person.name = [self.name copy];
person.age = self.age;
return person;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
Person *person = [[Person allocWithZone:zone] init];
person.name = [self.name mutableCopy];
person.age = self.age;
return person;
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.name = @"Jack";
person.age = 23;
Person *copyPerson = [person copy]; // 深拷贝
Person *mutableCopyPerson = [person mutableCopy]; // 深拷贝
NSLog(@"person = %p;copyPerson = %p",person,copyPerson);
NSLog(@"person = %p;mutableCopyPerson = %p",person,mutableCopyPerson);
}
return 0;
}
我们可以看到无论是copy还是mutableCopy,都是深拷贝,这是因为我们本来就是使用copyWithZone和mutableCopyWithZone进行拷贝的。
容器类对象的深拷贝
1. copyItems 方法是NSArray和NSMutableArray的一个方法,用于创建一个新的数组,并对数组中的元素进行拷贝。该方法遍历原数组的每个元素,并对每个元素执行copy操作,然后将拷贝后的元素添加到新数组中。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString* str1;
FKUser* p1 = [[FKUser alloc] initWithName:@"krystal" pass:@"12"];
FKUser* p2 = [p1 copy];
NSArray* ary = [NSArray arrayWithObjects:p1, p2, nil];
NSArray* ary1 = [[NSArray alloc] initWithArray:ary copyItems:YES];
NSLog(@"%p, %p", ary, ary1);
NSLog(@"%p, %p", ary1[0], ary[0]);
FKUser* p3 = ary1[0];
FKUser* p4 = ary[0];
NSLog(@"%p, %p", p3.name, p4.name);
}
return 0;
}
我们对于数组内部的元素也进行一了一次深拷贝。这个方法实现了我们的对于容器内部的一个深拷贝。但是,要注意一个点,就是我们容器类对象中的元素是非容器类才可以实现一个复制,但是注意这里我们没有对对象进行更深一层的拷贝。
归档与解档代码实现
实现解档与归档:之前有了解过,这里再回顾一遍加深印象
关键在与为自定义类实现两个方法,如下:
#import "Coffee.h"
@implementation Coffee
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:@"name"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
_name = [coder decodeObjectOfClass:[NSString class] forKey:@"name"];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
Coffee* coffee = [[[self class] allocWithZone:zone] init];
coffee.name = [self.name copy];
return coffee;;
}
@end
//使用
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:coffee requiringSecureCoding:YES error:nil];
Coffee* copyCoffee = [NSKeyedUnarchiver unarchivedObjectOfClass:[Coffee class] fromData:data error:nil];
总而言之:
可变对象 -> copy :深拷贝 -> 不可变对象
可变对象 -> mutableCopy : 深拷贝 -> 可变对象
不可变对象 -> copy : 浅拷贝 -> 不可变对象
不可变对象 -> mutableCopy : 深拷贝 -> 可变对象
属性关键字
分类 | 属性关键字 |
原子性 | atomic、nonatomic |
读写 | readwrite. readonly. setter\getter |
内存管理 | assign、weak、unsafe_unretained、retain、strong、copy |
可空性 | (nullable、_Nullable 、__nullable)、 |
1 原子性
atomic原子性(默认)。
编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁,可以保证属性的赋值和取值的原子性操作是线程安全的,但不包括操作和访问。
比如说 atomic 修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在 atomic 的负责范围之内的,所以给被 atomic 修饰的数组添加对象或者移除对象是没办法保证线程安全的。
nonatomic 非原子性,一般属性都用 nonatomic 进行修饰,因为 atomic 非常耗时。
2 读写权限
readwrite可读可写(默认),同时生成 setter 方法和 getter 方法的声明和实现。
readonly只读,只生成 getter 方法的声明和实现。
setter可以指定生成的 setter 方法名,如 setter = setName。
getter可以指定生成的 getter 方法名,如 getter = getName。
3. 内存管理
属性关键字用法
assign
1. 既可以修饰基本数据类型,也可以修饰对象类型;
2. setter 方法的实现是直接赋值,一般用于基本数据类型 ;
3. 修饰基本数据类型,如 NSInteger、BOOL、int、float 等;
4. 修饰对象类型时,不增加其引用计数;
5. 会产生悬垂指针(悬垂指针:assign 修饰的对象在被释放之后,指针仍然指向原对象地址,该指针变为悬垂指针。这时候如果继续通过该指针访问原对象的话,就可能导致程序崩溃)。
weak
1. 只能修饰对象类型;
2. ARC 下才能使用;
3. 修饰弱引用,不增加对象引用计数,主要可以用于避免循环引用;
4. weak 修饰的对象在被释放之后,会自动将指针置为 nil,不会产生悬垂指针。
unsafe_unretained
1. 既可以修饰基本数据类型,也可以修饰对象类型;
2. MRC 下经常使用,ARC 下基本不用;
3. 修饰弱引用,同 weak;但性能更好,同时使用要求高,因为会产生悬垂指针。
retain
1. MRC 下使用,ARC 下基本使用 strong;
2. 修饰强引用,将指针原来指向的旧对象释放掉,然后指向新对象,同时将新对象的引用计数加1;
3. setter 方法的实现是 release 旧值,retain 新值,用于OC对象类型。
strong
1. ARC 下才能使用;
2. 原理同 retain;
3. 但是在修饰 block 时,strong 相当于 copy,而 retain 相当于 assign。
4. 浅拷贝
copy
setter 方法的实现是 release 旧值,copy 新值,用于 NSString、block 等类型。是深拷贝
4. 修饰变量关键字
这部分还没仔细了解,以后再来补全。
const
常量修饰符,表示不可变,可以用来修饰右边的基本变量和指针变量(放在谁的前面修饰谁(基本数据变量p,指针变量*p))。
常量(const)和宏定义(define)的区别:
使用宏和常量所占用的内存差别不大,宏定义的是常量,常量都放在常量区,只会生成一份内存 缺点:
编译时刻:宏是预编译(编译之前处理),const是编译阶段。导致使用宏定义过多的话,随着工程越来越大,编译速度会越来越慢
宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。
优点:
宏能定义一些函数,方法。 const不能。
static
extern
Q&A:
copy和strong的区别:(深拷贝 浅拷贝)
由上面可以看出,strong修饰的对象,在引用一个对象的时候,内存地址都是一样的,只有指针地址不同,copy修饰的对象也是如此。为什么呢?不是说copy修饰的对象是生成一个新的内存地址嘛?这里为什么内存地址还是原来的呢?
因为,对不可变对象赋值,无论是strong还是copy,都是一样的,原内存地址不变,生成了新的指针地址。
那我们用可变对象对属性赋值。
strong修饰的属性内存地址依旧没有改变,但是copy修饰的属性内存子产生了变化。
对可变对象赋值 strong 是原地址不变,引用计数+1(浅拷贝)。 copy是生成一个新的地址和对象,生成一个新指针指向新的内存地址(深拷贝)
我们来修改OriginalStr的值:
看到 strong 修饰的属性,跟着进行了改变 当改变了原有值的时候,由于OriginalStr是可变类型,是在原有内存地址上进行修改,无论是指针地址和内存地址都没有b改变,只是当前内存地址所存放的数据进行改变。由于 strong 修饰的属性虽然指针地址不同,但是指针是指向原内存地址的,所以会跟着 OriginalMutableStr 的改变而改变。
不同于strong,copy修饰的类型不仅指针地址不同,而且指向的内存地址也和OriginalMutableStr 不一样,所以不会跟着 OriginalMutableStr 的改变而改变。
Q:以下代码会出现什么问题?
@property (copy) NSMutableArray *array;
答: 不论赋值过来的是NSMutableArray
还是NSArray
对象,进行copy
操作后都是NSArray
对象(深拷贝)。由于属性被声明为NSMutableArray
类型,就不可避免的会有调用方去调用它的添加对象、移除对象等一些方法,此时由于copy
的结果是NSArray
不可变对象,对NSArray
对象调用添加对象、移除对象等方法,就会产生程序异常。
Q:assign 和 weak 关键字的区别有哪些?
weak
只能修饰对象,而assign
既可以修饰对象也可以修饰基本数据类型;
assign
修饰的对象在被释放后,指针仍然指向原对象地址;而weak
修饰的对象在被释放之后会自动置指针为 nil;
相同点:在修饰对象的时候,assign
和weak
都不改变对象的引用计数。
Q:atomic 修饰的属性是怎么样保存线程安全的?
答: 编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁,可以保证属性的赋值和取值原子性操作是线程安全的,但不包括操作和访问。
比如说atomic
修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在atomic
的负责范围之内的,所以给被atomic
修饰的数组添加对象或者移除对象是没办法保证线程安全的。
Q:weak和assign的区别?
答:修饰的对象:weak修饰oc对象类型的数据,assign用来修饰是非指针变量。
引用计数:weak 和 assign 都不会增加引用计数。
释放:weak 修饰的对象释放后,指针地址自动设置为 nil,assign修饰的对象释放后指针地址依然在,成为野指针。
修饰delegate 在MRC使用assign,在ARC使用weak。
属性关键字的其他知识还有很多,笔者会在接下来的学习中不断丰富本篇博客。