Copy的那點事兒~,Copy事兒~
Copy的簡單使用copy 的效果
對來源物件進行拷貝,建立出新的副本,彼此修改互不干擾!
###OC中copy的方法
copy–>建立對象的副本
- 如果對象有可變/不可變版本的區別,copy方法,只能拷貝出
不可變的版本
- 如果對象沒有可變/不可變的區別,copy方法就是建立一個副本
mutableCopy
* 建立對象的可變副本(如果對象有”可變/不可變”版本的區別,才需要使用此方法)
###副本的特點
彼此的內容一樣,具有相同的方法
可變版本對象的copy
/** 可變版本對象的copy 對於可變對象的copy/mutableCopy都是深拷貝 */- (void)copyDemo1 { // arrayM本身為可變對象 NSMutableArray *arrayM = [NSMutableArray array]; NSLog(@"來源物件\t\t%@\t記憶體位址%p", arrayM.class, arrayM); // copy ---> 不可變 地址變化 新的對象 // 無論是可變對象,還是不可以變對象,copy之後都會編程 不可變 id a = [arrayM copy]; NSLog(@"copy後\t%@\t記憶體位址%p", [a class], a); // mutableCopy => 可變 地址變化 新的對象,mutableCopy可以保持可變的特性 id aM = [arrayM mutableCopy]; NSLog(@"mutableCopy後%@\t記憶體位址%p", [aM class], aM);}
運行結果:
來源物件 __NSArrayM 記憶體位址0x7f91c2c1d940copy後 __NSArrayI 記憶體位址0x7f91c2e09a00mutableCopy後__NSArrayM 記憶體位址0x7f91c2d23660
不可變版本對象的copy
/** 不可變版本對象的copy */- (void)copyDemo2 { NSArray *array = [NSArray array]; NSLog(@"來源物件%@ 記憶體位址%p", array.class, array); // copy ---> 不可變 地址沒有變化 引用計數+1 // 對於不可變對象的copy操作,進行的淺拷貝,系統並不會為之分配記憶體空間,僅僅是retainCount+1 id a = [array copy]; NSLog(@"copy後%@ 記憶體位址%p", [a class], a); // mutableCopy ---> 可變 地址變化 新的對象 // 不可變的mutaleCopy的操作會變為可變對象 id aM = [array mutableCopy]; NSLog(@"mutableCopy%@ 記憶體位址%p", [aM class], aM);}
執行結果:
來源物件__NSArrayI 記憶體位址0x7fb4e07010d0 copy後__NSArrayI 記憶體位址0x7fb4e07010d0 mutableCopy__NSArrayM 記憶體位址0x7fb4e063daf0
小結:
不要隨隨變變給可變對象做 copy 操作
都會建立新的副本,深拷貝(只要有一個可以修改,就是深拷貝)
可變 ----> 可變可變 ----> 不可變不可變 ----> 可變
不會建立新的副本,只是引用計數+1,淺拷貝,指標拷貝(兩個對象前後都不需要修改)
不可變 =》 不可變
Copy屬性
在物件導向程式開發中,有一個非常重要的原則
####開閉原則
-開:對內開放,向怎麼改,就怎麼改
-閉:對外封閉,只能用,不能改
建議:如果屬性是 NSString,建議使用 copy 屬性
注意:可變字串一定不要使用 copy 屬性
// 頭銜,如果區分可變和不可變版本,做一次copy操作得到的就是不可變的字串!@property (nonatomic, copy) NSMutableString *title;
對象的類型
- 1> 一個對象的準確類型,是在給該對象”分配記憶體空間”的時候指定的類型
- 2> 對象的”類型”,是程式員指定該對象的類型,指定類型之後,就可以具有該對象的方法!
- 3> 能否使用對象的方法,取決於運行時,這個對象的類型是否真的正確!
- 4> 如果類型不正確會出現 -[NSObject length]: unrecognized selector
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"]; Person *p = [[Person alloc] init]; p.name = strM; NSLog(@"%@", p.name); // zhangsan [strM setString:@"lisi"]; NSLog(@"===> %@", p.name); // zhangsan // 問題:p.name的類型 NSString & NSMutableString // 答案:NSCFString ---> NSString // NSMutableString類型的資料做了一次copy後,會變為不可變的NSString類型 id obj = p.name; NSLog(@"%@", [p.name class]); // NSCFString// [obj setString:@"wangwu"]; // 報錯,不應該對NSString進行修改 NSLog(@"===> %@", p.name); // wangwu
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"]; NSString *str = @"haha"; NSLog(@"%p", str); // 0x10a182138 Person *p = [[Person alloc] init];// p.name = strM; p.name = str; NSLog(@"%p", p.name); //0x10a182138 跟 str地址是一樣滴~,p.name指向了str的空間 NSLog(@"%@", p.name); // haha [strM setString:@"lisi"]; NSLog(@"===> %@ %@", p.name, strM); // p.name 和 strM不可能一樣,strM的改變不會影響到p.name
// 從網路擷取到一個字串 NSMutableString *strM = [NSMutableString stringWithString:@"BOSS"]; Person *p = [[Person alloc] init]; p.title = strM; [strM setString:@"經理"]; NSLog(@"===> %@ %@", p.title, strM); // Attempt to mutate immutable object with setString: // 試圖使用 setString: 方法修改"不可變對象"? // setString方法,是title存在之後,修改title的內容! [p.title setString:@"jingli"]; // 程式會崩掉,p.title為不可變對象 NSLog(@"!!!!> %@ %@", p.title, strM);
Copy的自訂對象
在很多商業級應用程式或者第三方架構,在開發時的模型通常會支援 copy
NSCache & NSMutableDictionary
-NSCache 的 key strong 的-Dict 的 key 是 copy 的
// NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
// dictM setObject:<#(id)#> forKey:<#(id<NSCopying>)#>
如果自訂對象要當作字典的 key,需要支援 copy!
Person.h
/** 要讓自訂對象支援 copy,需要做兩件事情 1. 遵守 NSCopying 協議 2. 實現 copyWithZone: 方法 */@interface Person : NSObject <NSCopying>@property (nonatomic, copy) NSString *name;@property (nonatomic, assign) int age;
Person.m
/** 有傳回值 -》 copy 出來的新對象 是一個對象方法 -> 將 self 建立一個副本 zone: 空間,指派至是需要記憶體空間的,如果指定了zone,就可以指定建立對象對應的記憶體空間 但是:zone是一個非常古老的技術,為了避免在堆中出現記憶體片段而使用的 在今天的開發中,zone幾乎可以忽略 如果對象沒有 可變/不可變 的版本區別,只要實現 copyWithZone 方法即可 */- (id)copyWithZone:(NSZone *)zone { // copy 是要建立一個新的副本,和當前的對象具有相同的內容 // 1. 執行個體化 person 對象 Person *p = [[Person alloc] init]; p.name = self.name; p.age = self.age; return p;}// 只需要寫模型的description就可以了,返回對象的描述資訊,便於調試使用,類似於 java 中的 toString()- (NSString *)description { // JSON的格式和字典非常像// @{@"name": @"zhangsan", @"age": @(19)} return [NSString stringWithFormat:@"<%@: %p> {name: %@, age: %d}", self.class, self, self.name,self.age];}// 也可以輸出調試資訊的字串,專門用來調試使用的// 有的網站上的培訓資料會提到這個方法,跟 description 方法非常類似!// 但是:如果在應用程式中,使用了這個方法,應用程式無法上架!蘋果會認為使用了私人API//- (NSString *)debugDescription {////}
- (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; p.name = @"zhangsan"; p.age = 19; NSLog(@"%@--- %p", p.name, p); // zhangsan--- 0x7fbacbf17120 Person *p1 = [p copy]; p1.name = @"xiaofang"; // 注意,地址不同,說明實現了自訂對象的copy NSLog(@"%@--- %p", p1.name, p1); // xiaofang--- 0x7fbacbe13900}
子類對象的Copy
#import "Person.h"@interface Student : Person// 學號@property (nonatomic, copy) NSString *no;@end```objc<div class="se-preview-section-delimiter"></div>#import "Student.h"@implementation Student- (id)copyWithZone:(NSZone *)zone { // 執行父類的 copy 方法,會把父類中的屬性完全 copy Student *s = [super copyWithZone:zone]; // 在子類的copy方法中,只需要給子類特有的屬性進行賦值即可! s.no = self.no; return s;}@end<div class="se-preview-section-delimiter"></div>
在父類的copyWithZone方法中:
要寫:
Person *p = [[self.class alloc] init]; p.name = self.name; p.age = self.age;<div class="se-preview-section-delimiter"></div>
不能寫 Person * p = [[Person alloc] init];這麼幹了,只能copy出Person對象,不對子類起作用。
測試Demo
// 一個對象的準確類型,取決分配記憶體空間指定的類型 Person *p = [[Student alloc] init]; p.name = @"zhangsan"; p.age = 19; // 給對象指定的類型,決定了能夠使用對象的哪些屬性和方法// p.no = @"001";// NSLog(@"%@ %@", p, p.no); NSLog(@"%@", p); // {name: zhangsan, age: 19} // copy會執行父類的copy方法 Student *p1 = [p copy]; // 雖然是父類的引用,但實際上copy的是子類 p1.name = @"xiaofang"; NSLog(@"%@ %@", p1, p1.no); // <Student: 0x7fb0f9443150> {name: xiaofang, age: 19} (null)