Blocks官方文檔閱讀記錄 -- Blocks Programming Topics

block.png

前言

最近,我的前同事問了我一個問題,你知道(global、malloc、stack)block嗎,什么是(global、malloc、stack)block,相信你在面試中或多或少遇到過一些Blocks的題目,一些簡單,一些偏門,但是我認為脫離實際開發(fā)的面試題對于面試應聘者是完全無意義的,或許你知道Blocks的內存結構,你也知道Blocksisa在不同情況下指向的類,但這些僅適合拿來茶余飯后交談的話題(裝B)罷了,因為你在半年一年之后自己都不清楚它是什么,只是再翻出來能很快頓悟,所以我不建議面試官將這些放在臺面上討論。
那么這篇文章寫的將是實際開發(fā)所需要用到的,同時也是Apple官方給出的Blocks文檔

關于Block對象的內存結構我不打算在本文講解,你可以在Block實現(xiàn)的源碼中去了解,但是從源碼中可以論證一點 Blockisa指針指向的類包含了一下幾種:

_NSConcreteStackBlock 
_NSConcreteMallocBlock
_NSConcreteAutoBlock
_NSConcreteFinalizingBlock 
_NSConcreteGlobalBlock
_NSConcreteWeakBlockVariable

并不是很多博客上的文章及面試官口中所說的這三種:

_NSConcreteStackBlock 
_NSConcreteMallocBlock
_NSConcreteGlobalBlock

本文目錄

一.Blocks概念、概述
二.Blocks申明、創(chuàng)建、使用
三.Blocks的變量類型(循環(huán)引用關系)、__block儲存類型(本文重點)

一.Blocks概念、概述

閉包是一個函數(shù)(或指向函數(shù)的指針),再加上該函數(shù)執(zhí)行的外部的上下文變量(有時候也稱作自由變量)。Block 實際上就是 Objective-C 語言對于閉包的實現(xiàn)。通常來說,Block都是一些簡短代碼片段的封裝,適用作工作單元,通常用來做并發(fā)任務、遍歷、以及回調

Block代表通常是小的,獨立的代碼片段。因此,它們特別有用,作為可以并發(fā)執(zhí)行的工作單元或者集合中的項目封裝的工具,或者當另一個操作完成時作為回調。

Block是傳統(tǒng)回調函數(shù)的有用替代,主要有兩個原因:

  • 它們允許您在方法實現(xiàn)的上下文中執(zhí)行的調用點編寫代碼,因此,Block通常是框架方法的參數(shù)。
  • 它們允許訪問局部變量,而不是使用需要一個體現(xiàn)您執(zhí)行操作所需的所有上下文信息的數(shù)據(jù)結構的回調函數(shù),您只需直接訪問局部變量即可。

對于官方文檔上的這兩點有點繞口,理解為,可以在方法的實現(xiàn)中調用Block,同時可以訪問局部變量,將這些信息回調到外部結構中去。

二.Blocks申明、創(chuàng)建、使用
2.1聲明、創(chuàng)建
int multiplier = 7;
int (^ myBlock)(int) = ^(int num){
   return num * multiplier;
};

下例說明了該示例:


blocks.jpg

注意:
int (^ myBlock)(int) 表示申明了一個名為myBlock的Block指針來保存 ^(int num){return num * multiplier;}對象,如果你對這句話不了解,你可以去看看我的 iOS 程序(APP)運行中的內存分配 這遍文章。

此處你可理解為 UIView *view = [UIView new]; 中的 UIView *view為指向UIView類型的指針,而[UIView new]是生成的一個UIView對象。

此處將代碼分成兩部分:

  • int (^ myBlock)(int) Block的聲明(可比著UIView *view看)
  • ^(int num){return num * multiplier;} Block的實現(xiàn)(可比著[UIView new]看)
2.2 Block使用

顯而易見,有了上面的基礎,我們就能夠大膽的猜測Block的用法了。

  • 1.聲明全局的Block。
  • 2.作為局部變量使用。
  • 3.用作屬性或者成員變量。
  • 4.作為函數(shù)的參數(shù)傳遞。

1.聲明全局的Block:

#import "ViewController.h"

/** 全局初始化Block */
void(^kGlobalBlock)(id , id ) = ^(id mayActionType, id parameters){
    NSLog(@"%@,%@",mayActionType,parameters);
};

/** 全局未初始化Block */
void(^kGlobalBlockWaitingInitinalize)(id , id );

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self golbalBlock];
}
#pragma mark - - 全局類型的 Block
- (void)golbalBlock{
    kGlobalBlock(@1,@"Bob");
    kGlobalBlockWaitingInitinalize = ^(id parameterOne, id parameterTwo){
        NSLog(@"parameterOne:%@ \n parameterOne:%@",parameterOne,parameterTwo);
    };
    
    kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");
    
}
@end

注意,調用Block時必須保證Block已經(jīng)被初始化,因為此處為Block都在內部實現(xiàn),Block都是已經(jīng)初始化。 如果調用了一個未被初始化的Block ,程序將會崩潰。
兩種驗證Block是否初始化的方法

if (kGlobalBlockWaitingInitinalize) {
    kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");
}
!kGlobalBlockWaitingInitinalize ? : kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");

2.作為局部變量使用:
局部變量即在方法、函數(shù)中的Block

- (void)viewDidLoad {
    [super viewDidLoad];
    int multiplier = 7;
    int (^ myBlock)(int) = ^(int num){
       return num * multiplier;
    };
    myBlock(3);
}

3.用作屬性或者成員變量:

#import "ViewController.h"

typedef NSString * (^BlocksTypeParameter)(id , id);
typedef NSString * (^BlocksTypeNonParameter)();
typedef void (^BlocksTypeNonReture)(id parameterOne, id parameterTwo);

@interface ViewController ()
/* 無參數(shù)類型 */
@property (nonatomic, copy) NSString *(^blockProperty)();
/* 有參數(shù)類型 */
@property (nonatomic, copy) NSString *(^blockPropertyWithParameters)(NSString *);
/* 無返回值類型 */
@property (nonatomic, copy) void(^blockPropertyWithNonRetern)();
@end


@implementation ViewController{
    /* 有參數(shù)類型 */
    BlocksTypeParameter _parameterBlock;
    /* 無參數(shù)類型 */
    BlocksTypeNonParameter _nonParemeterBlock;
    /* 無返回值類型 */
    BlocksTypeNonReture _nonRetureBlock ;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self propertyBlock];
}

#pragma mark - - 屬性、成員變量類型的 Block
- (void)propertyBlock{
    NSString *name = @"Bob";
    // 無參數(shù)類型
    _blockProperty = ^{
        NSLog(@"no parameters");
        return name;
    };
    
    // 有參數(shù)類型
    _blockPropertyWithParameters = ^(NSString *parameter){
        NSLog(@"has parameters");
        return parameter;
    };
    
    // 無返回值類型
    _blockPropertyWithNonRetern = ^{
        NSLog(@"non retern");
    };
}

4.作為函數(shù)的參數(shù)傳遞

- (void)viewDidLoad {
    [super viewDidLoad];
    [self postNetWorkWithParameters:@"parameters" success:^(id response) {
        NSLog(@"%@",response);
    } failure:^(id error) {
        NSLog(@"%@",error);
    }];
}
- (void)postNetWorkWithParameters:(id)parameters
                          success:(void (^)(id response))finishBlock
                          failure:(void (^)(id error))failure{
    finishBlock(@"success");
    failure(@"failure");
}
三.Blocks的變量類型(循環(huán)引用關系)、__block儲存類型(本文重點)

如果你還在為使用Block照成循環(huán)引用而感到苦惱,那么看完這里將會解開你的疑惑
首先來說說官方文檔上關于Block可使用的變量歸為5類

1.Global variables are accessible, including static variables that exist within the enclosing lexical scope.

2.Parameters passed to the block are accessible (just like parameters to a function).

3.Stack (non-static) variables local to the enclosing lexical scope are captured as const variables. Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.

4.Variables local to the enclosing lexical scope declared with the __block storage modifier are provided by reference and so are mutable. Any changes are reflected in the enclosing lexical scope, including any other blocks defined within the same enclosing lexical scope. These are discussed in more detail in The __block Storage Type.

5.Local variables declared within the lexical scope of the block, which behave exactly like local variables in a function. Each invocation of the block provides a new copy of that variable. These variables can in turn be used as const or by-reference variables in blocks enclosed within the block.

即:

1.全局變量
2.傳遞給Block的參數(shù)
3.方法中局部變量非__block 捕獲變量
4.方法中__block 語法修飾的變量
5.Block內聲明的變量

3.1 關于__block存儲類型:

您可以通過應用__block存儲類型修飾符來指定導入的變量是可變的,即讀寫。

__block變量存儲在變量的作用域范圍中,以及在變量的作用域中聲明或創(chuàng)建的所有BlockBlock Copy之間共享。因此,如果在框架中聲明的Block的Copies of the blocks都能在框架的末尾(例如,通過在某個地方排隊等待執(zhí)行),那么存儲將會在堆棧的銷毀中存活下來。在給定作用域范圍內的多個Block可以同時使用一個共享變量。

作為優(yōu)化,Block存儲從堆棧開始,就像Block本身一樣。如果這個Block是用block copy復制的,變量會被復制到堆中。因此,__block 變量的地址可以隨時間變化。

對于__block變量還有兩個進一步的限制:它們不能是variable length arrays,也不能是包含C99 variable length arrays 的結構。

3.2 關于循環(huán)引用

如果您在方法的實現(xiàn)中使用了一個Block,當這個Block 被復制時,它會對Block中使用的對象的進行強引用:

那么關于循環(huán)引用的造成的原因必然是,在Block Copy 到堆中時Block強引用了這個對象,然后對象又存在強引用Block的情況下就會造成強引用。


不要在所有的Block中都將對象 __weak 或者__weak再__strong、
不要在所有的Block中都將對象 __weak 或者__weak再__strong、
不要在所有的Block中都將對象 __weak 或者__weak再__strong、
重要的事情說三遍。

第一種情況:
在block中使用對象,判斷是否造成循環(huán)引用,需要判斷當前對象是否強引用了block,block是否也強引用了改對象。

第二種情況:
對象A-強引用-對象B,對象B強引用Block,Block中又持有了對象A,也是一種循環(huán)引用。 對象A需要Block釋放后才能釋放,block需要對象B釋放后才能釋放,對象B需要對象A釋放后才能釋放,所以三者形成了循環(huán)引用。

以下是示例代碼

@interface BlockView : UIView
@property (nonatomic, copy) void(^myBlcok)() ;
@end
@implementation BlockView
@end

@interface ViewController ()
@property (nonatomic, copy) NSString *(^blockProperty)();
@property (nonatomic, strong) BlockView *blockView;
@end
- (void)viewDidLoad {
    [super viewDidLoad];

    // case 1
    _blockProperty = ^{
        return self.name;
    };
    _blockView.myBlcok = ^{
        NSLog(@"%@",_blockView); // 關注點不只在self,而是誰持有這個Block。
    };

    // case 2
    _blockView.myBlcok = ^{
        NSLog(@"%@",self.name);
    };
}
B387E9BA-9F1E-47F3-9FEE-238499E9A8CD.png

第一種是在兩者之間循環(huán),第二種是在三者之間循環(huán)。


以下是對循環(huán)引用的解決方案:

__weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
    };

那么對于方法、函數(shù)中的Block,大部分只存在Block對 對象進行強引用,不存在對象對Block強引用,此時的Block作為參數(shù)先是保存在棧中,然后 被Copy到堆中。

但如果你使用一些參數(shù)中可能含有 ivar 的系統(tǒng) api ,如 GCD 、NSNotificationCenter就要小心一點:比如GCD 內部如果引用了 self,而且 GCD 的其他參數(shù)是 ivar,則要考慮到循環(huán)引用:(此部分參考《招聘一個靠譜的iOS》面試題參考答案(下))

__weak __typeof__(self) weakSelf = self;
  _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                object:nil
                                                                 queue:nil
                                                            usingBlock:^(NSNotification *note) {
      __typeof__(self) strongSelf = weakSelf;
      [strongSelf dismissModalViewControllerAnimated:YES];
  }];

self --> _observer --> block --> self 顯然這也是一個循環(huán)引用。

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

相關閱讀更多精彩內容

  • Blocks編程要點 目錄 簡介............................................
    xuejunjun閱讀 1,452評論 0 5
  • Blocks are a non-standard extension added by Apple Inc. t...
    天口三水羊閱讀 3,859評論 30 124
  • 一、Objective-C發(fā)展史 Objective-C從1983年誕生,已經(jīng)走過了30多年的歷程。隨著時間的推移...
    沒事蹦蹦閱讀 5,999評論 12 34
  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,869評論 0 23
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,617評論 30 472

友情鏈接更多精彩內容