1. MRC
ObjC中的內(nèi)存管理機(jī)制跟C語言中指針的內(nèi)容是同樣重要的,要開發(fā)一個程序并不難,但是優(yōu)秀的程序則更測重于內(nèi)存管理迁霎,它們往往占用內(nèi)存更少吱抚,運(yùn)行更加流暢。雖然在新版Xcode引入了ARC考廉,但是很多時候它并不能完全解決你的問題秘豹。在Xcode中關(guān)閉ARC:項目屬性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting設(shè)置為No即可。
MRC的理解:
①. 在iOS中昌粤,使用引用計數(shù)來管理OC對象的內(nèi)存.
②. 一個新創(chuàng)建的OC對象引用計數(shù)默認(rèn)是1既绕,當(dāng)引用計數(shù)減為0,OC對象就會銷毀涮坐,釋放其占用的內(nèi)存空間.
③. 調(diào)用retain
會讓OC對象的引用計數(shù)+1凄贩,調(diào)用release
會讓OC對象的引用計數(shù)-1
④. 內(nèi)存管理的經(jīng)驗總結(jié).
? ???當(dāng)調(diào)用alloc
、new
袱讹、copy
疲扎、mutableCopy
方法返回了一個對象,在不需要這個對象時廓译,要調(diào)用release
或者autorelease
來釋放它.
? ???想擁有某個對象评肆,就讓它的引用計數(shù)+1债查;不想再擁有某個對象非区,就讓它的引用計數(shù)-1.
⑤. 可以通過以下私有函數(shù)來查看自動釋放池的情況:extern void_objc_autoreleasePoolPrint(void);.
如果在MRC的環(huán)境下,有如下代碼NSString *string = @"abc",當(dāng)你對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"];
});
}
上邊兩段代碼執(zhí)行后會有什么問題?
第一個執(zhí)行后會崩潰盹廷,因為是正常的OC對象征绸,而不是tagged pointer,所以
- (void)setName:(NSString *)name {
if(_name != name){
[_name release];
_name = [name retrain];
}
}
由于是異步線程原來的name通過release釋放俄占,又有一個線程來釋放name管怠,但是現(xiàn)在name的引用計數(shù)為0,不能再次釋放缸榄,所以會崩潰渤弛。
第二個屬于tagged pointer,就不會發(fā)生這樣的問題甚带。
2. copy
提到copy,就會想到關(guān)鍵字copy,mutableCopy和深拷貝,淺拷貝等名詞,但是這些名詞都有是什么,都在什么時候使用,都有什么作用呢?
copy的目的是產(chǎn)生一個副本,但是這個副本不與源對象產(chǎn)生任何影響.iOS為此提供了兩個方法copy和mutableCopy.
copy和mutableCopy.
copy:拷貝的結(jié)果是一個不可變(imutable)的對象, 無論源對象是可變的還是不可變的她肯,copy之后的都是不可變的類型.
不可變類型 變量名 = [不可變類型 copy];
不可變類型 變量名 = [可變類型 copy];
mutableCopy:可變拷貝的結(jié)果的數(shù)據(jù)類型是一個可變的對象,無論源對象時不可變的還是可變的鹰贵,可變拷貝之后的數(shù)據(jù)類型都是可變類型.
可變類型 變量名 = [不可變類型 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];
總結(jié)
- 對系統(tǒng)非容器類不可變對象調(diào)用Copy方法其實只是把當(dāng)前對象的指針指向了原對象的地址晴氨。
- 調(diào)用mutableCopy方法則是新分配了一塊內(nèi)存區(qū)域并把新對象的指針指向了這塊區(qū)域。
- 對于可變對象來說調(diào)用Copy和MutableCopy方法都會重新分配一塊內(nèi)存碉输。
- copy和mutableCopy的區(qū)別在于copy在復(fù)制對象的時候其實是返回了一個不可變對象籽前,因此當(dāng)調(diào)用方法改變對象的時候會崩潰。
三. @property
中copy關(guān)鍵字
當(dāng)我們使用一個copy關(guān)鍵字聲明一個對象的時候, 調(diào)用 set 方法的時候枝哄,copy關(guān)鍵字會為對象自動copy一個副本肄梨,舉個例子:
@property (nonatomic, copy) NSArray *array;
- (void)setArray:(NSArray *)array {
_array = [array copy]; //這里為array copy 了一個副本
}
如果我們直接用strong關(guān)鍵字的話,又是怎樣的呢挠锥?
@property (nonatomic, strong) NSArray *array;
- (void)setArray:(NSArray *)array {
//他們指向了同一塊內(nèi)存空間峭范,如果此時傳入的array是一個NSMutableArray的話,
//self.array可能會在不知情的情況下被修改瘪贱。這種情況下面還會再說到
_array = array;
}
為什么用@property聲明的NSString(或NSArray纱控,NSDictionary)經(jīng)常使用copy關(guān)鍵字?使用strong關(guān)鍵字菜秦,會有什么問題甜害?
在.h定義一個以 strong 修飾的 array:@property (nonatomic , strong) NSArray*array;
在.m中實現(xiàn)
NSMutableArray *muArray = [[NSMutableArray alloc] init]; //0x0000000170253290
self.array = muArray;// self.array 0x0000000170253290 self.array和muArray指向同一塊內(nèi)存地址
[muArray addObject:@1];// muArray log(1)
NSLog(@"%@",self.array);// self.array log(1)
[muArray removeAllObjects];// muArray log()
NSLog(@"%@",self.array);// self.array log()
//由上面的結(jié)果可以看出,因為self.array和muArray指向同一塊內(nèi)存地址,所以對muArray的操作,會直接影響到self.array
NSArray *array = @[@1,@2,@3,@4];
[muArray addObjectsFromArray:array];//muArray 0x0000000170253290
self.array = muArray.copy;//這里進(jìn)行了深拷貝,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)時尔店,經(jīng)常用copy關(guān)鍵字,接下來我們來解釋為什么要用strong關(guān)鍵字來聲明可變對象(NSMutableString主慰、NSMutableArray嚣州、NSMutableDictionary),而不用copy對象?
假如我們用copy關(guān)鍵字在.h里來聲明一個NSMutableArray對象:@property (nonatomic, copy) NSMutableArray *mutableArray;
.
.m實現(xiàn)方法
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array1;
[self.mutableArray removeObjectAtIndex:0]; //crash
上面執(zhí)行到 removeObjectAtIndex 會crash共螺,原因是 mutableArray 是用copy關(guān)鍵字聲明的该肴,copy返回的是一個不可變對象,也就是NSMutableArray會變成NSArray,然后你再執(zhí)行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]);
/** 打印結(jié)果:
mutableArr:0x600003ddaa90 0x600003ddaa30 0x600003dda9d0
copyMutableArr:0x600003ddaac0 0x600003ddaa30 0x600003dda9d0
mutableArr:0x600003ddaa90 0x600003ddaa30 0x600003dda9d0
copyMutableArr:0x600003ddaac0 0x600003ddaa30 0x600003dda9d0
copyMutableArr的str1的值:abcBian
*/
單層拷貝:單層深拷貝是指集合對象的內(nèi)容復(fù)制僅限于對象本身匀哄,對象元素仍然是指針復(fù)制,mutableArr的深拷貝copyMutableArr開辟了新的內(nèi)存雏蛮,但是里面值得內(nèi)存地址還和mutableArr共享一份地址涎嚼,明顯就是指針拷貝,所以說這不是完全意義上的深拷貝挑秉,叫單層深拷貝法梯!
//只需這樣創(chuàng)建深拷貝,就是完全深拷貝
NSMutableArray * copyMutableArr = [[NSMutableArray alloc] initWithArray:mutableArr copyItems:YES];
完全復(fù)制:完全復(fù)制是指集合對象的內(nèi)容復(fù)制不僅限于對象本身,對象元素也是要復(fù)制
五. 深拷貝和淺拷貝
說道copy和mutableCopy就不得不說的是深拷貝和淺拷貝.
- 深拷貝:內(nèi)容拷貝,產(chǎn)生新的對象.源對象的引用計數(shù)器+1.
- 淺拷貝:指針拷貝,不產(chǎn)生新的對象. 源對象的引用計數(shù)器不變.
但是我們?nèi)绾闻袛郼opy是深拷貝還是淺拷貝呢?其實我們主要判斷是通過是深拷貝還是淺拷貝就看不拷貝是否對原來的對象的值產(chǎn)生影響.如果有影響就是深拷貝,如果沒有影響就是淺拷貝.
我們可以總結(jié)一下:
表 | 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];
{
}
所以這里的可變對象在經(jīng)過set方法后會變成不可變對象犀概,可變數(shù)據(jù)的方法使用會報錯立哑。
六. 自定義對象
在iOS中并不是所有對象都支持Copy和MutableCopy,遵循NSCopying協(xié)議的類可以發(fā)送Copy協(xié)議,遵循NSMutableCopying協(xié)議的類可以發(fā)送MutableCopy消息阱冶。如果一個對象沒有遵循這兩個協(xié)議而發(fā)送Copy或者M(jìn)utableCopy消息那么會發(fā)生異常刁憋。如果要遵循NSCopying協(xié)議,那么必須實現(xiàn)copyWithZone方法木蹬。
如果要遵循NSMutableCopying協(xié)議那么必須實現(xiàn)mutableCopyWithZone方法至耻。
對于自定義對象來說調(diào)用Copy和MutableCopy方法都會重新分配一塊內(nèi)存若皱。
// 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方法內(nèi)部不能打印self,不然會造成死循環(huán)
- (NSString *)description {
return [NSString stringWithFormat:@"[name = %@,year = %ld]", _name,_year];
}
//自定義深拷貝,實現(xiàn)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
//調(diào)用
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學(xué)習(xí)知識請聯(lián)系:QQ(814299221)