<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];
}];