相信現(xiàn)在在寫(xiě)OC @property 的時(shí)候特別是對(duì) NSString, 都已經(jīng)習(xí)慣的記住了使用 copy 關(guān)鍵字來(lái)進(jìn)行修飾。 然后我看到有些代碼里面對(duì) NSDictionary NSArray等對(duì)象卻依舊在使用 strong,來(lái)進(jìn)行修飾鹅巍。 我覺(jué)得既然 其對(duì)象被定義成了 UnMutableObject 我們?cè)诙x的時(shí)候畸裳,就應(yīng)該想到在其應(yīng)該是不可變的對(duì)象某残,如果我們違背了這個(gè)原則很容易就導(dǎo)致出現(xiàn)一些意想不到的問(wèn)題驹碍。 先來(lái)看一段簡(jiǎn)單的示意代碼背犯。
@interface ViewController ()
@property (nonatomic, strong) NSArray *rankArray;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSMutableArray* dataSour = [NSMutableArray arrayWithObjects:@"90",@"80",@"100",nil];
// 獲取數(shù)據(jù),進(jìn)行展示绍妨。
self.rankArray = dataSour;
// 正常顯示排行润脸。
NSLog(@"%@",self.rankArray);
[dataSour addObject:@"25"];
// 說(shuō)好的不可變數(shù)組呢? 為什么他去?突然冒出來(lái)了個(gè) 25毙驯?
NSLog(@"%@",self.strongName);
}
上面這段小例子告訴我們,為什么在聲明 不可變對(duì)象的時(shí)候灾测,為什么要使用 Copy爆价。而不是Strong。 由于其使用 Strong 導(dǎo)致其聲明的時(shí)候原本是一個(gè)不可變數(shù)組,但是在 set 方法中由于沒(méi)有使用 copy 來(lái)進(jìn)行賦值铭段。 導(dǎo)致自己已經(jīng)隱形的變成了一個(gè)可變數(shù)組骤宣。 但是如果使用 Copy 來(lái)聲明的話就不一樣的 。 因?yàn)椴还苣阍磾?shù)據(jù)類型是可變不可變序愚,進(jìn)過(guò) Copy 之后最終產(chǎn)物都是 不可變對(duì)象憔披。
上面的代碼寫(xiě)的很簡(jiǎn)單,當(dāng)然我們開(kāi)發(fā)中也不會(huì)犯這種錯(cuò)誤爸吮, 這只是一個(gè)簡(jiǎn)單的比喻芬膝。 我們可以想象一下,加入這個(gè)是一個(gè)B同事開(kāi)發(fā)的接口拗胜。 需要A同事需要調(diào)用這個(gè)接口 把 dataSour 傳到 B同事的接口的時(shí)候蔗候, 如果 A 直接傳入一個(gè)可變數(shù)組進(jìn)來(lái), 然后后面還修改了這段數(shù)據(jù)埂软。 那么后面的問(wèn)題可想而知了锈遥。
簡(jiǎn)單明了直接上一張, 測(cè)試研究結(jié)果圖,感興趣了解細(xì)節(jié)的可以往下看勘畔。如果有不正確的地方還望幫忙糾正所灸。
主要通過(guò) NSString、NSMutableString 通過(guò)簡(jiǎn)單的小例子來(lái)深入介紹炫七,可變對(duì)象和不可變對(duì)象使用 copy mutableCopy 得到的結(jié)果來(lái)說(shuō)明爬立,理解了 深淺拷貝之 對(duì)@property 引用計(jì)數(shù)關(guān)鍵字的理解和原理就能更加清晰明了。
一万哪、NSString
//分析字符串 深淺拷貝
- (void)analyzeString {
NSString* string = @"StringJerseyCafe";
// 淺拷貝侠驯、未生成新地址、對(duì)指針進(jìn)行了一份拷貝奕巍、指向原對(duì)象地址所指向的同一份內(nèi)容吟策。
NSString* copyString = [string copy];
// 深拷貝、生成了新的內(nèi)存地址的止、對(duì)內(nèi)容也進(jìn)行了一份拷貝檩坚、使用新的內(nèi)存地址指向新的內(nèi)容。
NSMutableString* mutableCopyString = [string mutableCopy];
// 圖一:
NSLog(@"String = %@-%p --- copyString = %@-%p ---- mutableCopyString = %@-%p/n", string, string, copyString, copyString, mutableCopyString, mutableCopyString);
// 證明淺拷貝和深拷貝原理诅福。
string = @"Jersey";
// 直接改變 string匾委、 其實(shí)相當(dāng)于將 string 重新分配一份內(nèi)存地址。
// 從copyString 可以看出氓润、 因?yàn)槠鋵?duì) String 的指針地址進(jìn)行了一份拷貝赂乐。 然后使用其同樣的內(nèi)存地址,指向的內(nèi)容還是同一份咖气。 所以當(dāng) string 改變了之后挨措、 并沒(méi)有影響到自己辐啄。
// mutableCopyString 更加不可能影響,其拷貝了一份內(nèi)容运嗜,然后生成另一份內(nèi)存地址。 指向拷貝出來(lái)的這份內(nèi)容悯舟。
// 圖二:
NSLog(@"String = %@-%p --- copyString = %@-%p ---- mutableCopyString = %@-%p/n", string, string, copyString, copyString, mutableCopyString, mutableCopyString);
}
結(jié)論: 不可變對(duì)象 copy 生成不可變對(duì)象,拷貝方式為淺拷貝担租。 執(zhí)行 mutableCopy 生成可變對(duì)象,拷貝方式為深拷貝。
二抵怎、NSMutableString
//分析可變字符串 深淺拷貝
- (void)analyzeMutableString {
NSMutableString* mutableString = [NSMutableString stringWithString:@"MutableStringJerseyCafe"];
// 可變字符串copy奋救、 拷貝其內(nèi)容,生成一份新地址 指向這份內(nèi)容反惕。 得到不可變字符串尝艘。
NSMutableString* copyMutableString = [mutableString copy];
NSMutableString* mutableCopyMutableString = [mutableString mutableCopy];
// 可變字符串 不管是執(zhí)行 copy、 mutableCopy 都是深拷貝姿染。 因?yàn)槠涠际巧梢环菪碌刂繁澈ィ缓髮?duì)原有內(nèi)容進(jìn)行一份拷貝。使新地址指向拷貝出來(lái)的同一份內(nèi)容悬赏。 所以下面的改變?cè)凶址畠?nèi)容, 也不會(huì)兩個(gè) 深拷貝出來(lái)的對(duì)象狡汉。唯一區(qū)別是 copy 得到不可變字符串,mutableCopy 得到可變字符串闽颇。 看見(jiàn)下面拼接驗(yàn)證盾戴。
// 圖三:
NSLog(@"mutableString = %@-%p --- copyMutableString = %@-%p ---- mutableCopyMutableString = %@-%p/n", mutableString, mutableString, copyMutableString, copyMutableString, mutableCopyMutableString, mutableCopyMutableString);
[mutableString appendFormat:@"改變可變字符串內(nèi)容"];
NSLog(@"mutableString = %@-%p --- copyMutableString = %@-%p ---- mutableCopyMutableString = %@-%p/n", mutableString, mutableString, copyMutableString, copyMutableString, mutableCopyMutableString, mutableCopyMutableString);
// 驗(yàn)證 mutableString copy 生成對(duì)象。 使用其拼接字符串兵多、 直接導(dǎo)致崩潰尖啡、 其屬于字符串而非可變字符串。
// [copyMutableString appendFormat:@"TestcopyMutableString"];
// 驗(yàn)證 mutableString mutableCopy 生成對(duì)象剩膘。 使用其拼接字符串衅斩、 返回結(jié)果正常、 其屬于可變字符串而非字符串援雇。
[mutableCopyMutableString appendFormat:@"TestmutableCopyMutableString"];
NSLog(@"mutableCopyMutableString = %@-%p/n", mutableCopyMutableString, mutableCopyMutableString);
}
結(jié)論: 可變對(duì)象 copy 生成不可變對(duì)象,拷貝方式為深拷貝矛渴。 執(zhí)行 mutableCopy 生成可變對(duì)象,拷貝方式為深拷貝。
三惫搏、NSString 與 Strong Copy Weak具温。
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, weak) NSString *weakName;
@property (nonatomic, strong) NSString *strongName;
@end
//使用字符串分析 copy與Strong與Weak 對(duì)其set方法有何影響
- (void)analyzeCopyandStrongWithString {
NSString *string = @"StringJerseyCafe";
// copy 修飾的字符串, 在進(jìn)行 set 方法時(shí), 只是對(duì) 當(dāng)前 string copy筐赔,所以結(jié)果就跟淺拷貝一樣铣猩。 復(fù)制指針指向同一份內(nèi)容。并不會(huì)對(duì)其引用計(jì)數(shù)器改變茴丰。 返回一個(gè) 不可變字符串达皿。
self.name = string;
// Strong 修飾的字符串天吓, 在進(jìn)行 set 方法時(shí), 對(duì)當(dāng)前 string retain, 使 string 引用計(jì)數(shù)器加1, 返回一個(gè)不可變字符串。該返回的對(duì)象指向 string 的內(nèi)存地址峦椰。
self.strongName = string;
// weak 修飾的字符串龄寞, 在進(jìn)行 set 方法時(shí), 只是簡(jiǎn)單的賦值到當(dāng)前 屬性上。所以 string 引用計(jì)數(shù)器不變汤功。 self.weakName 使用著 string內(nèi)存地址物邑,但是不會(huì)使 引用計(jì)數(shù)器加1。
self.weakName = string;
// 圖六:
NSLog(@"string = %@-%p --- Name = %@-%p ---- StrongName = %@-%p ---- weakName = %@-%p", string, string, self.name, self.name, self.strongName, self.strongName, self.weakName, self.weakName);
string = [NSString stringWithFormat:@"對(duì)String重新分配內(nèi)存地址"];
// 圖八:
NSLog(@"string = %@-%p --- Name = %@-%p ---- StrongName = %@-%p ---- weakName = %@-%p", string, string, self.name, self.name, self.strongName, self.strongName, self.weakName, self.weakName);
string = nil;
// 直接將String 的指向nil;
// 圖九:
NSLog(@"string = %@-%p --- Name = %@-%p ---- StrongName = %@-%p ---- weakName = %@-%p", string, string, self.name, self.name, self.strongName, self.strongName, self.weakName, self.weakName);
// 使用三種不同的 引用計(jì)數(shù)器修飾關(guān)鍵字, 然后對(duì) 原有 string 進(jìn)行內(nèi)容修改, 指針地址修改, 銷毀滔金。 都不會(huì)影響到原有屬性的指針或者內(nèi)容,這里具體原因 我沒(méi)有完全研究明白色解。
// 并且很奇怪的是此 字符串 在只有 weakName 使用的情況下,此時(shí)出了 string 作用域餐茵,正常應(yīng)該是已經(jīng)消耗的科阎。 但是我再 - (void)viewDidAppear:(BOOL)animated 函數(shù) 進(jìn)行測(cè)試,發(fā)現(xiàn)此 字符串并未釋放忿族。
// 但是如果使用下面可變字符串的話 則會(huì)釋放掉锣笨。 這個(gè)以后有時(shí)間在好好研究, 希望大神看到這塊可以指點(diǎn)一下。
}
// 測(cè)試 weak 對(duì)引用計(jì)數(shù)器的影響道批。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 正常
NSLog(@"測(cè)試weak引用是否成功釋放%@",self.weakName);
// 由于self.strongName 制空, 不在對(duì) string 對(duì)其引用票唆。此時(shí) string 引用計(jì)數(shù)器為0 , self.weakName由于也沒(méi)有對(duì)其引用,所以應(yīng)該直接釋放掉了。 但是不知道為什么原因 輸出結(jié)果表示其并未釋放
self.strongName = nil;
self.name = nil;
// 圖十:
NSLog(@"測(cè)試weak引用是否成功釋放%@",self.weakName);
}
結(jié)論: 不用的@property 引用計(jì)數(shù)關(guān)鍵字主要是 set 和 get 方法的影響屹徘、 使用 Strong 修飾則在進(jìn)行 set 方法時(shí) 是對(duì)當(dāng)前賦值變量進(jìn)行了 retain走趋, 使其引用計(jì)數(shù)器 +1, 使用 Copy 修飾則在進(jìn)行 set 方法時(shí) 是對(duì)當(dāng)前賦值變量進(jìn)行了 copy,不會(huì)使引用計(jì)數(shù)器 改變噪伊。 使用 weak 修飾主要是針對(duì) OC 變量的時(shí)候簿煌,只是簡(jiǎn)單的進(jìn)行了賦值操作 不會(huì)對(duì)其引用計(jì)數(shù)器造成變化。 與 assign 一樣鉴吹, 只是 assign 針對(duì)非 OC對(duì)象姨伟。
四、 NSMutableString 與 Strong Copy Weak豆励。
//使用可變字符串分析 copy與Strong 對(duì)其set方法有何影響
- (void)analyzeCopyandStrongWithMutableString {
NSMutableString *mutableString = [NSMutableString stringWithFormat:@"StringJerseyCafe"];
// copy 修飾的字符串夺荒, 在進(jìn)行 set 方法時(shí), 只是對(duì) 當(dāng)前 NSMutableString copy,所以結(jié)果是深拷貝 得到一個(gè) NSString 對(duì)象良蒸。 生成新對(duì)象指針指向同一份內(nèi)容技扼。也并不會(huì)對(duì)其引用計(jì)數(shù)器改變。 返回一個(gè) 不可變字符串嫩痰。
self.name = mutableString;
// Strong 修飾的字符串剿吻, 在進(jìn)行 set 方法時(shí), 對(duì)當(dāng)前 string retain, 使 string 引用計(jì)數(shù)器加1, 該返回的對(duì)象指向 string 的內(nèi)存地址。
self.strongName = mutableString;
// weak 修飾的字符串串纺, 在進(jìn)行 set 方法時(shí), 只是簡(jiǎn)單的賦值到當(dāng)前 屬性上丽旅。所以 string 引用計(jì)數(shù)器不變椰棘。 self.weakName 使用著 string內(nèi)存地址,但是不會(huì)使 引用計(jì)數(shù)器加1榄笙。
self.weakName = mutableString;
// 由輸出結(jié)果可以得出邪狞、 使用Strong 和 weak 修飾的屬性,其指針地址都是跟mutableString一致。 因?yàn)槠涠际鞘褂昧薽utableString 的指針地址 指向同一塊內(nèi)容,只是Strong 會(huì)對(duì) 其內(nèi)存增加一份引用計(jì)數(shù)器,而weak 不變而已茅撞。 在過(guò)來(lái)看 copy外恕。 由于其是可變字符串 copy、 其是深拷貝, 所以肯定會(huì)生成一份新地址, 然后指向其拷貝出來(lái)的相同的一份內(nèi)容乡翅。 所以其地址已經(jīng)改變了。得到的是一個(gè)不可變的字符串罪郊。 所以又這點(diǎn)也證明了為什么我們?cè)趯?xiě) @property 針對(duì) NSString NSArray NSDictionary 時(shí)都要使用 copy 來(lái)進(jìn)行修飾的原因, 這樣就成功了確保了 不過(guò)你使用 可變對(duì)象還是不可變對(duì)象 賦值到這個(gè)屬性的時(shí)候 最終結(jié)果都是只會(huì)得到 不可變的對(duì)象蠕蚜。 符合我們的預(yù)期結(jié)果。
// 圖十一:
NSLog(@"string = %@-%p --- Name = %@-%p ---- StrongName = %@-%p ---- weakName = %@-%p", mutableString, mutableString, self.name, self.name, self.strongName, self.strongName, self.weakName, self.weakName);
[mutableString appendFormat:@"對(duì)String 內(nèi)存地址所指向內(nèi)容進(jìn)行修改"]; // 由輸出結(jié)果可以得出, 由于使用 Strong 和 weak 修飾的屬性,其都是在使用 mutableString 地址, 所以當(dāng) mutableString 的內(nèi)容發(fā)生改變時(shí), 兩個(gè)屬性同樣也是指向這一份改變后的內(nèi)容的悔橄。 但是 Copy 修飾的就不一樣了靶累。 由于其是深拷貝出來(lái)的, 內(nèi)存地址完全是獨(dú)立的,其內(nèi)容也不可能會(huì)發(fā)生改變。
// 圖十二:
NSLog(@"string = %@-%p --- Name = %@-%p ---- StrongName = %@-%p ---- weakName = %@-%p", mutableString, mutableString, self.name, self.name, self.strongName, self.strongName, self.weakName, self.weakName);
mutableString = [NSMutableString stringWithFormat:@"對(duì)String重新分配一份內(nèi)存地址"]; // 我們對(duì) mutableString 重新分配內(nèi)存, Strong 和 weak 修飾的屬性還是指向原先的地址和內(nèi)容, Copy 也是一樣癣疟。 不會(huì)對(duì)其造成影響挣柬。
// 圖十三:
NSLog(@"string = %@-%p --- Name = %@-%p ---- StrongName = %@-%p ---- weakName = %@-%p", mutableString, mutableString, self.name, self.name, self.strongName, self.strongName, self.weakName, self.weakName);
mutableString = nil;
// 直接將String 的指向nil;
// 由輸出結(jié)果推理 Strong, 此方法只是將 mutableString 的指針地址指向nil。 但是這部分內(nèi)存還有 Strong 在引用著睛挚, 所以并不會(huì)釋放銷毀邪蛔,所以也不會(huì)對(duì)Strong 和 weak 造成影響。
// 但是如果嘗試不對(duì) Strong 進(jìn)行賦值扎狱,單獨(dú)只有 weak 在引用的話, 出了這個(gè)方法作用域, 此內(nèi)存就會(huì)被回收掉侧到。
// 嘗試- (void)viewDidAppear:(BOOL)animated 進(jìn)行測(cè)試。如果只有 weak 在使用則此內(nèi)存直接釋放淤击。Copy 修飾的同樣不會(huì)有任何影響匠抗, 因?yàn)槠湟呀?jīng)是獨(dú)立的一份內(nèi)存地址。
// 圖十四:
NSLog(@"string = %@-%p --- Name = %@-%p ---- StrongName = %@-%p ---- weakName = %@-%p", mutableString, mutableString, self.name, self.name, self.strongName, self.strongName, self.weakName, self.weakName);
}
// 測(cè)試 weak 對(duì)引用計(jì)數(shù)器的影響污抬。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 正常
NSLog(@"測(cè)試weak引用是否成功釋放%@",self.weakName);
// 由于self.strongName 制空, 不在對(duì) string 對(duì)其引用汞贸。此時(shí) string 引用計(jì)數(shù)器為0 , self.weakName由于也沒(méi)有對(duì)其引用,所以應(yīng)該直接釋放掉了。 但是不知道為什么原因 輸出結(jié)果表示其并未釋放
self.strongName = nil;
self.name = nil;
// 圖十五:
NSLog(@"測(cè)試weak引用是否成功釋放%@",self.weakName);
}