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];
}];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • Block Block底层原理实现 首先我们来看四个函数 造成这样的原因是:传值和传址。为什么说会有传值和传址,把...
    b485c88ab697阅读 7,704评论 0 41
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,876评论 0 7
  • 多线程、特别是NSOperation 和 GCD 的内部原理。运行时机制的原理和运用场景。SDWebImage的原...
    LZM轮回阅读 1,982评论 0 12
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,729评论 0 23