属性关键字
- 深拷贝与浅拷贝
- 类型
- 各类对象深浅拷贝判断
- 完全深拷贝的实现
- 属性关键字
- @property、@synthesize和@dynamic
- 原子操作
- 读写权限
- 内存管理
- strong 🆚 copy
- 总结
深拷贝与浅拷贝
先前学习OC时已经对深浅拷贝进行了一次学习,这里进行一个复习总结和补充,具体参照博客深拷贝与浅拷贝。
类型
OC对象的拷贝形式分为深拷贝、浅拷贝,其中深拷贝又细分为单层深拷贝和完全深拷贝。
- 浅拷贝:指针拷贝,复制新指针与原指针存到同一块内存区域。
- 深拷贝:内容拷贝,将数据拷贝到一块新的内存区域,新指针指向新区域。
- 单层深拷贝:副本对象本身是深拷贝,其里面所有对象是浅拷贝。(例如容器类数组对象本身是深拷贝,数组元素是浅拷贝)
- 完全深拷贝:无论副本对象本身还是里面对象元素都是深拷贝。
各类对象深浅拷贝判断
- 可变对象(非容器类、容器类)的copy和mutableCopy都为深拷贝。
- 不可变对象(非容器类、容器类)的copy为浅拷贝,mutableCopy为深拷贝。
- 自定义对象的copy和mutableCopy都为深拷贝。
无论容器类对象还是非容器类对象,都属于系统类,xCode会自动帮我们实现好copyWithZone:
和 mutableCopyWithZone:
方法。然而自定义类的这两个方法需要我们自己手动实现。
@implementation Person- (id)copyWithZone:(NSZone *)zone {Person *copy = [[[self class] allocWithZone:zone] init];copy.name = [self.name copy];copy.age = self.age;return copy;
}- (id)mutableCopyWithZone:(NSZone *)zone {Person *copy = [[[self class] allocWithZone:zone] init];copy.name = [self.name mutableCopy];copy.age = self.age;return copy;
}@end
从上述的方法实现中我们可以看出,无论是copy还是mutableCopy,这里都init出一个新的对象,开辟了一个新内存,与原对象不在一个内存地址,这就是深拷贝。
完全深拷贝的实现
在之前的博客中对完全深拷贝的实现没有过多讲解,这里具体总结一下从单层深拷贝转换为为完全深拷贝的两种方法。
- 归档和解档
这种方法的重点在于实现encodeWithCoder:
和 initWithCoder:
方法。对于系统自带类来说,自动实现了这两个方法,可以直接归档和解档。相反,自定义类的归档和解档的关键就是遵守NSSecureCoding
协议实现以下方法:
#import "Person.h"@implementation Person//支持安全编码
+(BOOL)supportsSecureCoding {return YES;
}//将对象转化为二进制
- (void)encodeWithCoder:(nonnull NSCoder *)coder {[coder encodeObject:self.name forKey:@"name"];
}//二进制还原对象
- (instancetype)initWithCoder:(nonnull NSCoder *)coder {if (self = [super init]) {self.name = [coder decodeObjectOfClass:[NSString class] forKey:@"name"];}return self;
}@end
#import <Foundation/Foundation.h>
#import "Person.h"int main(int argc, const char * argv[]) {@autoreleasepool {Person *person = [[Person alloc] init];person.name = @"Alice";NSLog(@"%p---%@", person, person.name);//归档,将对象封装进二进制容器NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];//解档Person *copyPerson = [NSKeyedUnarchiver unarchivedObjectOfClass:[Person class] fromData:data error:nil];NSLog(@"%p---%@", copyPerson, copyPerson.name);person.name = @"Bob";NSLog(@"%p---%@", person, person.name);NSLog(@"%p---%@", copyPerson, copyPerson.name);}return 0;
}
运行结果:
从运行结果可以看出,通过解档归档,得到了一个新对象,新建了一块内存地址,内容相同但地址不同,因此是深拷贝。并且,修改原始对象不改变解档对象,二者彼此独立,所以copyPerson依然是原来的值,这也说明实现了深拷贝。
- 使用
initWithXxx: copyItems:YES
方法
该方法只能实现一层深拷贝,因此只有当容器类对象中的对象是自定义类对象或者可变对象时,可以实现完全深拷贝。很好理解,因为自定义类对象和可变对象的拷贝都是深拷贝,因此使用该方法再实现一层深拷贝就实现了完全深拷贝。
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableString *str1 = [NSMutableString stringWithString:@"Alice"];NSMutableString *str2 = [NSMutableString stringWithString:@"Bob"];NSArray *arr = @[str1, str2];NSArray *copyArr = [[NSArray alloc] initWithArray:arr copyItems:YES];NSLog(@"%@---%p", arr, arr);NSLog(@"%@---%p", copyArr, copyArr);NSLog(@"%@---%p", str1, str1);NSLog(@"%@---%p", copyArr[0], copyArr[0]);[str1 appendString:@"Tom"];NSLog(@"%@---%p", str1, str1);NSLog(@"%@---%p", copyArr[0], copyArr[0]);}return 0;
}
运行结果:
从运行结果可以看出,通过这种方法只复制了内容,但地址不同,并且对原对象的修改不会影响copyArr对象的内容。
值得注意的是:如果不是上述所说的容器类对象中的对象是自定义类对象或者可变对象时,使用该方法实现的是单层深拷贝。
属性关键字
- 属性的本质是 ivar(实例变量)+getter(getter方法)+setter(setter方法)。
- 属性的修饰符应该按照顺序排列:原子操作、读写权限、内存管理。
@property、@synthesize和@dynamic
ivar+getter+setter的组合方式使得代码臃肿,因此使用@property、@synthesize和@dynamic来实现属性自动合成存取方法。
- @property:用于封装对象中的数据,自动生成属性的setter与getter方法的声明。
- @synthesize:自动生成setter和getter方法。
- @dynamic:告诉编译器不自动进行@synthesize,而是程序员手动实现setter和getter方法。
注意⚠️:
- 如果重写setter和getter方法,编译器就不会自动为@property生成@synthesize,需要手动添加。
- 如果在协议里使用@property声明一个属性,那么在某个类中遵循这个协议时,@synthesize需要手动添加同时生成实例变量和setter和getter方法。
#import <Foundation/Foundation.h>@protocol Demand <NSObject>-(void)testDemand;
@property(nonatomic, strong) NSString *name;@end@interface Person : NSObject<NSSecureCoding>@end#import "Person.h"@implementation Person@synthesize name = _name;-(void)testDemand {NSLog(@"属性关键字");
}
原子操作
属性是否具有原子性可以理解为线程是否安全。
同步锁:在多线程环境中,当多个线程同时访问或修改同一对象时,会导致程序崩溃等结果。同步锁则是一种用于确保在给定时间内只有一个线程可以执行某特定代码而保证线程安全的机制。
- atomic:原子性,加同步锁,但也不一定保证线程安全,且会损耗性能。
- nonatomic:非原子性,不加同步锁,使用这个可以提高访问性能,代码中常用nonatomic修饰属性。
读写权限
- readwrite:默认,生成setter和getter方法。
- readonly:只生成getter方法。
内存管理
- assign:
- 既可以修饰基本数据类型(NSInteger、BOOL、int、float 等),也可以修饰对象类型。
ps:如果使用assign修饰OC对象,对象销毁时可能会产生悬垂指针,从而出现程序崩溃,因此最好assign只用来修饰基本数据类型。
- 一般对于基本数据类型,setter方法的实现可以是直接赋值。
- 修饰对象类型时,不增加引用计数。
- 修饰delegate 在MRC使用assign.
- 会产生悬垂指针,assign修饰的对象在被释放后,指针仍然指向原对象地址,也就是说如果assign修饰的对象被释放后,仍通过该指针访问原对象的话,程序可能崩溃。
悬垂指针:指针所指的内存已经被释放(对象已被销毁),但指针本身仍然存在,并继续指向那块现在已经无效的内存地址。
- 既可以修饰基本数据类型(NSInteger、BOOL、int、float 等),也可以修饰对象类型。
- weak:
- 只能修饰对象类型(只能修饰OC对象,如UIButton、UIView等)。
- ARC下才能使用。
ARC: 是一种编译器特性,它*编译时自动为你插入适当的内存管理代码(
retain
,release
,autorelease
),而不是像传统手动引用计数(MRC)那样需要开发者自己写这些代码。 - 修饰弱引用,不增加引用计数,用于避免循环利用。
- weak 修饰的对象在被释放之后,会自动将指针置为 nil,不会产生悬垂指针。
- 对于有必要进行 remove 的视图可以使用 weak,remove 之后会自动置为 nil。
- 修饰delegate 在ARC使用weak。
- unsafe_unretained:
- 既可以修饰基本数据类型,也可以修饰对象类型。
- MRC 下经常使用,ARC 下基本不用。
- 修饰弱引用,同 weak,但性能更好,同时使用要求高,因为会产生悬垂指针。
- retain:
- MRC下使用。
- 修饰强引用,将指针原来指向的旧对象释放掉,然后指向新对象,同时新对象的引用计数加1。
- setter 方法的实现是 release 旧值,retain 新值,用于OC对象类型。
strong 🆚 copy
- strong:
- ARC 下才能使用。
- 原理同 retain,但在修饰 block 时,strong 相当于 copy,而 retain 相当于 assign。
- 浅拷贝
- copy:
- setter 方法的实现是 release 旧值,copy 新值。一般用于 block、NSString、NSArray、NSDictionary 等类型。
- copy设置的属性会复制传入的对象,而不是仅仅保持对传入对象的引用,用于确保属性拥有自己的独立副本,使得避免不经意的更改。
- 区别:
- 属性声明使用copy修饰,则合成方法使用类的copy方法,生成一个对象的不可变副本。
- strong修饰的的对象的赋值是多个指针指向同一个地址,而copy对象的赋值是每次在内存中开辟一个新内存地址,指针指向不同的地址。
@interface Person : NSObject<NSSecureCoding>@property(nonatomic, strong) NSString *strStrong;
@property(nonatomic, copy) NSString *strCopy;
@property(nonatomic, strong) NSMutableString *mutablestrStrong;
@property(nonatomic, copy) NSMutableString *mutablestrCopy;-(void)testDemo;@end-(void)testDemo {NSMutableString *str = [NSMutableString stringWithString:@"Hello"];NSLog(@"%@---%p", str, str);self.strStrong = str;NSLog(@"%@---%p", self.strStrong, self.strStrong);self.strCopy = str;NSLog(@"%@---%p", self.strCopy, self.strCopy);self.mutablestrStrong = str;NSLog(@"%@---%p", self.mutablestrStrong, self.mutablestrStrong);self.mutablestrCopy = str;NSLog(@"%@---%p", self.mutablestrCopy, self.mutablestrCopy);[str appendString:@" iOS"];NSLog(@"%@---%p", self.strStrong, self.strStrong);NSLog(@"%@---%p", self.strCopy, self.strCopy);NSLog(@"%@---%p", self.mutablestrStrong, self.mutablestrStrong);NSLog(@"%@---%p", self.mutablestrCopy, self.mutablestrCopy);
}
运行结果:
从运行结果看的出,正如我们的结论所说,strong修饰的赋值指向同一个地址(浅拷贝),copy修饰的赋值指向新的地址(深拷贝)。因此,当原值改变时,strong修饰的对象的指针还指向原内存,因此值随之改变,而copy的不会改变。
因此,对于copy修饰的对象,为确保对象中的字符串值不会无意改动,应该在赋值前拷贝一份。
总结
属性关键字的知识还有很多,后续学习后会完善补充。