iOS (5) --Block

<h1>1、Block底層原理實現</h1>

首先我們來看四個函數

void test1()
{
    int a = 10;

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

    a = 20;

    block(); // 10
}

void test2()
{
    __block int a = 10;

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

    a = 20;

    block(); // 20
}

void test3()
{
    static int a = 10;

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

    a = 20;

    block(); // 20
}

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

    a = 20;

    block();//20
}

造成這樣的原因是:傳值和傳址。為什么說會有傳值和傳址,把.m編譯成c++代碼。得到.cpp文件,我們來到文件的最后,看到如下代碼

struct __test1_block_impl_0 {
    struct __block_impl impl;
    struct __test1_block_desc_0* Desc;
    int a;
    __test1_block_impl_0(void *fp,struct __test1_block_desc_0* Desc,int _a,int flag=0): a(_a){
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
};

static void __test1_block_func_0(struct __test1_block_imp_0 *__cself)
{
    int a = __cself->a;
    NSLog(a);//這里就是打印a的值,代碼太長,而且沒意義,我就不敲出來了。
}

void test1()
{
    int a = 10;
    void (*block)() = (void (*)())&__test1_block_impl_0((void *))__test1_block_func_0,&__test1_block_desc_0_DATA,a);

    a = 20;
    ((void (*)(__block_impl *))((__block_ipml *)block)->FuncPtr)((_block_impl *)block);
}

int main(int argc, const char * argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;         
       test1();
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我們看到void test1()中,void (*block)() 右邊最后面 ,把a傳進去了,也就是把10這個值傳進去了.

而且對void (block)()簡化分析,void (block)() = &__test1_block_impl_0();所以block就是指向結構體的指針。

10傳入block后,代碼最上面創建的__test1_block_impl_0結構體中,a = 10;

對void test1()中最下面的函數進行簡化分析,得到(block)->FuncPtr)(block),我們在回到剛才test1_block_impl_0這個結構體中,impl.FuncPtr = fp;而fp又是傳入結構體的第一個參數,而在void (block)()中,傳入結構體的第一個參數為*test1_block_func_0,也就是說(block)->FuncPtr)(block) =》__test1_block_func_0(block);

上一步相當于調用test1_block_func_0()這個函數,我們來看這個函數,有這樣一段代碼:int a = cself->a;訪問block中的a值,傳遞給a;所以是10.這種就是傳值!!!

=====
我們再來看test2( );添加了__block會發送什么變化呢

void test2()
{
    __attribute__((_blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),10};

    void(*block)() =  (void (*)())&__test2_block_impl_0((void *))__test2_block_func_0,&__test2_block_desc_0_DATA,(__Block_byref_a_0 *)&a,570425344);

    (a.__forwarding->a) = 20;

    ((void (*)(__block_impl *))((__block_ipml *)block)->FuncPtr)((_block_impl *)block);
}

int main(int argc, const char * argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;         
       test2();
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

代碼雖然很多看著很復雜,但是我們只需要看我們想要知道的,睜大你的眼睛,看到void(*block)()這個函數的最后面,有個&a,天啊,這里傳的是a的地址。從test2到test4,都是傳址,所以a的值發生改變,block打印出來的是a的最終值。
總結:只有普通局部變量是傳值,其他情況都是傳址。

<h1>2、block的定義</h1>

// 無參無返回
void(^block)();
// 無參有返回
int(^block1)();
// 有參有返回
int(^block1)(int number);

也可以直接打入inline來自動生成block格式

<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
    <#statements#>
};

<h1>3、block的內存管理</h1>

1> 無論當前環境是ARC還是MRC,只要block沒有訪問外部變量,block始終在全局區
2> MRC情況下
block如果訪問外部變量,block在棧里
不能對block使用retain,否則不能保存在堆里
只有使用copy,才能放到堆里
3> ARC情況下
block如果訪問外部變量,block在堆里
block可以使用copy和strong,并且block是一個對象

<h1>4、block的循環引用</h1>

如果要在block中直接使用外部強指針會發生錯誤,使用以下代碼在block外部實現可以解決
__weak typeof(self) weakSelf = self;
但是如果在block內部使用延時操作還使用弱指針的話會取不到該弱指針,需要在block內部再將弱指針強引用一下
__strong typeof(self) strongSelf = weakSelf;

<h1>5、描述一個你遇到過的retain cycle例子。</h1>

block中的循環引用:一個viewController
@property (nonatomic,strong)HttpRequestHandler * handler;    

@property (nonatomic,strong)NSData  *data;    
 _handler = [httpRequestHandler sharedManager];   
  [ downloadData:^(id responseData){     
_data = responseData;  
   }];
self 擁有_handler, _handler 擁有block, block擁有self(因為使用了self的_data屬性,block會copy 一份self) 
解決方法:
__weak typedof(self)weakSelf = self  
 [ downloadData:^(id responseData){    
     weakSelf.data = responseData;

<h1>6、block中的weak self,是任何時候都需要加的么?</h1>

不是什么任何時候都需要添加的,不過任何時候都添加似乎總是好的。只要出現像self->block->self.property/self->_ivar這樣的結構鏈時,才會出現循環引用問題。好好分析一下,就可以推斷出是否會有循環引用問題。

<h1>7、通過block來傳值</h1>

在控制器間傳值可以使用代理或者block,使用block相對來說簡潔
在前一個控制器的touchesBegan:方法內實現如下代碼

ModalViewController *modalVc = [[ModalViewController alloc] init];

  modalVc.valueBlcok = ^(NSString *str){

  NSLog(@"ViewController拿到%@",str);
  };

  [self presentViewController:modalVc animated:YES completion:nil];

在ModalViewController控制器的.h文件中聲明一個block屬性 @property (nonatomic ,strong) void(^valueBlcok)(NSString *str);

并在.m文件中實現方法

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        // 傳值:調用block
        if (_valueBlcok) {
        _valueBlcok(@"123");
        }
    }

這樣在ModalViewController回到上一個控制器的時候,上一個控制器的label就能顯示ModalViewController傳過來的字符串

<h1>8、block作為一個參數使用</h1>

新建一個類,在.h文件中聲明一個方法- (void)calculator:(int(^)(int result))block;

并在.m文件中實現該方法

  -(void)calculator:(int (^)(int))block
  {
  self.result = block(self.result);
  }

在其他類中調用該方法

 CalculatorManager *mgr = [[CalculatorManager alloc] init];
  [mgr calculator:^(int result){
  result += 5;
  return result;
  }];

<h1>9、block作為返回值使用</h1>

在masonry框架中我們可以看到如下用法make.top.equalTo(superview.mas_top).with.offset(padding.top); 這個方法實現就是將block作為返回值來使用

來分析一下這段代碼:其實可以將這段代碼看成make.top,make.equalTo,make.with,make.offset,所以可以得出一個結論是make.top返回了一個make,才能實現make.top.equalTo

那來模仿一下這種功能的實現

新建一個類,在.h文件中聲明一個方法- (CalculatorManager *(^)(int a))add;

在.m文件中實現方法

  -(CalculatorManager * (^)(int a))add
  {
      return ^(int a){
          _result += a;
      return self;
  };
  }

這樣就可以在別的類中實現上面代碼的用法

   mgr.add(1).add(2).add(3);

<h1>10、block的變量傳遞</h1>

如果block訪問的外部變量是局部變量,那么就是值傳遞,外界改了,不會影響里面
如果block訪問的外部變量是__block或者static修飾,或者是全局變量,那么就是指針傳遞,block里面的值和外界同一個變量,外界改變,里面也會改變
驗證一下是不是這樣
通過Clang來將main.m文件編譯為C++
在終端輸入如下命令clang -rewrite-objc main.m

void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,         &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
 void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,        

可以看到在編譯后的代碼最后可以發現被__block修飾過得變量使用的是&a,而局部變量是a

<h1>11、block的注意點</h1>

在block內部使用外部指針且會造成循環引用情況下,需要用__weak修飾外部指針

__weak typeof(self) weakSelf = self;

在block內部如果調用了延時函數還使用弱指針會取不到該指針,因為已經被銷毀了,需要在block內部再將弱指針重新強引用一下

__strong typeof(self) strongSelf = weakSelf;

如果需要在block內部改變外部變量的話,需要在用__block修飾外部變量
<h1>12、使用block有什么好處?使用NSTimer寫出一個使用block顯示(在UILabel上)秒表的代碼。</h1>

說到block的好處,最直接的就是代碼緊湊,傳值、回調都很方便,省去了寫代理的很多代碼。
對于這里根本沒有必要使用block來刷新UILabel顯示,因為都是直接賦值。當然,筆者覺得這是在考驗應聘者如何將NSTimer寫成一個通用用的Block版本。
NSTimer封裝成Block版: http://www.henishuo.com/nstimer-block/
使用起來像這樣:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                    repeats:YES
                                   callback:^() {
  weakSelf.secondsLabel.text = ...
}
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

<h1>13、block跟函數很像:</h1>

可以保存代碼
有返回值
有形參
調用方式一樣

<h1>14、使用系統的某些block api(如UIView的block版本寫動畫時),是否也考慮引用循環問題?</h1>

系統的某些block api中,UIView的block版本寫動畫時不需要考慮,但也有一些api需要考慮。所謂“引用循環”是指雙向的強引用,
所以那些“單向的強引用”(block 強引用 self )沒有問題,比如這些:

[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }]; 
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }]; 
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" 
                           object:nil 
                           queue:[NSOperationQueue mainQueue]
                           usingBlock:^(NSNotification * notification) {
                           self.someProperty = xyz; 
                                                    }];

這些情況不需要考慮“引用循環”。
但如果你使用一些參數中可能含有成員變量的系統api,如GCD、NSNotificationCenter就要小心一點。比如GCD內部如果引用了 self,而且GCD的其他參數是成員變量,則要考慮到循環引用:

__weak __typeof(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
    __typeof__(self) strongSelf = weakSelf;
    [strongSelf doSomething];
    [strongSelf doSomethingElse];
});

類似的:

__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 顯然這也是一個循環引用。

<h1>15、談談對Block 的理解?并寫出一個使用Block執行UIVew動畫?</h1>

Block是可以獲取其他函數局部變量的匿名函數,其不但方便開發,并且可以大幅提高應用的執行效率(多核心CPU可直接處理Block指令)

[UIView transitionWithView:self.view duration:0.2
                                      ptions:UIViewAnimationOptionTransitionFlipFromLeft
                                      animations:^{ [[blueViewController view] removeFromSuperview]; [[self view] insertSubview:yellowViewController.view atIndex:0]; }
                                       completion:NULL];

<h1>16、寫出上面代碼的Block的定義。</h1>

typedef void(^animations) (void);
typedef void(^completion) (BOOL finished);

<h1>17、什么是block</h1>

1> 對于閉包(block),有很多定義,其中閉包就是獲取其它函數局部變量的匿名函數,這個定義即接近本質又較好理解。

2> 對于剛接觸Block的同學,會覺得有些繞,因為我們習慣寫這樣的程序main(){ funA();} funA(){funB();} funB(){…..}; 就是函數main調用函數A,函數A調用函數B… 函數們依次順序執行,但現實中不全是這樣的,例如項目經理M,手下有3個程序員A、B、C,當他給程序員A安排實現功能F1時,他并不等著A完成之后,再去安排B去實現F2,而是安排給A功能F1,B功能F2,C功能F3,然后可能去寫技術文檔,而當A遇到問題時,他會來找項目經理M,當B做完時,會通知M,這就是一個異步執行的例子。

3> 在這種情形下,Block便可大顯身手,因為在項目經理M,給A安排工作時,同時會告訴A若果遇到困難,如何能找到他報告問題(例如打他手機號),這就是項目經理M給A的一個回調接口,要回掉的操作,比如接到電話,百度查詢后,返回網頁內容給A,這就是一個Block,在M交待工作時,已經定義好,并且取得了F1的任務號(局部變量),卻是在當A遇到問題時,才調用執行,跨函數在項目經理M查詢百度,獲得結果后回調該block。

<h1>18、block 實現原理</h1>

Objective-C是對C語言的擴展,block的實現是基于指針和函數指針。
從計算語言的發展,最早的goto,高級語言的指針,到面向對象語言的block,從機器的思維,一步步接近人的思維,以方便開發人員更為高效、直接的描述出現實的邏輯(需求)。
使用實例:cocoaTouch框架下動畫效果的Block的調用

使用typed聲明block
typedef void(^didFinishBlock) (NSObject *ob);
這就聲明了一個didFinishBlock類型的block,
然后便可用
@property (nonatomic,copy) didFinishBlock finishBlock;
聲明一個blokc對象,注意對象屬性設置為copy,接到block 參數時,便會自動復制一份。
__block是一種特殊類型,
使用該關鍵字聲明的局部變量,可以被block所改變,并且其在原函數中的值會被改變。

<h1>19、關于block</h1>

面試時,面試官會先問一些,是否了解block,是否使用過block,這些問題相當于開場白,往往是下面一系列問題的開始,所以一定要如實根據自己的情況回答。

1). 使用block和使用delegate完成委托模式有什么優點?

首先要了解什么是委托模式,委托模式在iOS中大量應用,其在設計模式中是適配器模式中的對象適配器,Objective-C中使用id類型指向一切對象,使委托模式更為簡潔。了解委托模式的細節:

iOS設計模式—-委托模式

使用block實現委托模式,其優點是回調的block代碼塊定義在委托對象函數內部,使代碼更為緊湊;

適配對象不再需要實現具體某個protocol,代碼更為簡潔。

2). 多線程與block

GCD與Block

使用 dispatch_async 系列方法,可以以指定的方式執行block

GCD編程實例

dispatch_async的完整定義

void dispatch_async(

dispatch_queue_t queue,

dispatch_block_t block);

功能:在指定的隊列里提交一個異步執行的block,不阻塞當前線程

通過queue來控制block執行的線程。主線程執行前文定義的 finishBlock對象

dispatch_async(dispatch_get_main_queue(),^(void){finishBlock();});

<h1>20、解釋以下代碼的內存泄漏原因</h1>

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {       
    HJTestCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TestCell" forIndexPath:indexPath];
    [cell setTouchBlock:^(HJTestCell *cell) {
        [self refreshData];
    }];       
   return cell;
}

原因:

[cell setTouchBlock:^(HJTestCell *cell) {
    [self refreshData];
}];

產生內存泄露的原因是因為循環引用

在給cell設置的TouchBlock中,使用了__strong修飾的self,由于Block的原理,當touchBlock從棧復制到堆中時,self會一同復制到堆中,retain一次,被touchBlock持有,而touchBlock又是被cell持有的,cell又被tableView持有,tableView又被self持有,因此形成了循環引用:self間接持有touchBlock,touchBlock持有self

一旦產生了循環引用,由于兩個object都被強引用,所以retainCount始終不能為0,無發釋放,產生內存泄漏

解決辦法:
使用weakSelf解除touchBlock對self的強引用

__weak __typeof__(self) weakSelf = self;
[cell setTouchBlock:^(HJTestCell *cell) {
    [weakSelf refreshData];
}];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Block Block底層原理實現 首先我們來看四個函數 造成這樣的原因是:傳值和傳址。為什么說會有傳值和傳址,把...
    b485c88ab697閱讀 7,899評論 0 41
  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 2,013評論 0 7
  • 多線程、特別是NSOperation 和 GCD 的內部原理。運行時機制的原理和運用場景。SDWebImage的原...
    LZM輪回閱讀 2,039評論 0 12
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,211評論 30 472
  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,785評論 0 23