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

1. MRC

ObjC中的內(nèi)存管理機(jī)制跟C語言中指針的內(nèi)容是同樣重要的,要開發(fā)一個程序并不難,但是優(yōu)秀的程序則更測重于內(nèi)存管理,它們往往占用內(nèi)存更少,運行更加流暢。雖然在新版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、copymutableCopy方法返回了一個對象,在不需要這個對象時,要調(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容