進入本主題之前首先需要了解一些準備知識!
那就是地址與指針的關系:計算機的內存儲器被劃分為一個個的內存單元。內存單元按照一定的規則編號,這個編號就是存儲單元的地址。地址編碼的最基本單位是字節(一個字節由8個二進制位組成),也就是說每個字節就是一個基本內存單元,對應一個地址。計算機就是通過這種地址編碼的方式來管理內存數據的,進行讀寫的操作。
在程序中通過兩種方式實現對內存的存取:一是通過變量,二是通過內存空間的地址。
具有靜態生存期的變量在程序開始運行前,也就是在編譯的時候就已經分配了內存空間:具有動態生存期的變量,在程序運行時遇到變量的聲明語句才被分配內存空間。在變量獲取內存空間的同時,變量名也就成為了對應內存空間的名稱,代表內存中的數據,所以可以通過變量名來訪問內存空間。
如果是動態分配的內存單元,則根本就沒有名稱,只能通過地址訪問。例如:在不同函數之間傳遞大量數據時,如果不是傳遞變量值,而是傳遞變量地址,就會減少系統開銷,提高效率。
動態內存分配:是指在程序執行過程中,動態地分配或者回收存儲空間的分配內存的方法。不像數組那樣需要預先分配存儲空間,而是由系統根據程序的需要分配,且大小就是程序要求的大小。最常用的就是鏈表進行動態分配內存。
好了開始進入正題;
在對于自定義的對象支持copy功能,也就是我們要給自定義的對象發送copy message,那我們就要手動實現NSCopying協議。在項目開發中我們如果對某個字典或者數組對象進行了一次mutbleCopy其實系統默認的調用了如下API:
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
首先大家看一下這段代碼的測試結果(復制的別人的加上自己的見解)
對于copy/mutableCopy?? NSString
NSString *string = @"hahahhahha";
//?string的%p打印輸出的是地址(存儲內容是@"hahahhahha")
//string的%@打印輸出的是存儲內容"hahahhahha"
棧區地址string=0x10001040
堆區內容string=@"hahahhahha";
NSString *copyString = [string copy];
// 沒有產生新對象(系統沒有再開辟一塊"hahahhahha"的內存空間,只是在棧上開辟了一個內存空間,用于存儲"hahahhahha"的地址)
NSMutableString *mutableCopyString = [string mutableCopy];
// 產生新對象(系統會在堆區再開辟一塊"hahahhahha"的內存空間,并且在棧上開辟了一個內存空間,用于存儲"新對象的內存空間"的地址)
NSLog(@"string = %p copyString = %p mutableCopyString = %p", string, copyString, mutableCopyString);
string=0x100001040 copyString=0x100001040 mutableCopyString=0x10020e320
對于copy/mutableCopy NSMutableString
NSMutableString *string = [NSMutableString stringWithString:@"嘿嘿"];
// string的%p打印輸出的是地址(存儲內容是@"嘿嘿")
//string的%@打印輸出的是存儲內容"嘿嘿"
棧區地址string=0x100206930
堆區內容string=@“嘿嘿”
NSString *copyString = [string copy];
// 產生新對象(系統會在堆區再開辟一塊"嘿嘿"的內存空間,并且在棧上開辟了一個內存空間,用于存儲"新對象的內存空間"的地址)
NSMutableString *mutableCopyString = [string mutableCopy];
// 產生新對象(系統會在堆區再開辟一塊"嘿嘿"的內存空間,并且在棧上開辟了一個內存空間,用于存儲"新對象的內存空間"的地址)
NSLog(@"string = %p copyString = %p mutableCopyString = %p", string, copyString, mutableCopyString);
string=0x100206930 copyString=0x100206a80 mutableCopyString=0x100206ab0
對于其它的對象nsarray,nsdictionary,nsmutablearray,nsmutabledictionary同樣適用
上面的拷貝類型的理解:淺拷貝(指針拷貝)是指在棧區開辟了一塊內存,存儲了和原對象地址一樣的地址,這兩個棧區中的地址指向同一個堆區中的對象。說的通俗一點就是淺拷貝相當于對指針進行了一次retain操作,引用計數器加1,多了一個指針指向共同的對象。
深拷貝(內容拷貝)是指不僅在堆區新開辟了一塊內存,存儲對象,并且還在棧區開辟了一塊內存,存儲了和原對象不一樣的地址,新開辟棧區存儲的地址就是在堆區新開辟對象內存空間的地址。
總結:對于不可變對象的copy,都是淺拷貝,拷貝出來的對象都是不可變對象;對于可變對象的copy,都是深拷貝,拷貝出來的對象是不可變對象;對于不可變對象的mutablecopy,都是深拷貝,拷貝出來的對象都是可變對象;對于可變對象的mutablecopy,都是深拷貝,拷貝出來的對象都是可變對象。想要讓自定義的對象支持copy和mutableCopy那么就要實現NSCopying協議,和NSMutableCopying協議。
copy與strong的區別
@interface ZPerson : NSObject
@property (nonatomic, readwrite,copy) NSString *name;
@end
NSMutableString *string = [NSMutableString stringWithFormat:@"11111"];
ZPerson *person = [[ZPerson alloc] init];
person.name = string;
//person.name = string實際是對name這樣操作了
//-(void)setName:(NSString*)name?? MRC下
{
if(_name){
[_name release];
}
_name=[name copy];//此處copy是深拷貝,則person.name的地址是在棧區新開辟內存中存儲的地址,和string的地址不一樣,因此所指向的對象就不一樣,對string的操作不影響person.name的內容。
}
[string appendString:@" hans"];
NSLog(@"name = %@", person.name);
輸出內容name=11111
@property (nonatomic, strong) NSString *name;
NSMutableString *string = [NSMutableString stringWithFormat:@"11111"];
ZPerson *person = [[ZPerson alloc] init];
person.name = string;
//? ARC下 因為name是strong類型的,person.name = string;相當于對string淺拷貝,引用計數加1,string和person.name指向同一對象,因此對string的操作就相當于對person.name的操作。
此處copy是深拷貝,則person.name的地址是在棧區新開辟內存中存儲的地址,和string的地址不一樣,因此所指向的對象就不一樣,對string的操作不影響person.name的內容。
[string appendString:@" hans"];
NSLog(@"name = %@", person.name);
輸出內容name=11111hans