更新:
- 2018.10.12 修改了copy與strong修飾NSString啥辨、NSArray這些類型的解釋和用法
開發(fā)環(huán)境:
Mac系統(tǒng)版本:macOS Mojave 10.14(18A391)
Xcode:Version 10.0 (10A255)
結(jié)論:
就怕你想看結(jié)論又不想看長篇大論涡匀,就把結(jié)論寫到上面,免得你滑屏幕累得慌
- strong 表示指向并擁有該對象溉知。其修飾的對象引用計(jì)數(shù)會(huì)增加 1陨瘩。該對象只要引用計(jì)數(shù)不為 0 則不會(huì)被銷毀。當(dāng)然強(qiáng)行將其設(shè)為 nil 可以銷毀它级乍。
- weak 表示指向但不擁有該對象舌劳。其修飾的對象引用計(jì)數(shù)不會(huì)增加。無需手動(dòng)設(shè)置玫荣,該對象會(huì)自行在內(nèi)存中銷毀甚淡。
- assign 主要用于修飾基本數(shù)據(jù)類型,如 NSInteger 和 CGFloat 捅厂,這些數(shù)值主要存在于棧上贯卦。
- weak 一般用來修飾對象,assign 一般用來修飾基本數(shù)據(jù)類型焙贷。
- copy 與 strong 類似撵割。不同之處是 strong 的復(fù)制是多個(gè)指針指向同一個(gè)地址,而 copy 的復(fù)制每次會(huì)在內(nèi)存中拷貝一份對象辙芍,指針指向不同地址睁枕。
- __weak、__strong與weak、strong類似外遇,區(qū)別是__weak注簿、__strong修飾變量,而weak跳仿、strong修飾屬性诡渴,而且__weak、__strong基本上都是與block相關(guān)菲语;
- 如果對象類型有對應(yīng)的可變類型妄辩,例如NSString、NSArray山上、NSDictionary等眼耀,需要結(jié)合場景合理使用,而對應(yīng)的可變類型用strong佩憾,使用copy會(huì)導(dǎo)致崩潰哮伟;
- delegate使用weak修飾;
- 各種視圖控件推薦使用weak妄帘,官方就是這樣做的楞黄;
探索:
1. 為什么assign不能用來修飾對象類型?
我先引用故胤道長在《iOS面試之道》這本書中的回答:
assign 修飾的對象被釋放后抡驼,指針的地址依然存在鬼廓,造成野指針,在堆上容易造成崩潰致盟。而棧上的內(nèi)存系統(tǒng)會(huì)自動(dòng)處理碎税,不會(huì)造成野指針。
總之一句話馏锡,使用assign修飾對象容易造成崩潰蚣录。
代碼驗(yàn)證環(huán)節(jié):
/// 定義一個(gè)assign修飾的字符串變量
@property (nonatomic, readwrite, assign) NSString *string_assign;
/// 驗(yàn)證代碼
- (void)influenceForNSStringWithAssign {
/// 此時(shí)self.string_assign的值為 null
NSLog(@"賦值前:string_assign = %@", self.string_assign);
{
NSMutableString *temp = [NSMutableString stringWithString:@"hello world"];
self.string_assign = temp;
/// 此處self.string_assign的值為 hello world
NSLog(@"賦值后:string_assign = %@", self.string_assign);
[temp appendString:@" changed"];
/// 此時(shí)self.string_assign的值為 hello world changed
NSLog(@"原始值修改后:string_assign = %@", self.string_assign);
}
/// 此時(shí)超出temp的作用域,temp被釋放眷篇,self.string_assign的指針地址依然存在萎河,成為野指針,此時(shí)使用self.string_assign就會(huì)造成崩潰
NSLog(@"超出原始值的作用于后:string_assign = %@\n\n", self.string_assign);
}
/// 控制臺(tái)輸出
2018-10-10 17:58:29.141563+0800 MemoryManagerDemo[36403:5926424] 賦值前:string_assign = (null)
2018-10-10 17:58:29.141712+0800 MemoryManagerDemo[36403:5926424] 賦值后:string_assign = hello world
2018-10-10 17:58:29.141827+0800 MemoryManagerDemo[36403:5926424] 原始值修改后:string_assign = hello world changed
(lldb) /// 此處崩潰
2. 使用copy和strong修飾NSString蕉饼、NSArray這類有對應(yīng)可變類型的對象類型有什么區(qū)別虐杯?
在對對象賦值的時(shí)候,如果是使用copy修飾昧港,那么會(huì)在內(nèi)存中將原對象的值拷貝一份擎椰,原對象與該對象指向的是不同的內(nèi)存區(qū)域,原對象在之后做任何修改都與該對象無關(guān)创肥;
而如果是使用strong修飾达舒,那么該對象與原對象雖然是不同的指針對象值朋,但指向的都是同一片內(nèi)存區(qū)域,如果原對象進(jìn)行了修改巩搏,那么即使這個(gè)對象是不可變的昨登,它的值也會(huì)發(fā)生變化;
如果確定在賦值之后贯底,原值不會(huì)修改丰辣,那么使用strong是比較好的選擇,畢竟禽捆,copy會(huì)消耗系統(tǒng)資源笙什,能省一點(diǎn)是一點(diǎn)吧。但是胚想,如果是要對外開放的琐凭,需要外部使用人員賦值的,建議還是使用copy浊服,因?yàn)槟悴恢浪麄儠?huì)不會(huì)改變统屈,你也控制不了,所以為了安全起見臼闻,而且對外開放提供給別人使用的肯定不會(huì)特別多,那么有限的幾個(gè)copy消耗點(diǎn)資源比起安全來囤采,又有什么大不了的呢述呐?
代碼驗(yàn)證環(huán)節(jié):
/// 先定義兩個(gè)使用不同修飾符的屬性
@property (nonatomic, readwrite, strong) NSString *string_strong;
@property (nonatomic, readwrite, copy) NSString *string_copy;
- (void)influenceForNSStringWithStrong {
NSLog(@"開始測試strong對NSString的影響");
NSLog(@"賦值前:string_strong = %@", self.string_strong); /* string_strong = (null) */
{
NSMutableString *temp = [NSMutableString stringWithString:@"hello world"];
self.string_strong = temp;
NSLog(@"賦值后:string_strong = %@", self.string_strong); /* string_strong = hello world */
[temp appendString:@" changed"];
NSLog(@"原始值修改后:string_strong = %@", self.string_strong); /* hello world changed */
}
NSLog(@"超出原始值的作用于后:string_strong = %@\n\n", self.string_strong); /* hello world changed */
}
- (void)influenceForNSStringWithCopy {
NSLog(@"開始測試copy對NSString的影響");
NSLog(@"賦值前:string_copy = %@", self.string_copy); /* string_copy = (null) */
{
NSMutableString *temp = [NSMutableString stringWithString:@"hello world"];
self.string_copy = temp;
NSLog(@"賦值后:string_copy = %@", self.string_copy); /* string_copy = hello world */
[temp appendString:@" changed"];
NSLog(@"原始值修改后:string_copy = %@", self.string_copy); /* string_copy = hello world */
}
NSLog(@"超出原始值的作用于后:string_copy = %@\n\n", self.string_copy); /* string_copy = hello world */
}
/// 控制臺(tái)輸出
2018-10-10 18:15:24.559236+0800 MemoryManagerDemo[36553:5936151] 開始測試strong對NSString的影響
2018-10-10 18:15:24.559368+0800 MemoryManagerDemo[36553:5936151] 賦值前:string_strong = (null)
2018-10-10 18:15:24.559460+0800 MemoryManagerDemo[36553:5936151] 賦值后:string_strong = hello world
2018-10-10 18:15:24.559551+0800 MemoryManagerDemo[36553:5936151] 原始值修改后:string_strong = hello world changed
2018-10-10 18:15:24.559650+0800 MemoryManagerDemo[36553:5936151] 超出原始值的作用于后:string_strong = hello world changed
2018-10-10 18:15:24.559742+0800 MemoryManagerDemo[36553:5936151] 開始測試copy對NSString的影響
2018-10-10 18:15:24.559860+0800 MemoryManagerDemo[36553:5936151] 賦值前:string_copy = (null)
2018-10-10 18:15:24.559957+0800 MemoryManagerDemo[36553:5936151] 賦值后:string_copy = hello world
2018-10-10 18:15:24.560050+0800 MemoryManagerDemo[36553:5936151] 原始值修改后:string_copy = hello world
2018-10-10 18:15:24.560151+0800 MemoryManagerDemo[36553:5936151] 超出原始值的作用于后:string_copy = hello world
3. 為什么說weak修飾的對象會(huì)自行在內(nèi)存中銷毀?
這就沒啥說的了蕉毯,直接代碼驗(yàn)證:
@property (nonatomic, readwrite, weak) NSString *string_weak;
- (void)influenceForNSStringWithWeak {
NSLog(@"開始測試weak對NSString的影響");
NSLog(@"賦值前:string_weak = %@", self.string_weak); /* string_weak = (null) */
{
NSMutableString *temp = [NSMutableString stringWithString:@"hello world"];
self.string_weak = temp;
NSLog(@"賦值后:string_weak = %@", self.string_weak); /* string_weak = hello world */
[temp appendString:@" changed"];
NSLog(@"原始值修改后:string_weak = %@", self.string_weak); /* string_weak = hello world changed */
}
/* string_weak = (null) 乓搬,此處已經(jīng)超出了temp的作用域了,而temp又沒有其他的引用代虾,就會(huì)被釋放进肯,string_weak自然就為null了*/
NSLog(@"超出原始值的作用于后:string_weak = %@\n\n", self.string_weak);
}
/// 控制臺(tái)輸出
2018-10-10 18:22:54.118462+0800 MemoryManagerDemo[36613:5941777] 開始測試weak對NSString的影響
2018-10-10 18:22:54.118845+0800 MemoryManagerDemo[36613:5941777] 賦值前:string_weak = (null)
2018-10-10 18:22:54.119251+0800 MemoryManagerDemo[36613:5941777] 賦值后:string_weak = hello world
2018-10-10 18:22:54.119474+0800 MemoryManagerDemo[36613:5941777] 原始值修改后:string_weak = hello world changed
2018-10-10 18:22:54.119673+0800 MemoryManagerDemo[36613:5941777] 超出原始值的作用于后:string_weak = (null)
4. 為什么不能用copy修飾NSMutableString這種可變對象?
@property (nonatomic, readwrite, strong) NSMutableString *mutableString_strong;
@property (nonatomic, readwrite, copy) NSMutableString *mutableString_copy;
- (void)influenceForNSMutableStringWithCopy {
NSString *temp = @"hello world";
self.mutableString_strong = [NSMutableString stringWithString:temp];
self.mutableString_copy = [NSMutableString stringWithString:temp];
[self.mutableString_strong appendString:@" changed"];
[self.mutableString_copy appendString:@" changed"];
}
/// 控制臺(tái)輸出
2018-10-10 20:35:49.432473+0800 MemoryManagerDemo[36990:5966214] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'
*** First throw call stack:
(
0 CoreFoundation 0x000000011124329b __exceptionPreprocess + 331
1 libobjc.A.dylib 0x00000001107df735 objc_exception_throw + 48
2 CoreFoundation 0x00000001112430f5 +[NSException raise:format:] + 197
3 CoreFoundation 0x0000000111189dc9 mutateError + 121
4 MemoryManagerDemo 0x000000010febf6f5 -[ViewController influenceForNSMutableStringWithCopy] + 309
5 MemoryManagerDemo 0x000000010febf3fb -[ViewController influenceForNSMutableString:] + 59
6 UIKitCore 0x0000000114ee47c3 -[UIApplication sendAction:to:from:forEvent:] + 83
7 UIKitCore 0x000000011501ce85 -[UIControl sendAction:to:forEvent:] + 67
8 UIKitCore 0x000000011501d1a2 -[UIControl _sendActionsForEvents:withEvent:] + 450
9 UIKitCore 0x000000011501c0e6 -[UIControl touchesEnded:withEvent:] + 583
10 UIKitCore 0x00000001156f7334 -[UIWindow _sendTouchesForEvent:] + 2729
11 UIKitCore 0x00000001156f8a30 -[UIWindow sendEvent:] + 4080
12 UIKitCore 0x0000000114efee10 -[UIApplication sendEvent:] + 352
13 UIKitCore 0x0000000114e370d0 __dispatchPreprocessedEventFromEventQueue + 3024
14 UIKitCore 0x0000000114e39cf2 __handleEventQueueInternal + 5948
15 CoreFoundation 0x00000001111a6b31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
16 CoreFoundation 0x00000001111a6464 __CFRunLoopDoSources0 + 436
17 CoreFoundation 0x00000001111a0a4f __CFRunLoopRun + 1263
18 CoreFoundation 0x00000001111a0221 CFRunLoopRunSpecific + 625
19 GraphicsServices 0x00000001198f41dd GSEventRunModal + 62
20 UIKitCore 0x0000000114ee3115 UIApplicationMain + 140
21 MemoryManagerDemo 0x000000010febfce0 main + 112
22 libdyld.dylib 0x0000000112ba9551 start + 1
23 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
上面的代碼執(zhí)行后棉磨,會(huì)導(dǎo)致崩潰江掩,可以看到上面控制臺(tái)輸出的崩潰記錄,奔潰的原因是Attempt to mutate immutable object with appendString:
乘瓤,這句話的意思是嘗試用附件字符串來改變不可變對象:
环形,我們創(chuàng)建的對象明明是可變的,為什么在改變的時(shí)候卻因?yàn)樵搶ο蟛豢筛淖兌罎⒛匮每科鋵?shí)這就是copy的貢獻(xiàn)了抬吟。接下來我們輸出他們賦值之后的類型你就會(huì)明白了,看下圖统抬。
看到了嗎火本?經(jīng)過賦值之后strong修飾的對象依然是NSMutableString危队,然而copy修飾的對象卻變成了NSString,這是為什么呢钙畔?
其實(shí)茫陆,這是因?yàn)樗械倪@種可變類型都是繼承對應(yīng)的不可變類型,然而刃鳄,卻沒有重寫不可變類型的copy方法盅弛,當(dāng)使用copy修飾的時(shí)候,調(diào)用不可變類型的copy方法叔锐,得到的其實(shí)是一個(gè)不可變的對象挪鹏,此時(shí),我們?nèi)バ薷乃淅樱匀痪蜁?huì)報(bào)錯(cuò)了讨盒。
結(jié)尾:
最近在看唐巧大神和故胤道長一起寫的《iOS面試之道》和《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》這兩本書,突然心血來潮步责,以前沒仔細(xì)研究過返顺,這次就研究了一下,把不定期更新的博客更新一下蔓肯,有用你就給個(gè)喜歡遂鹊,沒用就當(dāng)看個(gè)熱鬧,想認(rèn)識(shí)交流技術(shù)的就關(guān)注一下蔗包,關(guān)注我的我也會(huì)關(guān)注你哦秉扑。