面試驅(qū)動技術(shù) - Block看我就夠了【干貨】

面試驅(qū)動技術(shù)合集(初中級iOS開發(fā)),關(guān)注倉庫,及時獲取更新 Interview-series

image

Block 在 iOS 算比較常見常用且??嫉牧?,現(xiàn)在面試中,要么沒面試題,有面試題的,基本都會考到 block 的點。本文特別干!(但是初中級iOSer應該能有所收獲~)

先來個面試題熱熱身,題目: 手撕代碼 - 用Block實現(xiàn)兩個數(shù)的求和

(這題如果會的,block基礎(chǔ)知識可以跳過了,直接到 Block原理探究)

簡單介紹block入門級用法

Block結(jié)構(gòu)比較復雜,一般用 typedef 定義,直接調(diào)用的感覺比較簡單、清晰易懂

//typedef block的時候有提示
typedef void(^MNBlock)(int);

@interface ViewController ()

@property (nonatomic, copy) MNBlock block;

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //直接用self.block調(diào)用
    self.block = ^(int a) {
        //dosomething...
    };
}
  • 參數(shù)解釋:

typedef <#returnType#>(^<#name#>)(<#arguments#>);

image

題目: 手撕代碼 - 用Block實現(xiàn)兩個數(shù)的求和

日常開發(fā)中,block聲明一般寫的比較多,實現(xiàn)一般是靠Xcode自動補全提示出現(xiàn)的,手撕代碼的情況下,等號右側(cè)的block實現(xiàn)要怎么寫?

聲明:

typedef int(^MNBlock)(int a, int b);

@interface ViewController ()

@property (nonatomic, copy) MNBlock sum;

Vip補全功能:

image

紙上按Enter沒用啊兄弟!看來還是需要了解一下Block右邊的東西~

先在 Xcode上按下 Enter,了解下再撕


image
^int(int a, int b) {
    //Control reaches end of non-void block    
    因為返回值是int類型,所以這里需要返回
}
image
int(^Sum)(int, int) = ^(int a, int b){
    return a + b;
};
int result = Sum(5, 10);
image

Block的坑出現(xiàn)!新手可能會寫錯的地方

1.聲明出錯 - void ^(testBlock)

image

修正版:

void (^testBlock)() = ^{
    
};

block的聲明,^ 和 blockName 都是在小括號里面??!

2.block各種實現(xiàn)的參數(shù)問題

聲明typedef int(^MNBlock)(int, int);

image
    self.sum = ^int(int a, int b) {
        return a + b;
    };

這里要注意,block聲明里面只有參數(shù)類型,沒有實際參數(shù)的話,Xcode提示也只有參數(shù),這里涉及到形參和實參的問題

聲明是形參,可以不寫參數(shù),但是使用的時候,必須有實際參數(shù),才可以進行使用,所以這里需要實參,可以在 ^int(int , int) 中手動添加實參^int(int a, int b),就可以讓a 和 b 參與運算

小tips:實際開發(fā)中,建議聲明的時候,如果需要帶參數(shù),最好形參也聲明下,這樣使用Xcode提示的時候,會把參數(shù)帶進去,方便得多~(踩過坑的自然懂!)

  1. 省略void導致看不懂block結(jié)構(gòu)的 (正常是兩個void導致局面混亂)
//聲明
typedef void(^MNBlock)(void);

//實現(xiàn)
self.sum = ^{
    //dosomething...
};

這種情況下,能知道怎么省略的,聲明里兩個void,能知道怎么對應的嗎?

這個其實比較簡單,block不管聲明 or 實現(xiàn),最后一個小括號,里面都是參數(shù),而參數(shù)是可以省略的!

而為了把聲明的兩個void區(qū)分開,返回值 or 參數(shù)區(qū)分開,其實就ok了

參數(shù)非void的例子

//聲明非void的參數(shù)
typedef void(^MNBlock)(int a);

//實現(xiàn)就必須帶參數(shù),不可省略!
self.sum = ^(int a) {
    
}

參數(shù)void的例子 ==> 參數(shù)可以省略

typedef int(^MNBlock)(void);

self.sum = ^{
    //聲明的返回值類型是int,所以一定要return;
    return 5;
};

其實-返回值是void的,也可以不省略

typedef void(^MNBlock)(void);

//實現(xiàn)的返回值不省略
self.sum = ^void () {
    
};

參數(shù)是void的省略:

typedef int(^MNBlock)();

//實現(xiàn)里面,沒有參數(shù),就可以不寫()
self.sum = ^int{
    return 5;
}

注意??! 聲明里面的返回值void是不可以省略的!!

image
  1. 小箭頭^混亂的問題,到底放小括號內(nèi)還是小括號外

聲明是 int(^MNBlock)(int a , int b)

實現(xiàn)是 ^int(int a, int b)

注意,這里箭頭之后的,不管是多寫() 還是少寫,都會出錯

image
image

所以這里還不能死記,比如不管聲明還是實現(xiàn),死記 (^ xxx) 是沒問題的 or 死記 ^…… xxx 不加括號是沒問題的,在這里都行不通,只能靠腦記了

這時候,就需要用到巧記了!

^ 和小括號組合的,一共有三種情況

  • 一種是聲明的,void(^MNBlock)
  • 一種是實現(xiàn)的,^int(int a,)
  • 還一種 ^(int a)

兄弟,看到這你還不亂嗎??!

image

怎么記看這里,

  • 手寫分為兩個部分,block等號左邊 or 等號右邊的,左邊為聲明,右邊為實現(xiàn)區(qū)分開
  • 聲明記住:^后面跟blockName,他們需要包起來! (blockName),只有聲明會用到blockName,先記住一點,如果有blockName,要和一起,用小括號包起來
  • 實現(xiàn)又分為兩種:
    • ^int:^后面跟的是返回值類型
      • ^ 直接跟類型,不用加"( )" ==> ^int
    • ^(int a):^后面直接跟參數(shù) (返回值是void)
      • 參數(shù)都是要用"( )"包起來的,如果^后面跟參數(shù),就得用"( )" ==> ^(int a),
      • 實現(xiàn)里,肯定有實際參數(shù),這時候,參數(shù)類型和實參,就得用( )包起來

^與小括號糾纏的總結(jié)

  • ^ 后面僅跟類型,不需要小括號,==> ^int
  • ^ 后面跟參數(shù),參數(shù)需要小括號 ==> ^(int a)
  • ^ 后面跟block名稱,^和blockName需要小括號 ==> void (^MNBlock)


Block原理探究

void (^MNBlock)(void) = ^(void){
    NSLog(@"this is a Block~ rua~");
};
MNBlock();

使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 轉(zhuǎn)成 C++ 代碼, 查看底層結(jié)構(gòu)

//對應上面的 MNBlock聲明
void (*MNBlock)(void) = (&__main_block_impl_0(__main_block_func_0,
                                                      &__main_block_desc_0_DATA));
        
//對應上面的 MNblock() 調(diào)用
MNBlock->FuncPtr(MNBlock);
//block聲明調(diào)用的 - __main_block_impl_0
struct __main_block_impl_0 {
  //結(jié)構(gòu)體內(nèi)的參數(shù)
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  //c++中的構(gòu)造函數(shù),類似于 OC 的 init 方法,返回一個結(jié)構(gòu)體對象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

這里的block封裝的函數(shù)調(diào)用解釋MNBlock->FuncPtr(MNBlock);

MNBlock 其實內(nèi)部結(jié)構(gòu)是 __main_block_impl_0,

struct __main_block_impl_0 {

  //函數(shù)調(diào)用地址在這個結(jié)構(gòu)體內(nèi)
  struct __block_impl impl;

  struct __main_block_desc_0* Desc;
  }
  
  struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  //函數(shù)調(diào)用地址在這里
  void *FuncPtr;
};

內(nèi)部只有兩個參數(shù),一個impl,一個Desc,而函數(shù)的調(diào)用地址 - FuncPtr是再impl中的,為什么這里能直接這樣寫呢?

因為,__main_block_impl_0 結(jié)構(gòu)的地址和他的第一個成員一樣,第一個成員的地址是__block_impl,所以__main_block_impl_0 和 __block_impl 的地址其實是同一個,通過格式強制轉(zhuǎn)換,將 main_block_impl_0 轉(zhuǎn)成 block_impl 就可以直接拿到他內(nèi)部的 FuncPtr 函數(shù)地址,然后進行調(diào)用!

image-20190307213258239
  • 可見- block本質(zhì)上是OC對象,內(nèi)部有一個isa指針

  • block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用的oc對象

Block面試題拋磚引玉~

開胃菜先來一下,以下結(jié)果輸出什么

int a = 10;
void (^MNBlock)(void) = ^{
    NSLog(@"a = %d",a);
};
a += 20;

MNBlock();

調(diào)用 MNBlock(); 之前,a 已經(jīng) + 20了,輸出30? 太天真了兄弟,這里涉及到capture的概念,即變量捕獲

Block捕獲變量(capture)

捕獲:Block內(nèi)部會新增一個成員,來存儲傳進來的變量

image-20190307214010613

block 內(nèi)部直接捕獲了傳進去的這個變量a(10)

image

創(chuàng)建block的時候,已經(jīng)將變量a=10 捕獲到 block內(nèi)部,之后再怎么修改,不會影響block 內(nèi)部的 a

auto 和 static的區(qū)別:以下會輸出什么~

static int b = 10;
void (^MNBlock)(void) = ^{
    NSLog(@"a = %d, b = %d",a,b);
};
a = 20;
b = 20;

MNBlock();

輸出

2019-03-07 21:49:49 Block-Demo a = 10, b = 20

why?

查看原因:

auto int a = 10;
static int b = 10;
void (*MNBlock)(void) = (&__main_block_impl_0(__main_block_func_0,
                                              &__main_block_desc_0_DATA,
                                              a,
                                              &b));

發(fā)現(xiàn):兩種變量,都有捕獲到block內(nèi)部。

a 是auto變量,走的是值傳遞,

b 是 static 變量,走的是地址傳遞,所以會影響(指針指向同一塊內(nèi)存,修改的等于是同個對象)

總結(jié)

  • 只有局部變量才需要捕獲,
  • 全局變量不需要捕獲,因為在哪都可以訪問
  • 需不需要捕獲,其實主要是看作用域問題
  • auto局部變量 ==>值傳遞->因為會銷毀
  • static局部變量==>不會銷毀==>所以地址傳遞

看圖就行~

image-20190307220857223

進階考題 - self 會被捕獲到 block 內(nèi)部嗎

void (^MNBlock)(void) = ^{
    NSLog(@"p = %p",self);
};

模擬看官作答:不會,因為整個類里,都能調(diào)用self,應該是全局的,全局變量不會捕獲到block中

哈哈哈哈!中計了!其實 self 是參數(shù)(局部變量)

struct __MNDemo__test_block_impl_0 {
  struct __block_impl impl;
  struct __MNDemo__test_block_desc_0* Desc;
  MNDemo *self; ==> 捕捉到了兄弟
  }

解釋原因:

  • 每個OC函數(shù),其實默認有兩個參數(shù),一個self,一個_cmd,只是他們倆兄弟默認是隱藏的
  • 而由于他們是參數(shù),所以是局部變量,局部變量就要被 block 捕獲
  • - (void)test(self, SEL _cmd){XXX} 默認的OC方法里面其實有這兩個隱藏的參數(shù)!所以上題的答案,self是會被block捕獲的!(能聽懂掌聲!)

進進階考題 - 成員變量_name 會被捕獲到 block 內(nèi)部嗎

void (^MNBlock)(void) = ^{
    NSLog(@"==%@",_name);
};

模擬看官作答:呵呵,老子都中了這么多次技了,這題學會了??! 因為_name是成員變量,全局的,也沒有self,所以不需要捕獲整個類就都可以隨便訪問它!

哎,兄弟,還是太年輕了!!

void (^MNBlock)(void) = ^{
    NSLog(@"==%@",self->_name);
};

看圖說話,不多bb, (能聽懂掌聲?。?/em>

Block的類型

  • __NSGlobalBlock__

  • __NSStackBlock__

  • __NSMallocBlock__

MRC環(huán)境下

void (^global)() = ^{
    NSLog(@"globalValue = %d",globalValue);
};

void (^autoBlock)() = ^{
    NSLog(@"this is a Block~ rua~ = %d",a);
};

void (^copyAuto)() = [autoBlock copy];

--------------------------------------------
print class
2019-03-08 17:40:43 Block-Demo

 global class = __NSGlobalBlock__ 
 autoBlock class = __NSStackBlock__ 
 copyAuto = __NSMallocBlock__

總結(jié):

image

內(nèi)存分配示意圖:

image

棧上的內(nèi)存系統(tǒng)會自動回收

  • ??臻g的block 不會對 對象進行強引用
  • 堆空間的block 可能會對對象產(chǎn)生強引用:
    • 如果是weak指針,不會強引用
    • 如果是strong指針,會強引用

堆上的內(nèi)存是由程序員控制,所以一般將block 拷貝到堆上,讓程序員控制他與內(nèi)部變量的生命周期

題目:以下輸出的順序是什么(ARC環(huán)境下)

@implementation MNPerson

- (void)dealloc{
    NSLog(@"MNPerson - dealloc");
}

@end

--------------------------------------

MNPerson *person = [[MNPerson alloc]init];

__weak MNPerson *weakPerson = person;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    NSLog(@"1-----%@",person);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"2------%@",weakPerson);
    });
    
});

NSLog(@"touchesBegan");

輸出結(jié)果

2019-03-08 22:38:59.038452+0800 touchesBegan
2019-03-08 22:39:00.056746+0800 1-----<MNPerson: 0x604000207840>
2019-03-08 22:39:00.057891+0800 MNPerson - dealloc
2019-03-08 22:39:02.058011+0800 2-----(null)

解釋:

  1. gcd的block會自動對auto變量進行copy操作

  2. block內(nèi)部對 auto 變量的強弱引用,取決于指針類型

  3. 1 中的auto變量是 person,沒聲明默認對象是 strong 類型,所以 gcd1 會對 person進行 1s的強引用

  4. gcd2 中的變量是 weakPerson,看到是__wesk指針,所以block內(nèi)部不會對其產(chǎn)生強引用

  5. 隨后,gcd1 對 person進行1s的強引用之后,gcd1 的block銷毀,person對象銷毀,打印MNPerson dealloc

  6. 最終,2s過后打印 2——weakPerson,因為person對象在gcd1 block結(jié)束之后,釋放掉了,所以此時person是空,因為是weak指針,對象是null不會crash,最終打印null

對象類型的auto變量

  • 當 block 內(nèi)部訪問了對象類型的auto變量時
    • 如果block在展示,不會對 auto 變量產(chǎn)生強引用
    • 如果 block 被 拷貝到堆上
      • 會調(diào)用 block 內(nèi)部的 copy 函數(shù)
      • copy 函數(shù)內(nèi)部會調(diào)用 _Block_object_assign 函數(shù)
      • _Block_object_assign 函數(shù)會根據(jù)auto變量的修飾符 ( strong、 weak、unsafe_unretained ) 做出對應的操作,看對內(nèi)部auto變量進行強引用還是弱引用(類似于 retain)
    • 如果 block 從 堆上移除
      • 會調(diào)用 block 內(nèi)部的 dispose 函數(shù)
      • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose 函數(shù)
      • _Block_object_dispose 類似于 release,會對auto變量進行自動釋放(當引用計數(shù)器=0的時候 )
image

block中的copy

  • 在ARC環(huán)境下,編譯器會根據(jù)情況,自動將棧上的block拷貝到堆上,比如以下幾種情況
    • block 作為函數(shù)返回值的時候
    • 將block復制給__strong指針的時候
    • block作為Cocoa API中方法名含有usingBlock的方法參數(shù)事
      • 比如:[array enumerateObjectsUsingBlock:XXX]

__block 修飾符的使用

題目:以下代碼的是否編譯通過,可以的話輸出結(jié)果是什么

int a = 10;
void (^block)() = ^{
    a = 20;
    NSLog(@"a = %d",a);
};

結(jié)果如下:

image-20190308225448279

思考:無法編譯,為啥呢?編譯的時候,block應該是會把auto變量捕獲進去的,那block結(jié)構(gòu)中應該有a才對啊

//main函數(shù)
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;
        void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

    }
    return 0;
}

//block執(zhí)行地址
  static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_kh_0rp73c0s2mvfp5gjf25j5y6h0000gn_T_main_1a12fa_mi_0,a);}

block執(zhí)行的時候,內(nèi)部是 __main_block_func_0 函數(shù),而a的聲明,是在main函數(shù),兩個函數(shù)相互獨立,對于他們來說,a都是一個局部變量,而且兩個函數(shù)中都對a初始化,兩個函數(shù)的中a不是同一個,那怎么可以在 執(zhí)行函數(shù)中,修改main函數(shù)中的局部變量呢,所以編譯報錯!

如何改?

  • 方案一:使用static
static int a = 10;
void (^block)() = ^{
    a = 20;
    NSLog(@"a = %d",a);
};

因為static修飾的auto變量,最終在block中進行的不是值傳遞,而是地址傳遞,措意執(zhí)行函數(shù)中的a 和 main 函數(shù)中的a,是同一個地址 ==> 等于同一個a,所以可以修改,輸出20

但是使用static,就會變成靜態(tài)變量,永遠在內(nèi)存中

  • 方案二: 使用__blcok
__block auto int a = 10;
void (^block)() = ^{
    a = 20;
    NSLog(@"a = %d",a);
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref ==> auto的話,是int a,__block,變成對象了
}
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;==> 指向自己的結(jié)構(gòu)體
 int __flags;
 int __size;
 int a; ==> 10在這里
};

a = 20;最終轉(zhuǎn)成 (a->__forwarding->a) = 20;

解釋下:__forwarding 是指向結(jié)構(gòu)體本身的指針,等價于a本身,其實就是通過a的結(jié)構(gòu)體指針,拿到里面的成員a,再對他賦值

指針傳遞,所以可以修改 auto 變量,通過block,間接引用 auto 變量

image-20190309205908169

__block的內(nèi)存管理

  • 當 block 在棧上的時候,不會對內(nèi)部的__block 變量產(chǎn)生強引用
  • 當 block 從棧上被 copy 到堆上的時候
    • 會調(diào)用block內(nèi)部的copy函數(shù)
    • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign 函數(shù)
    • _Block_object_assign 函數(shù)會對 __block 變量進行一次 retain操作,產(chǎn)生強引用

抄圖分析 :

image-20190309210956453
image-20190309211009229
  • 當block從堆中移除時
    • 會調(diào)用 block 內(nèi)部的 dispose 函數(shù)
    • dispose內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    • _Block_object_dispose函數(shù)會對__block變量進行一次release操作,如果retainCount為0,自動釋放該__block變量
image-20190309211246277
image-20190309211257030

總結(jié):

  • block在棧上的時候,不會對內(nèi)部的變量產(chǎn)生強引用
  • 當block從棧上 copy 到堆上的時候,內(nèi)部都會調(diào)用 __Block_object_assign
    • 如果是__block修飾的變量,會__block修飾的對象產(chǎn)生強引用
    • 如果是普通auto變量,看修飾的指針類型是strong 還是 weak(unsafe_unretained)
      • strong修飾的,block就會對內(nèi)部的auto變量產(chǎn)生強引用
      • weak修飾的,block就不會對內(nèi)部的auto變量產(chǎn)生強引用
    • 特別注意!上述條件僅在ARC環(huán)境下生效,如果是MRC環(huán)境下,block不會對內(nèi)部auto變量產(chǎn)生強引用!(MRC下不會進行retain操作)
  • 當block從堆上移除的時候,內(nèi)部會調(diào)用__Block_object_dispose函數(shù),相當于對block內(nèi)部所持有的對象進行移除release操作,如果retainCount為0,自動釋放該__block變量

__block中的 _ forwarding 指針

內(nèi)存拷貝的時候,如果block從棧被copy到堆上,肯定也希望內(nèi)部的變量一起存儲到堆上(讓變量的生命周期可控,才不會被回收)

加入變量a在棧上,在棧上的指針,指向堆上的 block,堆上的block的 forwarding指向他自己,就可以保證,修改&獲取的變量,都是堆上的變量

image-20190309213120820

最終,__block指向的變量,是指向堆上的

__block 修飾的類型

@implementation MNObject

- (void)dealloc{
    NSLog(@"MNObject - dealloc");
}

@end


--------------------------------------------

typedef void (^MNBlock)();

MNBlock block;
{
    MNObject *obj = [[MNObject alloc]init];
    __block __weak MNObject *weakObj = obj;
    
    block = ^{
        NSLog(@"----------%p",weakObj);
    };
}
block();

問,上述代碼的輸出順序是?

2019-03-09 21:57:56.673296+0800 Block-Demo[72692:8183596] MNObject - dealloc
2019-03-09 21:57:56.673520+0800 Block-Demo[72692:8183596] ----------0x0

解釋:ARC下

image-20190309220353476

上述代碼,block 持有的是 weakObj,weak指針,所以block內(nèi)部的__block結(jié)構(gòu)體,對他內(nèi)部持有的person不強引用!所以出了 小括號后,person沒有被強引用,生命gg,先dealloc,輸出dealloc,之后進行block調(diào)用,打印 ---------

特別注意,上述邏輯進在ARC下,如果在MRC下,中間結(jié)構(gòu)體對象,不會對person 進行retain操作! 即便 person 是強指針修飾,也不會對內(nèi)部的person對象進行強引用!

MRC環(huán)境下

MNBlock block;
{
    MNObject *obj = [[MNObject alloc]init];
    block = [^{
        NSLog(@"----------%p",obj);
    }copy];
    
    [obj release];
}
block();

[block release];

--------------------
輸出:
2019-03-09 21:59:56.673296+0800 Block-Demo[72692:8183596] MNObject - dealloc
2019-03-09 21:59:56.673520+0800 Block-Demo[72692:8183596] ----------0x0

上述代碼,obj 是 strong 修飾,但是并沒有被 block 強引用!可見MRC環(huán)境下,修飾的對象,生成的中間block對象不會對 auto變量產(chǎn)生強引用。

Block的循環(huán)應用問題

傳送門: 實際開發(fā)中-Block導致循環(huán)引用的問題(ARC環(huán)境下)

考題:MRC 下,block的循環(huán)引用如何解決呢?

  • 方案1:unsafe_unretained

MRC下,沒有__weak,所以只能用_unsafe_unretained指針,原理和 weak 一樣(ARC環(huán)境下不推薦使用,可能導致野指針,推薦使用weak)

__unsafe_unretained MNObject *weakSelf = self;
self.block = [^{
    NSLog(@"----------%p",weakSelf);
}copy];
  • 方案2: __block
__block self;
self.block = [^{
    NSLog(@"----------%p",self);
}copy];

why? 上面關(guān)于 __block的總結(jié)

特別注意!上述條件僅在ARC環(huán)境下生效,如果是MRC環(huán)境下,block不會對內(nèi)部auto變量產(chǎn)生強引用!(MRC下不會進行retain操作)

image-20190309224535679
  • 方案3: 手動在block函數(shù)內(nèi)將對象制空,并且必須手動保證block調(diào)用
MNObject *obj = [[MNObject alloc]init];
__unsafe_unretained MNObject *weakObj = obj;
obj.block = [^{
    NSLog(@"----------%p",obj);
    obj = nil;
}copy];

obj.block();
image-20190309225056495

但是這個一定要注意,block必須調(diào)用,因為對象指針的清空操作,是寫在block函數(shù)中的,如果沒調(diào)用block,循環(huán)引用問題還是會存在,所以不推薦使用。

實際開發(fā)中,循環(huán)引用的檢測工具推薦,facebook開源的 FBRetainCycleDetector,用過的都說好~


話外篇補充 - Block 和 delegate使用場景

個人愚見

  • 直接異步返回的,可以用block,比如網(wǎng)絡(luò)請求,無需其他人工動作觸發(fā)的

  • 如果是需要類似點擊才能觸發(fā)的,比如 Button的點擊事件,可以用 delegate



老實說,block其實非常難,能考得特別深,本文也只是簡單探究&總結(jié)下中級iOS常見的block考題,以及對Block底層的初步探究,如果是像我所在的三線城市,去面試那種非一線公司的話,如果能掌握本文,可能block相關(guān)的題目能答個八九不離十吧!(可能題目會變換組合,但是萬變不離其宗)


block的文章其實很多,但是如果要真的深入理解,還是得動手,這里推薦初中級iOSer可以跟著本文的思路,一步一步跟著探究試試,本文只是起個拋磚引玉的作用



友情演出:小馬哥MJ

參考資料

Objective-C 高級編程 iOS與OS X多線程和內(nèi)存管理

實際開發(fā)中-Block導致循環(huán)引用的問題(ARC環(huán)境下)

招聘一個靠譜的 iOS

ChenYilong/iOSInterviewQuestions

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,120評論 1 32
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,863評論 0 38
  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 1,996評論 0 7
  • 親愛的,茫茫人海,我們有緣相見,相知,相戀,這本就是一種天大的緣分。我知道你愛我,而我同樣也愛你,為了讓我們更好地...
    陶小小閱讀 510評論 0 0
  • (一) 江天晚,剡水寬,一橫長堤湖光醉,排舞幾曲香汗碎,煩惱拋卻云霄外。 (二) 月如勾,橋似眉,一瞬晚霞半城璀,...
    龍山曉蟲閱讀 180評論 0 1