iOS內(nèi)存管理(3)-MRC寂屏、Copy

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)用allocnew袱讹、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就不得不說的是深拷貝和淺拷貝.

  1. 深拷貝:內(nèi)容拷貝,產(chǎn)生新的對象.源對象的引用計數(shù)器+1.
  2. 淺拷貝:指針拷貝,不產(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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尘颓,一起剝皮案震驚了整個濱河市走触,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疤苹,老刑警劉巖互广,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卧土,居然都是意外死亡惫皱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門尤莺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旅敷,“玉大人,你說我怎么就攤上這事颤霎∠彼” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵友酱,是天一觀的道長晴音。 經(jīng)常有香客問我,道長缔杉,這世上最難降的妖魔是什么锤躁? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮壮吩,結(jié)果婚禮上进苍,老公的妹妹穿的比我還像新娘加缘。我一直安慰自己鸭叙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布拣宏。 她就那樣靜靜地躺著沈贝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勋乾。 梳的紋絲不亂的頭發(fā)上宋下,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機(jī)與錄音辑莫,去河邊找鬼学歧。 笑死,一個胖子當(dāng)著我的面吹牛各吨,可吹牛的內(nèi)容都是我干的枝笨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼横浑!你這毒婦竟也來了剔桨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤徙融,失蹤者是張志新(化名)和其女友劉穎洒缀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欺冀,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡树绩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了隐轩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葱峡。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖龙助,靈堂內(nèi)的尸體忽然破棺而出蝙泼,到底是詐尸還是另有隱情稿湿,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站挽唉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏藐翎。R本人自食惡果不足惜橘忱,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赡鲜。 院中可真熱鬧空厌,春花似錦、人聲如沸银酬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揩瞪。三九已至赋朦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間李破,已是汗流浹背宠哄。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留嗤攻,地道東北人毛嫉。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像妇菱,于是被迫代替她去往敵國和親承粤。 傳聞我的和親對象是個殘疾皇子惊畏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,111評論 1 32
  • 前言 不敢說覆蓋OC中所有copy的知識點,但最起碼是目前最全的最新的一篇關(guān)于 copy的技術(shù)文檔了密任。后續(xù)發(fā)現(xiàn)有新...
    zyydeveloper閱讀 3,362評論 4 35
  • 1.設(shè)計模式是什么浪讳? 你知道哪些設(shè)計模式缰盏,并簡要敘述?設(shè)計模式是一種編碼經(jīng)驗淹遵,就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,165評論 0 12
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制口猜。與retain配對使用的方法是dealloc還是release,為什么透揣?需要與a...
    丶逐漸閱讀 1,971評論 1 16
  • It's Saturday. It was so queer to be put to bed in the da...
    Mr_Oldman閱讀 157評論 0 0