OC-copy,单例
总结
编号 | 主题 | 内容 |
---|---|---|
一 | NSFileManager | NSFileManager介绍/用法(常见的判断)/文件访问/文件操作 |
二 | 集合对象的内存管理 | 集合对象的内存管理/内存管理总结 |
三 | *copy | copy基本概念/使用/copy快速入门/内存管理 |
四 | @Property中的copy关键字 | @property中的copy的作用/@property内存管理策略选择 |
五 | 自定义类实现copy操作 | 自定义类实现copy操作 |
六 | 单例设计模式 | 单例模式概念/简单的单例模式实现 |
七 | 宏定义抽取单例 |
一. NSFileManager
1.NSFileManager介绍
-
什么是NSFileManager
- 顾名思义, NSFileManager是用来管理文件系统的
- 它可以用来进行常见的文件\文件夹操作
-
NSFileManager使用了单例模式
- 使用defaultManager方法可以获得那个
单例
对象
- 使用defaultManager方法可以获得那个
[NSFileManager defaultManager]
2.NSFileManager用法
- - (BOOL)fileExistsAtPath:(NSString *)path;
- path这个文件\文件夹是否存在
NSFileManager *manager = [NSFileManager defaultManager]; // 可以判断文件 BOOL flag = [manager fileExistsAtPath:@"/Users/MJ-Hee/Desktop/lnj.txt"]; NSLog(@"flag = %i", flag); // 可以判断文件夹 flag = [manager fileExistsAtPath:@"/Users/MJ-Hee/Desktop/未命名文件夹"]; NSLog(@"flag = %i", flag);
- - (BOOL)fileExistsAtPath:(NSString )path isDirectory:(BOOL)isDirectory;
- path这个文件\文件夹是否存在, isDirectory代表是否为文件夹
NSFileManager *manager = [NSFileManager defaultManager]; BOOL directory = NO; BOOL flag = [manager fileExistsAtPath:@"/Users/MJ-Hee/Desktop/未命名文件夹" isDirectory:&directory]; NSLog(@"flag = %i, directory = %i", flag, directory);
-
- (BOOL)isReadableFileAtPath:(NSString *)path;
- path这个文件\文件夹是否可读
-
- (BOOL)isWritableFileAtPath:(NSString *)path;
- path这个文件\文件夹是否可写
-
系统目录不允许写入
-
- (BOOL)isDeletableFileAtPath:(NSString *)path;
- path这个文件\文件夹是否可删除
-
系统目录不允许删除
3.NSFileManager的文件访问
- - (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error;
- 获得path这个文件\文件夹的属性
NSFileManager *manager = [NSFileManager defaultManager]; NSDictionary *dict = [manager attributesOfItemAtPath:@"/Users/MJ-Hee/Desktop/hmj.txt" error:nil]; NSLog(@"dit = %@", dict);
-
- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error;
- 获得path的当前子路径
-
- (NSData )contentsAtPath:(NSString )path;
- 获得文件内容
- 只能获取当前文件夹下的所有文件,不能获取子文件夹下的文件
NSFileManager *manager = [NSFileManager defaultManager]; NSArray *paths = [manager contentsOfDirectoryAtPath:@"/Users/MJ-Hee/Desktop" error:nil]; NSLog(@"paths = %@", paths);
-
- (NSArray )subpathsAtPath:(NSString )path;
-
- (NSArray *)subpathsOfDirectoryAtPath:(NSString *)path error:(NSError **)error;
- 都能获得path的所有子路径
- 第二个有error监听
NSFileManager *manager = [NSFileManager defaultManager]; NSArray *paths = [manager subpathsAtPath:@"/Users/LNJ/Desktop/"]; NSLog(@"paths = %@", paths);
4.NSFileManager的文件操作
-
- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- 拷贝(生成新的)
-
- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- 移动(剪切,不生成新的)
-
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error;
- 删除
-
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error;
- 创建文件夹(createIntermediates为YES代表自动创建中间的文件夹)
- createFileAtPath: 创建到那个位置
- withIntermediateDirectories: 如果指定的文件中有一些文件夹不存在,是否自动创建不存在的文件夹
- attributes:指定创建出来的文件夹的属性
- error: 是否创建成功,失败的话会给传入的参数赋值
NSFileManager *manager = [NSFileManager defaultManager]; BOOL flag = [manager createDirectoryAtPath:@"/Users/LNJ/Desktop/test" withIntermediateDirectories:YES attributes:nil error:nil]; NSLog(@"flag = %i", flag);
- - (BOOL)createFileAtPath:(NSString )path contents:(NSData)data attributes:(NSDictionary *)attr;
- 创建文件(NSData是用来存储二进制字节数据的)
- createFileAtPath:指定创建文件的地址
- contents:创建的文件的内容
- attributes:指定创建出来的文件的属性
NSString *str = @"lnj"; //NSData : 二进制数据 //将字符串转化成二进制数据 NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; NSFileManager *manager = [NSFileManager defaultManager]; BOOL flag = [manager createFileAtPath:@"/Users/LNJ/Desktop/abc.txt" contents:data attributes:nil]; NSLog(@"flag = %i", flag);
- 注意
- 该方法只能用于创建文件,不能用于创建文件夹
二.集合对象的内存管理
1.集合对象的内存管理
- 当一个对象加入到集合中,那么该对象的引用计数会+1
- 当集合被销毁的时候,集合会向集合中的元素发送release消息
- 通过类对象创建的对象不需要release
- NSMutableArray *arr = [NSMutableArray array];
- 通过类对象创建的对象不需要release
NSMutableArray *arr = [[NSMutableArray alloc] init]; Person *p = [[Person alloc] init]; NSLog(@"retainCount = %lu", [p retainCount]); //如果将一个对象添加到一个数组/字典中,那么数组会对对象进行一次retain [arr addObject:p]; NSLog(@"retainCount = %lu", [p retainCount]); [p release]; NSLog(@"retainCount = %lu", [p retainCount]); //当数组对象释放后,会给数组中所有的对象发送一条release消息 [arr release];
- 当一个对象加入到集合中,那么该对象的引用计数会+1
- 当把一个对象从集合中移除时,会向移除的元素发送release消息
NSMutableArray *arr = [[NSMutableArray alloc] init]; Person *p = [[Person alloc] init]; NSLog(@"retainCount = %lu", [p retainCount]); [arr addObject:p]; NSLog(@"retainCount = %lu", [p retainCount]); //当数组移除一个对象之后,会给这个对象发送一条release消息 [arr removeObject:p]; NSLog(@"retainCount = %lu", [p retainCount]); [p release]; [arr release];
2.集合对象内存管理总结
-
1.官方内存管理原则
- 1> 当调用alloc、new、copy(mutableCopy)方法产生一个新对象的时候,就必须在最后调用一次release或者autorelease
- 2> 当调用retain方法让对象的计数器+1,就必须在最后调用一次release或者autorelease
-
2.集合的内存管理细节
- 1> 当把一个对象添加到集合中时,这个对象会做了一次retain操作,计数器会+1
- 2> 当一个集合被销毁时,会对集合里面的所有对象做一次release操作,计数器会-1
- 3> 当一个对象从集合中移除时,这个对象会一次release操作,计数器会-1
-
3.普遍规律
- 1> 如果方法名是add\insert开头,那么被添加的对象,计数器会+1
- 2> 如果方法名是remove\delete开头,那么被移除的对象,计数器-1
三.copy
1.copy基本概念
-
什么是copy
- Copy的字面意思是“复制”、“拷贝”,是一个产生副本的过程
-
常见的复制有:文件复制
- 作用:利用一个源文件产生一个副本文件
-
特点:
- 修改源文件的内容,不会影响副本文件
- 修改副本文件的内容,不会影响源文件
-
OC中的copy
- 作用:利用一个源对象产生一个副本对象
- 特点:
- 修改源对象的属性和行为,不会影响副本对象
- 修改副本对象的属性和行为,不会影响源对象
2.Copy的使用
-
如何使用copy功能
- 一个对象可以调用copy或mutableCopy方法来创建一个副本对象
- copy : 创建的是不可变副本(如NSString、NSArray、NSDictionary)
- mutableCopy :创建的是可变副本(如NSMutableString、NSMutableArray、NSMutableDictionary)
-
使用copy功能的前提
- copy : 需要遵守NSCopying协议,实现copyWithZone:方法
@protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end
- 使用mutableCopy的前提
- 需要遵守NSMutableCopying协议,实现mutableCopyWithZone:方法
@protocol NSMutableCopying - (id)mutableCopyWithZone:(NSZone *)zone; @end
- 一般情况下,拷贝会生成一个新对象
2.深复制和浅复制
- 不可变对象调用copy,不会生成一个新对象
- 因为原来的和copy的对象都是不可变的,满足需求,所以不需要重新生成一个新对象
- 浅复制(浅拷贝,指针拷贝,shallow copy)
- 源对象和副本对象是同一个对象
- 源对象(副本对象)引用计数器+1,相当于做一次retain操作
- 本质是:没有产生新的对象
NSString *srcStr = @"lnj"; NSString *copyStr = [srcStr copy]; NSLog(@"src = %p, copy = %p", srcStr, copyStr);
- 深复制(深拷贝,内容拷贝,deep copy)
- 源对象和副本对象是不同的两个对象
- 源对象引用计数器不变,副本对象计数器为1(因为是新产生的)
- 本质是:产生了新的对象
NSString *srcStr = @"lnj"; NSMutableString *copyStr = [srcStr mutableCopy]; NSLog(@"src = %p, copy = %p", srcStr, copyStr); NSLog(@"src = %@, copy = %@", srcStr, copyStr); [copyStr appendString:@" cool"]; NSLog(@"src = %@, copy = %@", srcStr, copyStr); NSMutableString *srcStr = [NSMutableString stringWithFormat:@"lnj"]; NSString *copyStr = [srcStr copy]; [srcStr appendString:@" cool"]; NSLog(@"src = %p, copy = %p", srcStr, copyStr); NSLog(@"src = %@, copy = %@", srcStr, copyStr); NSMutableString *srcStr = [NSMutableString stringWithFormat:@"lnj"]; NSMutableString *copyStr = [srcStr mutableCopy]; [srcStr appendString:@" cool"]; [copyStr appendString:@" 520it"]; NSLog(@"src = %p, copy = %p", srcStr, copyStr); NSLog(@"src = %@, copy = %@", srcStr, copyStr);
- 只有源对象和副本对象都不可变时,才是浅复制,其它都是深复制
- 只有当源对象是不可变调用copy时才是浅拷贝
内存管理
- 在iOS环境下运行
-
总结 +如果是浅拷贝,那么系统会自动对源对象进行一次retain
- 如果是深拷贝,会生成新对象,那么系统不会对源对象进行retain,但需要对新对象进行release
-
浅拷贝的原则
- alloc/new/retain/copy必须有对应的release
四.@Property中的copy关键字
1.@property中的copy的作用
- 防止外界修改内部成员变量的值
@interface Person : NSObject //用retain,外界可以修改内部的数据,此处改为copy可以防止外界修改内部的数据 @property (nonatomic, retain) NSString *name; @end NSMutableString *str = [NSMutableString stringWithFormat:@"lnj"]; Person *p = [[Person alloc] init]; p.name = str; // person中的属性会被修改 [str appendString:@" cool"]; NSLog(@"name = %@", p.name);
- 可以使用copy保存block,这样可以保住block中使用的外界对象的命
- 防止访问对象时,对象已经释放
- 不用copy情况
Person *p = [[Person alloc] init]; p.name = @"lnj"; Dog *d = [[Dog alloc] init]; d.age = 10; NSLog(@"retainCount = %lu", [d retainCount]); // 1 p.pBlock = ^{ // 报错, 调用之前就销毁了 NSLog(@"age = %d", d.age); }; [d release]; // 0 p.pBlock(); [p release];
- 用copy情况
- 不用copy情况
Person *p = [[Person alloc] init]; p.name = @"lnj"; Dog *d = [[Dog alloc] init]; d.age = 10; NSLog(@"retainCount = %lu", [d retainCount]); // 1 p.pBlock = ^{ // 会对使用到的外界对象进行一次retain NSLog(@"age = %d", d.age); NSLog(@"retainCount = %lu", [d retainCount]); // 1 }; [d release]; // 1 p.pBlock(); [p release];
- copy block之后引发的循环引用
- 如果对象中的block又用到了自己,那么为了泄露内存,应该将对象修饰为__block
2.@property内存管理策略选择
-
非ARC
- 1> copy : 只用于NSString\block
- 2> retain : 除NSString\block以外的OC对象
- 3> assign :基本数据类型、枚举、结构体(非OC对象),当2个对象相互引用,一端用retain,一端用assign
-
ARC
- 1> copy : 只用于NSString\block
- 2> strong : 除NSString\block以外的OC对象
- 3> weak : 当2个对象相互引用,一端用strong,一端用weak
- 4> assgin : 基本数据类型、枚举、结构体(非OC对象)
- 注意
- 只要是block使用copy,不是拷贝,而是将block从栈中转移到堆中
- 只要
五. 自定义类实现copy操作
1.自定义类实现copy操作
- 让类遵守NSCopying协议
- 实现 copyWithZone:方法,在该方法中返回一个对象的副本即可。
- 在copyWithZone方法中,创建一个新的对象,并设置该对象的数据与现有对象一致, 并返回该对象.
-
zone: 表示空间,分配对象是需要内存空间的,如果指定了zone,就可以指定 新建对象对应的内存空间。但是:zone是一个非常古老的技术,为了避免在堆中出现内存碎片而使用的。在今天的开发中,zone几乎可以忽略
-
- 以后想让自定义的对象能够被copy,只需要遵守NSCopying协议
- 实现协议中的-(id)copyWithZone(NSZone *)zone方法
-
在-(id)copyWithZone(NSZone *)zone方法中创建一个副本对象,然后将当前对象的值赋值给副本对象即可
-
无父类实现
-(id)copyWithZone(NSZone *)zone{ //class用于获取一个类 CustomMode *custom = [[[self class] copyWithZone:zone] init]; Custom ->_a = [_a copyWithZone:zone]; Custom -> _c = _c;//不是对象的 直接赋值 Return custom; }
- 有父类实现
- 不调用父类方法, 无法拷贝父类中继承的属性
- 不重新父类copyWithZone, 无法拷贝本来中的特有属性
- 如果想让子类调用copy时,仍保留子类特有的属性,那么就需要重写copyWithZone,并在copyWithZone中调用父类的copyWithZone,即[super copyWithZone:zone]
-(id)copyWithZone(NSZone *)zone{ CustomModel *custom = [super copyWithZone:zone]; //只调用子类特有的成员变量的setter方法即可 …. Return custom; }
六.单例设计模式
- 可以实现对象的共享
1.单例模式概念
-
什么是单例模式:(Singleton)
- 单例模式的意图是让类的对象成为系统中唯一的实例,供一个访问点,供客户类共享资源。
-
什么情况下使用单例?
- 1、类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方法。
- 2、这个唯一的实例只能通过子类化进行扩展,而且扩展的对象不会破坏客户端代码。
-
单例设计模式的要点:
- 1)某个类只能有一个实例。
- 2)他必须自行创建这个对象
- 3)必须自行向整个系统提供这个实例;
- 4)这个方法必须是一个静态类
- 5)一般情况下,创建一个单例对象都有一个与之对应的
类
方法 - 6)用于创建单例对象的方法名称都以share开头,或者以default开头
2.简单的单例模式实现
- 调用alloc时,内部会调用allocWithZone
static 类名 *_instance = nil; + (instancetype)allowWithZone:(struct _NSZone *)zone { //由于所有的创建方法都会调用该方法,所以只需要在该方法中控制当前对象只创建一次即可 if (_instance == nil) { NSLog(@"创建了一个对象); _instance = [[super allowWithZone:zone] init; } return _instance; //以下代码在多线程中也能保证只执行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[super allowWithZone:zone] init]; } ;) return _instance; } //copyWithZone方法用什么调用?对象 - (id)copyWithZone:(NSZone *)zone { return _instance; } - (id)mutableCopyWithZone:(NSZone *)zone { return _instance; } - (oneway void) release { //为保证整个程序过程中只有一份实例,在这个方法中什么都不做 } - (instancetype)retain { return _instance; } - (NSUInteger)retainCount { //注意:为了方便程序员之间沟通,一般情况下不会在单例中返回retainCount = 1,而是返回一个比较大的值 return MAXFLOAT; }
七.宏定义抽取单例
// 如何判断当前是ARC还是MRC? // 可以在编译的时候判断当前是否是ARC #if __has_feature(objc_arc) NSLog(@"ARC"); #else NSLog(@"MRC"); #endif
/*********Singleton.h**********/
// 以后就可以使用interfaceSingleton来替代后面的方法声明
#define interfaceSingleton(name) +(instancetype)share##name
#if __has_feature(objc_arc) // ARC #define implementationSingleton(name) \ + (instancetype)share##name \ { \ name *instance = [[self alloc] init]; \ return instance; \ } \ static name *_instance = nil; \ + (instancetype)allocWithZone:(struct _NSZone *)zone \ { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ _instance = [[super allocWithZone:zone] init]; \ }); \ return _instance; \ } \ - (id)copyWithZone:(NSZone *)zone{ \ return _instance; \ } \ - (id)mutableCopyWithZone:(NSZone *)zone \ { \ return _instance; \ } #else // MRC #define implementationSingleton(name) \ + (instancetype)share##name \ { \ name *instance = [[self alloc] init]; \ return instance; \ } \ static name *_instance = nil; \ + (instancetype)allocWithZone:(struct _NSZone *)zone \ { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ _instance = [[super allocWithZone:zone] init]; \ }); \ return _instance; \ } \ - (id)copyWithZone:(NSZone *)zone{ \ return _instance; \ } \ - (id)mutableCopyWithZone:(NSZone *)zone \ { \ return _instance; \ } \ - (oneway void)release \ { \ } \ - (instancetype)retain \ { \ return _instance; \ } \ - (NSUInteger)retainCount \ { \ return MAXFLOAT; \ } #endif /********自定义类.h中********/ #import "Singleton.h" interfaceSingleton(类名); /********自定义类.m中********/ implementationSingleton(类名)