iOS内存管理(3)-MRC、Copy

1. MRC

ObjC中的内存管理机制跟C语言中指针的内容是同样重要的,要开发一个程序并不难,但是优秀的程序则更测重于内存管理,它们往往占用内存更少,运行更加流畅。虽然在新版Xcode引入了ARC,但是很多时候它并不能完全解决你的问题。在Xcode中关闭ARC:项目属性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting设置为No即可。

MRC的理解:
①. 在iOS中,使用引用计数来管理OC对象的内存.
②. 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间.
③. 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
④. 内存管理的经验总结.
     当调用allocnewcopymutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它.
     想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1.
⑤. 可以通过以下私有函数来查看自动释放池的情况:extern void_objc_autoreleasePoolPrint(void);.

如果在MRC的环境下,有如下代码NSString *string = @"abc",当你对string做release操作的时候可能会得到retaincount的值为-1,原因是这时的string是一个Tagged Pointer的对象.

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i <100; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"asdfghjklzxcv"];
});
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i <100; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"asd"];
});
}
上边两段代码执行后会有什么问题?
第一个执行后会崩溃,因为是正常的OC对象,而不是tagged pointer,所以

  • (void)setName:(NSString *)name {
    if(_name != name){
    [_name release];
    _name = [name retrain];
    }
    }
    由于是异步线程原来的name通过release释放,又有一个线程来释放name,但是现在name的引用计数为0,不能再次释放,所以会崩溃。
    第二个属于tagged pointer,就不会发生这样的问题。

2. copy

提到copy,就会想到关键字copy,mutableCopy和深拷贝,浅拷贝等名词,但是这些名词都有是什么,都在什么时候使用,都有什么作用呢?
copy的目的是产生一个副本,但是这个副本不与源对象产生任何影响.iOS为此提供了两个方法copy和mutableCopy.

copy和mutableCopy.

copy:拷贝的结果是一个不可变(imutable)的对象, 无论源对象是可变的还是不可变的,copy之后的都是不可变的类型.

不可变类型 变量名 = [不可变类型 copy];
不可变类型 变量名 = [可变类型 copy];

mutableCopy:可变拷贝的结果的数据类型是一个可变的对象,无论源对象时不可变的还是可变的,可变拷贝之后的数据类型都是可变类型.

可变类型 变量名 = [不可变类型 mutableCopy];
可变类型 变量名 = [可变类型 mutableCopy];

copy与mutableCopy的使用

一.非集合类对象(NSString,NSMutableString,NSData,NSNumber...)的copy 和 mutableCopy
NSString *str1 = @"imutable";                                
NSString *Str2 = [str1 copy];                           
NSMutableString *Str3 = [str1 mutableCopy];   
NSMutableString *str4 = [[NSMutableString alloc]initWithString:@"mutable"];
NSMutableString *str5 = [str4 copy];
NSMutableString *str6 = [str4 mutableCopy];  
[str6 appendFormat:@"hello"];
[str5 appendFormat:@"hello"];   // crash
二. 集合类对象(NSArray,NSDictionary,NSSet...)的copy 和 mutableCopy
NSArray *array0 = @[@"a",@"b",@"c"];
NSArray *array1 = [array0 copy];
NSArray *array2 = [array0 mutableCopy];
NSMutableArray *array3 = [[NSMutableArray alloc]initWithObjects:@"a",@"b",@"c", nil];
NSMutableArray *array4 = [array3 copy];
NSMutableArray *array5 = [array3 mutableCopy];

总结

  • 对系统非容器类不可变对象调用Copy方法其实只是把当前对象的指针指向了原对象的地址。
  • 调用mutableCopy方法则是新分配了一块内存区域并把新对象的指针指向了这块区域。
  • 对于可变对象来说调用Copy和MutableCopy方法都会重新分配一块内存。
  • copy和mutableCopy的区别在于copy在复制对象的时候其实是返回了一个不可变对象,因此当调用方法改变对象的时候会崩溃。
三. @property中copy关键字

当我们使用一个copy关键字声明一个对象的时候, 调用 set 方法的时候,copy关键字会为对象自动copy一个副本,举个例子:

@property (nonatomic, copy) NSArray *array;
- (void)setArray:(NSArray *)array {
_array = [array copy];  //这里为array  copy 了一个副本
}

如果我们直接用strong关键字的话,又是怎样的呢?

@property (nonatomic, strong) NSArray *array;
- (void)setArray:(NSArray *)array {
//他们指向了同一块内存空间,如果此时传入的array是一个NSMutableArray的话,
//self.array可能会在不知情的情况下被修改。这种情况下面还会再说到
_array = array;  
}

为什么用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字?使用strong关键字,会有什么问题?
在.h定义一个以 strong 修饰的 array:@property (nonatomic , strong) NSArray*array;
在.m中实现

NSMutableArray *muArray = [[NSMutableArray alloc] init]; //0x0000000170253290
self.array = muArray;// self.array  0x0000000170253290 self.array和muArray指向同一块内存地址
[muArray addObject:@1];// muArray log(1)
NSLog(@"%@",self.array);// self.array log(1)
[muArray removeAllObjects];// muArray log()
NSLog(@"%@",self.array);// self.array log()
//由上面的结果可以看出,因为self.array和muArray指向同一块内存地址,所以对muArray的操作,会直接影响到self.array
NSArray *array = @[@1,@2,@3,@4];
[muArray addObjectsFromArray:array];//muArray 0x0000000170253290
self.array = muArray.copy;//这里进行了深拷贝,self.array 0x00000001702532f0
NSLog(@"%@",self.array);// self.array log(1,2,3,4)
[muArray removeAllObjects];// muArray log()
NSLog(@"%@",self.array);// self.array log(1,2,3,4)

1.因为父类指针可以指向子类对象(如上面的NSArray对象可以指向一个NSMutableArray对象),使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
2.如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

上面解释了为什么用@property声明不可变对象(NSString、NSArray,NSDictionary)时,经常用copy关键字,接下来我们来解释为什么要用strong关键字来声明可变对象(NSMutableString、NSMutableArray、NSMutableDictionary),而不用copy对象?
假如我们用copy关键字在.h里来声明一个NSMutableArray对象:@property (nonatomic, copy) NSMutableArray *mutableArray;.
.m实现方法

NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array1;
[self.mutableArray removeObjectAtIndex:0]; //crash

上面执行到 removeObjectAtIndex 会crash,原因是 mutableArray 是用copy关键字声明的,copy返回的是一个不可变对象,也就是NSMutableArray会变成NSArray,然后你再执行removeObjectAtIndex方法,就会报找不到这个方法而crash.

四. 单层拷贝和完全拷贝
 NSMutableString * str1 =  [NSMutableString stringWithString:@"Bian"] ;
NSMutableString * str2 = [NSMutableString stringWithString:@"Sun"] ;
NSMutableArray * mutableArr = [[NSMutableArray alloc] initWithObjects:str1,str2, nil];
NSMutableArray * copyMutableArr = [mutableArr mutableCopy];

NSLog(@"mutableArr:%p %p %p",mutableArr,mutableArr[0],mutableArr[1]);
NSLog(@"copyMutableArr:%p %p %p",copyMutableArr,copyMutableArr[0],copyMutableArr[1]);
 // 修改str1的值
 [str1 insertString:@"abc" atIndex:0];
NSLog(@"mutableArr:%p %p %p",mutableArr,mutableArr[0],mutableArr[1]);
NSLog(@"copyMutableArr:%p %p %p",copyMutableArr,copyMutableArr[0],copyMutableArr[1]);
NSLog(@"%@",copyMutableArr[0]);
/** 打印结果:
mutableArr:0x600003ddaa90 0x600003ddaa30 0x600003dda9d0
copyMutableArr:0x600003ddaac0 0x600003ddaa30 0x600003dda9d0
mutableArr:0x600003ddaa90 0x600003ddaa30 0x600003dda9d0
copyMutableArr:0x600003ddaac0 0x600003ddaa30 0x600003dda9d0
copyMutableArr的str1的值:abcBian
*/

单层拷贝:单层深拷贝是指集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制,mutableArr的深拷贝copyMutableArr开辟了新的内存,但是里面值得内存地址还和mutableArr共享一份地址,明显就是指针拷贝,所以说这不是完全意义上的深拷贝,叫单层深拷贝!

//只需这样创建深拷贝,就是完全深拷贝
 NSMutableArray * copyMutableArr = [[NSMutableArray alloc] initWithArray:mutableArr copyItems:YES];

完全复制:完全复制是指集合对象的内容复制不仅限于对象本身,对象元素也是要复制

五. 深拷贝和浅拷贝

说道copy和mutableCopy就不得不说的是深拷贝和浅拷贝.

  1. 深拷贝:内容拷贝,产生新的对象.源对象的引用计数器+1.
  2. 浅拷贝:指针拷贝,不产生新的对象. 源对象的引用计数器不变.
    但是我们如何判断copy是深拷贝还是浅拷贝呢?其实我们主要判断是通过是深拷贝还是浅拷贝就看不拷贝是否对原来的对象的值产生影响.如果有影响就是深拷贝,如果没有影响就是浅拷贝.
    我们可以总结一下:
copy mutableCopy
NSString NSString(浅拷贝) NSMutableString(深拷贝)
NSMutableString NSString(深拷贝) NSMutableString(深拷贝)
NSArray NSArray(浅拷贝) NSMutableArray(深拷贝)
NSMutableArray NSArray(深拷贝) NSMutableArray(深拷贝)
NSDictionary NSDictionary(浅拷贝) NSMutableDictionary(深拷贝)
NSMutableDictionary NSDictionary(深拷贝) NSMutableDictionary(深拷贝)

@property(copy,notomic)NSMutableArray *data;会有什么问题
在set方法会变成
-(void)setData:(NSMutableArray *)data{
if(_data != data){
[_data release];
_data = [data copy];
{
}
所以这里的可变对象在经过set方法后会变成不可变对象,可变数据的方法使用会报错。

六. 自定义对象

在iOS中并不是所有对象都支持Copy和MutableCopy,遵循NSCopying协议的类可以发送Copy协议,遵循NSMutableCopying协议的类可以发送MutableCopy消息。如果一个对象没有遵循这两个协议而发送Copy或者MutableCopy消息那么会发生异常。如果要遵循NSCopying协议,那么必须实现copyWithZone方法。
如果要遵循NSMutableCopying协议那么必须实现mutableCopyWithZone方法。
对于自定义对象来说调用Copy和MutableCopy方法都会重新分配一块内存。

//  Man.h
#import <Foundation/Foundation.h>

@interface Man : NSObject<NSCopying,NSMutableCopying>
@property (nonatomic,strong)NSString *name;
@property (nonatomic,assign)NSInteger year;
@end
//  Man.m
#import "Man.h"

@implementation Man
#pragma mark description方法内部不能打印self,不然会造成死循环
- (NSString *)description {
    return [NSString stringWithFormat:@"[name = %@,year = %ld]", _name,_year];
}
//自定义深拷贝,实现copyWithZone方法
-(id)copyWithZone:(NSZone *)zone{
    Man *newMan = [[[self class] allocWithZone:zone] init];
    newMan.name = self.name;
    newMan.year = self.year;
    return newMan;
}

-(id)mutableCopyWithZone:(NSZone *)zone{
    Man *newMan = [[[self class] allocWithZone:zone] init];
    newMan.name = self.name;
    newMan.year = self.year;
    return newMan;
}
@end
//调用
    Man *man = [[Man alloc]init];
    man.name = @"张三";
    man.year = 1;
    Man *newMan = [man copy];
    Man *newMutMan = [man mutableCopy];
    NSLog(@"man = %@,man地址 = %p,newMan = %@,newMan地址 = %p,newMutMan = %@, newMutMan地址 =  %p",man,man,newMan,newMan,newMutMan,newMutMan);
    /**
     man = [name = 张三,year = 1],
     man地址 = 0x604000036900,
     newMan = [name = 张三,year = 1],
     newMan地址 = 0x6040004207e0,
     newMutMan = [name = 张三,year = 1],
     newMutMan地址 =  0x60400003c2a0
     */
    newMan.name = @"李四";
    NSLog(@"man = %@,man地址 = %p,newMan = %@,newMan地址 = %p,newMutMan = %@, newMutMan地址 =  %p",man,man,newMan,newMan,newMutMan,newMutMan);
    /**
     man = [name = 张三,year = 1],
     man地址 = 0x604000036900,
     newMan = [name = 李四,year = 1],
     newMan地址 = 0x6040004207e0,
     newMutMan = [name = 张三,year = 1],
     newMutMan地址 =  0x60400003c2a0
     */
    newMutMan.name = @"王五";
    NSLog(@"man = %@,man地址 = %p,newMan = %@,newMan地址 = %p,newMutMan = %@, newMutMan地址 =  %p",man,man,newMan,newMan,newMutMan,newMutMan);
    /**
     man = [name = 张三,year = 1],
     man地址 = 0x604000036900,
     newMan = [name = 李四,year = 1],
     newMan地址 = 0x6040004207e0,
     newMutMan = [name = 王五,year = 1],
     newMutMan地址 =  0x60400003c2a0
     */
                            想了解更多iOS学习知识请联系:QQ(814299221)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 前言 不敢说覆盖OC中所有copy的知识点,但最起码是目前最全的最新的一篇关于 copy的技术文档了。后续发现有新...
    zyydeveloper阅读 3,353评论 4 35
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述?设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型...
    龍飝阅读 2,140评论 0 12
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,960评论 1 16
  • It's Saturday. It was so queer to be put to bed in the da...
    Mr_Oldman阅读 155评论 0 0