<h1>1酥馍、Block底層原理實(shí)現(xiàn)</h1>
首先我們來(lái)看四個(gè)函數(shù)
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
}
造成這樣的原因是:傳值和傳址故慈。為什么說(shuō)會(huì)有傳值和傳址板熊,把.m編譯成c++代碼。得到.cpp文件察绷,我們來(lái)到文件的最后干签,看到如下代碼
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的值,代碼太長(zhǎng)拆撼,而且沒(méi)意義容劳,我就不敲出來(lái)了。
}
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(bǔ)傳進(jìn)去了,也就是把10這個(gè)值傳進(jìn)去了.
而且對(duì)void (block)()簡(jiǎn)化分析莺禁,void (block)() = &__test1_block_impl_0();所以block就是指向結(jié)構(gòu)體的指針留量。
10傳入block后,代碼最上面創(chuàng)建的__test1_block_impl_0結(jié)構(gòu)體中哟冬,a = 10楼熄;
對(duì)void test1()中最下面的函數(shù)進(jìn)行簡(jiǎn)化分析,得到(block)->FuncPtr)(block)柒傻,我們?cè)诨氐絼偛?em>test1_block_impl_0這個(gè)結(jié)構(gòu)體中孝赫,impl.FuncPtr = fp;而fp又是傳入結(jié)構(gòu)體的第一個(gè)參數(shù),而在void (block)()中红符,傳入結(jié)構(gòu)體的第一個(gè)參數(shù)為*test1_block_func_0青柄,也就是說(shuō)(block)->FuncPtr)(block) =》__test1_block_func_0(block);
上一步相當(dāng)于調(diào)用test1_block_func_0()這個(gè)函數(shù),我們來(lái)看這個(gè)函數(shù)预侯,有這樣一段代碼:int a = cself->a;訪問(wèn)block中的a值致开,傳遞給a;所以是10.這種就是傳值N凇K痢!
=====
我們?cè)賮?lái)看test2( );添加了__block會(huì)發(fā)送什么變化呢
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 };
代碼雖然很多看著很復(fù)雜糜芳,但是我們只需要看我們想要知道的飒货,睜大你的眼睛魄衅,看到void(*block)()這個(gè)函數(shù)的最后面,有個(gè)&a,天啊塘辅,這里傳的是a的地址晃虫。從test2到test4,都是傳址扣墩,所以a的值發(fā)生改變哲银,block打印出來(lái)的是a的最終值。
總結(jié):只有普通局部變量是傳值呻惕,其他情況都是傳址荆责。
<h1>2、block的定義</h1>
// 無(wú)參無(wú)返回
void(^block)();
// 無(wú)參有返回
int(^block1)();
// 有參有返回
int(^block1)(int number);
也可以直接打入inline來(lái)自動(dòng)生成block格式
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
<h1>3亚脆、block的內(nèi)存管理</h1>
1> 無(wú)論當(dāng)前環(huán)境是ARC還是MRC,只要block沒(méi)有訪問(wèn)外部變量,block始終在全局區(qū)
2> MRC情況下
block如果訪問(wèn)外部變量,block在棧里
不能對(duì)block使用retain,否則不能保存在堆里
只有使用copy,才能放到堆里
3> ARC情況下
block如果訪問(wèn)外部變量,block在堆里
block可以使用copy和strong,并且block是一個(gè)對(duì)象
<h1>4做院、block的循環(huán)引用</h1>
如果要在block中直接使用外部強(qiáng)指針會(huì)發(fā)生錯(cuò)誤,使用以下代碼在block外部實(shí)現(xiàn)可以解決
__weak typeof(self) weakSelf = self;
但是如果在block內(nèi)部使用延時(shí)操作還使用弱指針的話會(huì)取不到該弱指針,需要在block內(nèi)部再將弱指針強(qiáng)引用一下
__strong typeof(self) strongSelf = weakSelf;
<h1>5、描述一個(gè)你遇到過(guò)的retain cycle例子濒持。</h1>
block中的循環(huán)引用:一個(gè)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(因?yàn)槭褂昧藄elf的_data屬性山憨,block會(huì)copy 一份self)
解決方法:
__weak typedof(self)weakSelf = self
[ downloadData:^(id responseData){
weakSelf.data = responseData;
<h1>6、block中的weak self弥喉,是任何時(shí)候都需要加的么?</h1>
不是什么任何時(shí)候都需要添加的玛迄,不過(guò)任何時(shí)候都添加似乎總是好的由境。只要出現(xiàn)像self->block->self.property/self->_ivar這樣的結(jié)構(gòu)鏈時(shí),才會(huì)出現(xiàn)循環(huán)引用問(wèn)題蓖议。好好分析一下虏杰,就可以推斷出是否會(huì)有循環(huán)引用問(wèn)題。
<h1>7勒虾、通過(guò)block來(lái)傳值</h1>
在控制器間傳值可以使用代理或者block,使用block相對(duì)來(lái)說(shuō)簡(jiǎn)潔
在前一個(gè)控制器的touchesBegan:方法內(nèi)實(shí)現(xiàn)如下代碼
ModalViewController *modalVc = [[ModalViewController alloc] init];
modalVc.valueBlcok = ^(NSString *str){
NSLog(@"ViewController拿到%@",str);
};
[self presentViewController:modalVc animated:YES completion:nil];
在ModalViewController控制器的.h文件中聲明一個(gè)block屬性 @property (nonatomic ,strong) void(^valueBlcok)(NSString *str);
并在.m文件中實(shí)現(xiàn)方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 傳值:調(diào)用block
if (_valueBlcok) {
_valueBlcok(@"123");
}
}
這樣在ModalViewController回到上一個(gè)控制器的時(shí)候,上一個(gè)控制器的label就能顯示ModalViewController傳過(guò)來(lái)的字符串
<h1>8纺阔、block作為一個(gè)參數(shù)使用</h1>
新建一個(gè)類(lèi),在.h文件中聲明一個(gè)方法- (void)calculator:(int(^)(int result))block;
并在.m文件中實(shí)現(xiàn)該方法
-(void)calculator:(int (^)(int))block
{
self.result = block(self.result);
}
在其他類(lèi)中調(diào)用該方法
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); 這個(gè)方法實(shí)現(xiàn)就是將block作為返回值來(lái)使用
來(lái)分析一下這段代碼:其實(shí)可以將這段代碼看成make.top,make.equalTo,make.with,make.offset,所以可以得出一個(gè)結(jié)論是make.top返回了一個(gè)make,才能實(shí)現(xiàn)make.top.equalTo
那來(lái)模仿一下這種功能的實(shí)現(xiàn)
新建一個(gè)類(lèi),在.h文件中聲明一個(gè)方法- (CalculatorManager *(^)(int a))add;
在.m文件中實(shí)現(xiàn)方法
-(CalculatorManager * (^)(int a))add
{
return ^(int a){
_result += a;
return self;
};
}
這樣就可以在別的類(lèi)中實(shí)現(xiàn)上面代碼的用法
mgr.add(1).add(2).add(3);
<h1>10修然、block的變量傳遞</h1>
如果block訪問(wèn)的外部變量是局部變量,那么就是值傳遞,外界改了,不會(huì)影響里面
如果block訪問(wèn)的外部變量是__block或者static修飾,或者是全局變量,那么就是指針傳遞,block里面的值和外界同一個(gè)變量,外界改變,里面也會(huì)改變
驗(yàn)證一下是不是這樣
通過(guò)Clang來(lái)將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,
可以看到在編譯后的代碼最后可以發(fā)現(xiàn)被__block修飾過(guò)得變量使用的是&a,而局部變量是a
<h1>11笛钝、block的注意點(diǎn)</h1>
在block內(nèi)部使用外部指針且會(huì)造成循環(huán)引用情況下,需要用__weak修飾外部指針
__weak typeof(self) weakSelf = self;
在block內(nèi)部如果調(diào)用了延時(shí)函數(shù)還使用弱指針會(huì)取不到該指針,因?yàn)橐呀?jīng)被銷(xiāo)毀了,需要在block內(nèi)部再將弱指針重新強(qiáng)引用一下
__strong typeof(self) strongSelf = weakSelf;
如果需要在block內(nèi)部改變外部變量的話,需要在用__block修飾外部變量
<h1>12、使用block有什么好處愕宋?使用NSTimer寫(xiě)出一個(gè)使用block顯示(在UILabel上)秒表的代碼。</h1>
說(shuō)到block的好處,最直接的就是代碼緊湊咧织,傳值萧豆、回調(diào)都很方便,省去了寫(xiě)代理的很多代碼邻寿。
對(duì)于這里根本沒(méi)有必要使用block來(lái)刷新UILabel顯示蝎土,因?yàn)槎际侵苯淤x值视哑。當(dāng)然,筆者覺(jué)得這是在考驗(yàn)應(yīng)聘者如何將NSTimer寫(xiě)成一個(gè)通用用的Block版本誊涯。
NSTimer封裝成Block版: http://www.henishuo.com/nstimer-block/
使用起來(lái)像這樣:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
repeats:YES
callback:^() {
weakSelf.secondsLabel.text = ...
}
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
<h1>13挡毅、block跟函數(shù)很像:</h1>
可以保存代碼
有返回值
有形參
調(diào)用方式一樣
<h1>14、使用系統(tǒng)的某些block api(如UIView的block版本寫(xiě)動(dòng)畫(huà)時(shí))醋拧,是否也考慮引用循環(huán)問(wèn)題慷嗜?</h1>
系統(tǒng)的某些block api中,UIView的block版本寫(xiě)動(dòng)畫(huà)時(shí)不需要考慮丹壕,但也有一些api需要考慮庆械。所謂“引用循環(huán)”是指雙向的強(qiáng)引用,
所以那些“單向的強(qiáng)引用”(block 強(qiáng)引用 self )沒(méi)有問(wèn)題菌赖,比如這些:
[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;
}];
這些情況不需要考慮“引用循環(huán)”缭乘。
但如果你使用一些參數(shù)中可能含有成員變量的系統(tǒng)api,如GCD琉用、NSNotificationCenter就要小心一點(diǎn)堕绩。比如GCD內(nèi)部如果引用了 self,而且GCD的其他參數(shù)是成員變量邑时,則要考慮到循環(huán)引用:
__weak __typeof(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
});
類(lèi)似的:
__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 顯然這也是一個(gè)循環(huán)引用奴紧。
<h1>15、談?wù)剬?duì)Block 的理解?并寫(xiě)出一個(gè)使用Block執(zhí)行UIVew動(dòng)畫(huà)?</h1>
Block是可以獲取其他函數(shù)局部變量的匿名函數(shù)晶丘,其不但方便開(kāi)發(fā)黍氮,并且可以大幅提高應(yīng)用的執(zhí)行效率(多核心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、寫(xiě)出上面代碼的Block的定義浅浮。</h1>
typedef void(^animations) (void);
typedef void(^completion) (BOOL finished);
<h1>17沫浆、什么是block</h1>
1> 對(duì)于閉包(block),有很多定義,其中閉包就是獲取其它函數(shù)局部變量的匿名函數(shù)滚秩,這個(gè)定義即接近本質(zhì)又較好理解专执。
2> 對(duì)于剛接觸Block的同學(xué),會(huì)覺(jué)得有些繞郁油,因?yàn)槲覀兞?xí)慣寫(xiě)這樣的程序main(){ funA();} funA(){funB();} funB(){…..}; 就是函數(shù)main調(diào)用函數(shù)A本股,函數(shù)A調(diào)用函數(shù)B… 函數(shù)們依次順序執(zhí)行,但現(xiàn)實(shí)中不全是這樣的已艰,例如項(xiàng)目經(jīng)理M痊末,手下有3個(gè)程序員A、B哩掺、C凿叠,當(dāng)他給程序員A安排實(shí)現(xiàn)功能F1時(shí),他并不等著A完成之后,再去安排B去實(shí)現(xiàn)F2盒件,而是安排給A功能F1蹬碧,B功能F2,C功能F3炒刁,然后可能去寫(xiě)技術(shù)文檔恩沽,而當(dāng)A遇到問(wèn)題時(shí),他會(huì)來(lái)找項(xiàng)目經(jīng)理M翔始,當(dāng)B做完時(shí)罗心,會(huì)通知M,這就是一個(gè)異步執(zhí)行的例子城瞎。
3> 在這種情形下渤闷,Block便可大顯身手,因?yàn)樵陧?xiàng)目經(jīng)理M脖镀,給A安排工作時(shí)飒箭,同時(shí)會(huì)告訴A若果遇到困難,如何能找到他報(bào)告問(wèn)題(例如打他手機(jī)號(hào))蜒灰,這就是項(xiàng)目經(jīng)理M給A的一個(gè)回調(diào)接口弦蹂,要回掉的操作,比如接到電話强窖,百度查詢(xún)后凸椿,返回網(wǎng)頁(yè)內(nèi)容給A,這就是一個(gè)Block翅溺,在M交待工作時(shí)削饵,已經(jīng)定義好,并且取得了F1的任務(wù)號(hào)(局部變量)未巫,卻是在當(dāng)A遇到問(wèn)題時(shí),才調(diào)用執(zhí)行启昧,跨函數(shù)在項(xiàng)目經(jīng)理M查詢(xún)百度叙凡,獲得結(jié)果后回調(diào)該block。
<h1>18密末、block 實(shí)現(xiàn)原理</h1>
Objective-C是對(duì)C語(yǔ)言的擴(kuò)展握爷,block的實(shí)現(xiàn)是基于指針和函數(shù)指針。
從計(jì)算語(yǔ)言的發(fā)展严里,最早的goto新啼,高級(jí)語(yǔ)言的指針,到面向?qū)ο笳Z(yǔ)言的block刹碾,從機(jī)器的思維燥撞,一步步接近人的思維,以方便開(kāi)發(fā)人員更為高效、直接的描述出現(xiàn)實(shí)的邏輯(需求)物舒。
使用實(shí)例:cocoaTouch框架下動(dòng)畫(huà)效果的Block的調(diào)用
使用typed聲明block
typedef void(^didFinishBlock) (NSObject *ob);
這就聲明了一個(gè)didFinishBlock類(lèi)型的block色洞,
然后便可用
@property (nonatomic,copy) didFinishBlock finishBlock;
聲明一個(gè)blokc對(duì)象,注意對(duì)象屬性設(shè)置為copy冠胯,接到block 參數(shù)時(shí)火诸,便會(huì)自動(dòng)復(fù)制一份。
__block是一種特殊類(lèi)型荠察,
使用該關(guān)鍵字聲明的局部變量置蜀,可以被block所改變,并且其在原函數(shù)中的值會(huì)被改變悉盆。
<h1>19盯荤、關(guān)于block</h1>
面試時(shí),面試官會(huì)先問(wèn)一些舀瓢,是否了解block廷雅,是否使用過(guò)block,這些問(wèn)題相當(dāng)于開(kāi)場(chǎng)白京髓,往往是下面一系列問(wèn)題的開(kāi)始航缀,所以一定要如實(shí)根據(jù)自己的情況回答。
1). 使用block和使用delegate完成委托模式有什么優(yōu)點(diǎn)?
首先要了解什么是委托模式堰怨,委托模式在iOS中大量應(yīng)用芥玉,其在設(shè)計(jì)模式中是適配器模式中的對(duì)象適配器,Objective-C中使用id類(lèi)型指向一切對(duì)象备图,使委托模式更為簡(jiǎn)潔灿巧。了解委托模式的細(xì)節(jié):
iOS設(shè)計(jì)模式—-委托模式
使用block實(shí)現(xiàn)委托模式,其優(yōu)點(diǎn)是回調(diào)的block代碼塊定義在委托對(duì)象函數(shù)內(nèi)部揽涮,使代碼更為緊湊;
適配對(duì)象不再需要實(shí)現(xiàn)具體某個(gè)protocol抠藕,代碼更為簡(jiǎn)潔。
2). 多線程與block
GCD與Block
使用 dispatch_async 系列方法蒋困,可以以指定的方式執(zhí)行block
GCD編程實(shí)例
dispatch_async的完整定義
void dispatch_async(
dispatch_queue_t queue,
dispatch_block_t block);
功能:在指定的隊(duì)列里提交一個(gè)異步執(zhí)行的block盾似,不阻塞當(dāng)前線程
通過(guò)queue來(lái)控制block執(zhí)行的線程。主線程執(zhí)行前文定義的 finishBlock對(duì)象
dispatch_async(dispatch_get_main_queue(),^(void){finishBlock();});
<h1>20雪标、解釋以下代碼的內(nèi)存泄漏原因</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];
}];
產(chǎn)生內(nèi)存泄露的原因是因?yàn)檠h(huán)引用
在給cell設(shè)置的TouchBlock中零院,使用了__strong修飾的self,由于Block的原理村刨,當(dāng)touchBlock從棧復(fù)制到堆中時(shí)告抄,self會(huì)一同復(fù)制到堆中,retain一次嵌牺,被touchBlock持有打洼,而touchBlock又是被cell持有的龄糊,cell又被tableView持有,tableView又被self持有拟蜻,因此形成了循環(huán)引用:self間接持有touchBlock绎签,touchBlock持有self
一旦產(chǎn)生了循環(huán)引用,由于兩個(gè)object都被強(qiáng)引用酝锅,所以retainCount始終不能為0诡必,無(wú)發(fā)釋放,產(chǎn)生內(nèi)存泄漏
解決辦法:
使用weakSelf解除touchBlock對(duì)self的強(qiáng)引用
__weak __typeof__(self) weakSelf = self;
[cell setTouchBlock:^(HJTestCell *cell) {
[weakSelf refreshData];
}];