目錄
1. block的迴圈引用是如何造成的?
2. 使用UIAnimation的block回撥時,需不需要使用__weak避免迴圈引用?為什麼?
3. block屬性是否可以用strong修飾?
4. 什麼場景下才需要對變數使用__block?
5. 執行以下GCD多執行緒程式碼,控制檯將列印什麼?
1. block的迴圈引用是如何造成的?
------------Light.h------------
#import <Foundation/Foundation.h>
@interface Light : NSObject
@property (nonatomic, copy) NSString *color;
@property (nonatomic, copy) void (^block)(void);
@end
------------Light.m------------
#import "Light.h"
@implementation Light
@end
------------main.m------------
#import "Light.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Light *loveLight = [Light alloc] init];
loveLight.color = @"green";
loveLight.block = ^{
NSLog(@"%@",loveLight.color);
};
loveLight.block();
}
}
複製程式碼
我們在上面的程式碼中建立了一個Light(光)類,並宣告兩個屬性color(顏色)及block。然後我們例項化一個物件loveLight並對其屬性賦值,實現並呼叫block,造成迴圈引用。 然後我們通過clang程式碼,瞭解這段程式碼內部的部分實現:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
RMPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Light *__strong _loveLight, int flags=0) : loveLight(_loveLight) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Light *__strong loveLight = __cself->loveLight; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_0xn052bn6dgb9z7pfk8bbg740000gn_T_main_d61985_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)loveLight, sel_registerName("color")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->loveLight, (void*)src->loveLight, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
...後省略
複製程式碼
通過clang後的原始碼我們可以知道:
- 物件的建立本身就是強引用(預設strong修飾)。
- 物件對block屬性賦值,在ARC下,block作為返回值時或者賦值給一個strong/copy修飾的物件會自動呼叫copy,loveLight強引用block。
- 物件的block在其內部捕獲了物件本身,block在自動呼叫copy的時候,_Block_object_assign(clang原始碼最後一行)會根據捕獲變數的所有權修飾符,來對變數的引用計數進行操作。此處loveLight本身是strong修飾,則引用計數+1,block強引用loveLight物件。
- 所以雙方互相引用,造成了迴圈引用。
同時面試中面試官還可能會詢問你如何檢測到記憶體洩漏,我們可以通過Instruments中的Leaks進行檢測,也可以選擇facebook釋出的FBRetainCycleDetector記憶體洩漏檢測工具。
2. 使用UIView Animation的block回撥時,是否需要考慮迴圈引用的問題?為什麼?
首先UIView Animation使用時,不需要考慮迴圈引用的問題。
UIKit將動畫直接整合到UIView的類中,當內部的一些屬性發生改變時,UIView將為這些改變提供動畫支援,並以類方法的形式提供介面。
而block造成迴圈引用的主要原因是物件與block的相互持有,UIView Animation的block本身處於類方法中,在使用時並不屬於呼叫控制器。同時控制器也無法強引用一個類,所以不會造成迴圈引用的問題。
3. block屬性是否可以用strong修飾?
block屬性可以使用strong屬性修飾符修飾,但是不推薦,會有記憶體洩漏的隱患。
首先,ARC中block用copy屬性修飾符修飾是MRC時代延續的產物,提醒開發者可能存在的記憶體問題。同時copy的確是可以用strong來替代的。
我們都知道block在OC中有三種型別:
- _NSConcreateGlobalBlock 全域性的靜態block,不會訪問任何外部變數。
- _NSConcreateStackBlock 棧區的block,當函式返回時會被銷燬。
- _NSConcreateMallocBlock 堆區的block,當引用計數為0時被銷燬。
block在MRC下可以存在於全域性區、棧區和堆區,而在ARC下,block會自動從棧區拷貝到堆區(除了裸寫block實現塊),所以只存在於全域性區和堆區。 所以對於棧區block,MRC下處於棧區,想在作用域外呼叫就得copy到堆區;ARC則自動copy堆區。
那麼這個時候問題就來了,strong屬性修飾符並不能拷貝,就會有野指標錯區的可能,造成Crash。這種情況很少見,但是不代表不可能發生,所以最好還是使用copy屬性修飾符。
4. 什麼場景下才需要對變數使用__block?
賦值場景下使用__block,使用場景下不需要。 我們來對比下賦值場景和使用場景
//賦值場景
NSMutableArray *__block array = nil;
void(^Block)(void) = ^{
array = [NSMutableArray array];
};
Block();
//使用場景
NSMultableArray *array = [NSMultableArray array];
void(^Block)() = ^{
[array addObject:@"1"];
};
Block();
複製程式碼
5. 執行以下GCD多執行緒程式碼,控制檯將列印什麼?
dispatch_queue_t gQueue= dispatch_get_global_queue(0, 0);
NSLog(@"1");
dispatch_sync(gQueue, ^{
NSLog(@"2");
dispatch_sync(gQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
複製程式碼
答案:12345
首先列印1,全域性佇列本質是併發佇列也就是併發同步,同步任務不開起執行緒,在主執行緒執行列印2。 然後全域性佇列執行同步任務,依舊不開啟執行緒,在主執行緒執行列印3。 同步任務完成,依舊存在於全域性佇列同步執行,列印4. 同步任務完成,列印5。