探索weak、strong、copy、assign、__weak、__strong的用法

更新:

  • 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)型。
  • copystrong 類(lèi)似。不同之處是 strong 的復(fù)制是多個(gè)指針指向同一個(gè)地址,而 copy 的復(fù)制每次會(huì)在內(nèi)存中拷貝一份對(duì)象,指針指向不同地址。
  • __weak、__strongweak、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ì)明白了,看下圖。

驗(yàn)證.png

看到了嗎?經(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)注你哦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容