更新:
- 2018.10.12 修改了copy與strong修飾NSString、NSArray這些類(lèi)型的解釋和用法
開(kāi)發(fā)環(huán)境:
Mac系統(tǒng)版本:macOS Mojave 10.14(18A391)
Xcode:Version 10.0 (10A255)
結(jié)論:
就怕你想看結(jié)論又不想看長(zhǎng)篇大論,就把結(jié)論寫(xiě)到上面,免得你滑屏幕累得慌
- strong 表示指向并擁有該對(duì)象。其修飾的對(duì)象引用計(jì)數(shù)會(huì)增加 1。該對(duì)象只要引用計(jì)數(shù)不為 0 則不會(huì)被銷(xiāo)毀。當(dāng)然強(qiáng)行將其設(shè)為 nil 可以銷(xiāo)毀它。
- weak 表示指向但不擁有該對(duì)象。其修飾的對(duì)象引用計(jì)數(shù)不會(huì)增加。無(wú)需手動(dòng)設(shè)置,該對(duì)象會(huì)自行在內(nèi)存中銷(xiāo)毀。
- assign 主要用于修飾基本數(shù)據(jù)類(lèi)型,如 NSInteger 和 CGFloat ,這些數(shù)值主要存在于棧上。
- weak 一般用來(lái)修飾對(duì)象,assign 一般用來(lái)修飾基本數(shù)據(jù)類(lèi)型。
- copy 與 strong 類(lèi)似。不同之處是 strong 的復(fù)制是多個(gè)指針指向同一個(gè)地址,而 copy 的復(fù)制每次會(huì)在內(nèi)存中拷貝一份對(duì)象,指針指向不同地址。
- __weak、__strong與weak、strong類(lèi)似,區(qū)別是__weak、__strong修飾變量,而weak、strong修飾屬性,而且__weak、__strong基本上都是與block相關(guān);
- 如果對(duì)象類(lèi)型有對(duì)應(yīng)的可變類(lèi)型,例如NSString、NSArray、NSDictionary等,需要結(jié)合場(chǎng)景合理使用,而對(duì)應(yīng)的可變類(lèi)型用strong,使用copy會(huì)導(dǎo)致崩潰;
- delegate使用weak修飾;
- 各種視圖控件推薦使用weak,官方就是這樣做的;
探索:
1. 為什么assign不能用來(lái)修飾對(duì)象類(lèi)型?
我先引用故胤道長(zhǎng)在《iOS面試之道》這本書(shū)中的回答:
assign 修飾的對(duì)象被釋放后,指針的地址依然存在,造成野指針,在堆上容易造成崩潰。而棧上的內(nèi)存系統(tǒng)會(huì)自動(dòng)處理,不會(huì)造成野指針。
總之一句話,使用assign修飾對(duì)象容易造成崩潰。
代碼驗(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這類(lèi)有對(duì)應(yīng)可變類(lèi)型的對(duì)象類(lèi)型有什么區(qū)別?
在對(duì)對(duì)象賦值的時(shí)候,如果是使用copy修飾,那么會(huì)在內(nèi)存中將原對(duì)象的值拷貝一份,原對(duì)象與該對(duì)象指向的是不同的內(nèi)存區(qū)域,原對(duì)象在之后做任何修改都與該對(duì)象無(wú)關(guān);
而如果是使用strong修飾,那么該對(duì)象與原對(duì)象雖然是不同的指針對(duì)象,但指向的都是同一片內(nèi)存區(qū)域,如果原對(duì)象進(jìn)行了修改,那么即使這個(gè)對(duì)象是不可變的,它的值也會(huì)發(fā)生變化;
如果確定在賦值之后,原值不會(huì)修改,那么使用strong是比較好的選擇,畢竟,copy會(huì)消耗系統(tǒng)資源,能省一點(diǎn)是一點(diǎn)吧。但是,如果是要對(duì)外開(kāi)放的,需要外部使用人員賦值的,建議還是使用copy,因?yàn)槟悴恢浪麄儠?huì)不會(huì)改變,你也控制不了,所以為了安全起見(jiàn),而且對(duì)外開(kāi)放提供給別人使用的肯定不會(huì)特別多,那么有限的幾個(gè)copy消耗點(diǎn)資源比起安全來(lái),又有什么大不了的呢?
代碼驗(yàn)證環(huán)節(jié):
/// 先定義兩個(gè)使用不同修飾符的屬性
@property (nonatomic, readwrite, strong) NSString *string_strong;
@property (nonatomic, readwrite, copy) NSString *string_copy;
- (void)influenceForNSStringWithStrong {
NSLog(@"開(kāi)始測(cè)試strong對(duì)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(@"開(kāi)始測(cè)試copy對(duì)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] 開(kāi)始測(cè)試strong對(duì)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] 開(kāi)始測(cè)試copy對(duì)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. 為什么說(shuō)weak修飾的對(duì)象會(huì)自行在內(nèi)存中銷(xiāo)毀?
這就沒(méi)啥說(shuō)的了,直接代碼驗(yàn)證:
@property (nonatomic, readwrite, weak) NSString *string_weak;
- (void)influenceForNSStringWithWeak {
NSLog(@"開(kāi)始測(cè)試weak對(duì)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又沒(méi)有其他的引用,就會(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] 開(kāi)始測(cè)試weak對(duì)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這種可變對(duì)象?
@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:,這句話的意思是嘗試用附件字符串來(lái)改變不可變對(duì)象:,我們創(chuàng)建的對(duì)象明明是可變的,為什么在改變的時(shí)候卻因?yàn)樵搶?duì)象不可改變而崩潰呢?其實(shí)這就是copy的貢獻(xiàn)了。接下來(lái)我們輸出他們賦值之后的類(lèi)型你就會(huì)明白了,看下圖。

看到了嗎?經(jīng)過(guò)賦值之后strong修飾的對(duì)象依然是NSMutableString,然而copy修飾的對(duì)象卻變成了NSString,這是為什么呢?
其實(shí),這是因?yàn)樗械倪@種可變類(lèi)型都是繼承對(duì)應(yīng)的不可變類(lèi)型,然而,卻沒(méi)有重寫(xiě)不可變類(lèi)型的copy方法,當(dāng)使用copy修飾的時(shí)候,調(diào)用不可變類(lèi)型的copy方法,得到的其實(shí)是一個(gè)不可變的對(duì)象,此時(shí),我們?nèi)バ薷乃?,自然就?huì)報(bào)錯(cuò)了。
結(jié)尾:
最近在看唐巧大神和故胤道長(zhǎng)一起寫(xiě)的《iOS面試之道》和《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》這兩本書(shū),突然心血來(lái)潮,以前沒(méi)仔細(xì)研究過(guò),這次就研究了一下,把不定期更新的博客更新一下,有用你就給個(gè)喜歡,沒(méi)用就當(dāng)看個(gè)熱鬧,想認(rèn)識(shí)交流技術(shù)的就關(guān)注一下,關(guān)注我的我也會(huì)關(guān)注你哦。