OC-属性关键字
深浅拷贝
OC对象的拷贝方式分为浅拷贝和深拷贝两种方式
浅拷贝
本质就是创建一个存放与复制对象相同地址的指针变量,所以也称为指针拷贝
深拷贝
创建一个与被复制对象的值完全相同的对象,但是他们的地址是不同的,所以也称为内容拷贝
对于可变类与非可变类他们的深浅拷贝总结下来就是,不可变类的copy是浅拷贝,mutablecopy是深拷贝。可变类的copy和mutablecopy都是深拷贝
整理
可变对象 -> copy :深拷贝 -> 不可变对象
可变对象 -> mutableCopy : 深拷贝 -> 可变对象
不可变对象 -> copy : 浅拷贝 -> 不可变对象
不可变对象 -> mutableCopy : 深拷贝 -> 可变对象
自定义类的深浅拷贝
自定义类的拷贝必须实现NSCopying与NSMutableCopying协议
自定义对象实现copy与mutableCopy都是深拷贝
NSCopying协议的标准写法
- (id)copyWithZone:(NSZone *)zone {Coffee* coffee = [[[self class] allocWithZone:zone] init];coffee.name = [self.name copy];return coffee;;
}
思考
为什么使用self.class而不是直接写Coffee类?这是为了防止如果后续有子类继承了这个类,但是没有重写copyWithZone:那么父类的实现依然会被调用,使用[self class]可以保证正确生成当前运行时类的实例。而不是Coffee实例对象
归档与解档代码实现
实现解档与归档:之前有了解过,这里再回顾一遍加深印象
关键在与为自定义类实现两个方法,如下
#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];
容器类拷贝
单层深拷贝
使用initWithXxx: copyItems:YES方法
int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableString* str = [NSMutableString stringWithString:@"hi"];NSArray* src = @[str];NSArray* dst = [[NSArray alloc] initWithArray:src copyItems:YES];NSLog(@"%p - %p", src[0],dst[0]);NSLog(@"%p - %p", src, dst);}return 0;
}
我们打印结果如下:
发现当容器里面没有容器时,里面一层的元素被深拷贝了,那么如果容器里面的容器的元素呢?我们试一下
int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableString* s = [NSMutableString stringWithString:@"hi"];NSArray* arr = @[s, @[@"hello"]];NSArray* newArr = [[NSArray alloc] initWithArray:arr copyItems:YES];NSLog(@"%p - %p", arr[1][0], newArr[1][0]);}return 0;
}
我们打印结果如下:
显然,里面容器的元素并为被深拷贝,依旧是指针引用
完全深拷贝
当我们想要容器与所有层级的元素都变成彼此独立的新对象时,常用两种方法,一种就是上面的解档归档方法,最方便,代码上面有讲解
属性关键字
通过@property、@synthesized、@dynamic这三个关键字实现属性自动合成存取方法
@property
自动生成属性的setter与getter方法的声明,用于封装对象中的数据,属性本质是ivar + setter + getter
@synthesize
帮我们自动生成setter与getter方法的实现以及合成实例变量
@dynamic
告诉编译器不用自动进行@synthesized,会在运行时提供这些方法的实现,无需产生警告
现在的版本已经不需要在单独写@synthesize了。但是需要注意下面几点
如果重写了setter与getter方法,则编译器就不会自动为这个@property添加@synthesize
如果该属性是readonly,那么只要从写了getter方法,property autosynhesis就不会执行
如果我们在协议中使用@property声明了一个属性,在某个类中遵循这个协议,我们就必须要在类中使用@synthesize来获取这个属性的成员变量,同时获得setter/getter的实现函数
#import <UIKit/UIKit.h>
@protocol Demand <NSObject>
- (void)clickButton;
@property(nonatomic, copy)NSString* name;
@end
@interface ViewController : UIViewController<Demand>
@end
#import "ViewController.h"
#import "Masonry.h"
@interface ViewController ()
@property (nonatomic, strong)UITableView* tableView;
@end
@implementation ViewController
@synthesize name = _name;
- (void)clickButton {NSLog(@"pop");
}
原子操作
属性是否具有原子性可以理解为线程是否安全
atomic
原子性,加同步锁,是默认的修饰符,使用这个会消耗性能,也不一定保证线层安全,如果需要保证线程安全,需要使用其他锁机制
nonatomic
非原子性,不实用同步锁,声明属性时基本都设置为nonatomic,可以提高访问性能
读写权限
默认为readwrite,属性拥有setter与getter方法
readonly
只读,仅提供getter方法
内存管理
weak
ARC机制下才可以使用,修饰弱引用,不增加对象的引用计数,主要可以用于避免循环引用。weak修饰的对象在释放之后,会自动将指针置为nil,不会产生悬垂指针(指向已经被释放或失效的指针)
assign
既可以修饰基本数据类型,也可以修饰对象类型。setter实现是直接赋值,常用于基本数据类型。修饰对象类型时不增加其引用计数,会产生悬垂指针(其修饰的对象在被释放后,指针依然指向原对象地址,该指针成为悬垂指针,此时如果依然通过该指针访问原对象的话,就可能导致程序崩溃),之所以可以修饰基本数据类型,因为基本数据类型一般分配在栈上,栈的内存会由系统自动处理,不会造野指针。MRC下的delegate往往是assign,此操作是为了防止delegate和self等自身产生循环引用
__unsafe_unretain
与 _weak一样,唯一的区别在于对象即使被销毁,指针也不会被自动置为nil,此时指针指向的是一个无用的地址(悬垂指针),如果使用此地址,程序会抛出异常
下面这段代码可以很好的理解
id __unsafe_unretained temp = [[NSMutableArray alloc] init];
[temp addobject:@"pop"];
为什么会崩溃呢?因为适用这个修饰符的话,不会增加引用计数,并且也不会把指针置为nil,后面使用它的话就是在操作一个已经被释放的对象。
copy
用于修饰OC对象类型,大多是不可变类,在调用setter方法给成员变量赋值时,会先生成一个被复制对象的副本,在将该副本复赋值成员变量
retain
MRC下使用,ARC下基本使用strong
修饰强引用,保留旧值,释放旧值,在设置新值,同时引用计数加一
setter方法实现如下,先release旧值,retain新值,用于OC对象
- (void)setObj:(NSObject)newObj {if (_obj != newObj) {[_obj release];_obj = [newObj retain];}
}
strong
ARC下使用,原理与retain相同,但是在修饰Block时,strong相当于copy,而retain相当于assign(跟block的特性有关,创建是在栈上,使用strong强引用的话,需要将block复制到堆区,延长生命周期)
copy和strong的对比
copy:深拷贝,内存地址不同,指针地址也不同,同样是release旧值,copy新值
strong:浅拷贝,内存地址不变,指针地址不同,逻辑同retain,(ARC)编译器会在合适的时候自动插入retain/release代码
如果属性声明中指定了copy特性,合成方法会使用类的copy方法。属性并没有mutableCopy特性,即使是可变的实例变量,使用的也是copy特性。会生成一个不可变副本
strong和copy关键字的用法
@property属性用copy修饰不可变对象,用strong修饰可变对象
常用的基本类型对应Foundation数据类型?
在声明一个属性时,尽量使用Foundation框架的数据类型,统一代码的数据类型
定义属性的格式
@property (nonatomic, readwrite, copy) NSString* name;
按照原子操作、读写权限、内存管理排序
ARC下@property的默认属性
基本数据类型:atomic、readwrite、assign
OC对象:atomic、readwrite、strong
注意事项
避免使用copy来修饰可变对象
如果一定要用copy的话,就重写setter方法