iOS基础·OC高级篇16iOS笔试面试

可能碰到的iOS笔试面试题(8)--Block

2016-05-04  本文已影响2614人  b485c88ab697

Block

Block底层原理实现

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
}


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

block的定义

// 无参无返回
void(^block)();
// 无参有返回
int(^block1)();
// 有参有返回
int(^block1)(int number);

也可以直接打入inline来自动生成block格式

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

block的内存管理

block的循环引用

描述一个你遇到过的retain cycle例子。

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;
block中的weak self,是任何时候都需要加的么?

通过block来传值

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

block作为一个参数使用

block作为返回值使用

block的变量传递

    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,         &__main_block_desc_0_DATA, a));

block的注意点

使用block有什么好处?使用NSTimer写出一个使用block显示(在UILabel上)秒表的代码。

说到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];

block跟函数很像:

使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?

系统的某些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 显然这也是一个循环引用。

谈谈对Block 的理解?并写出一个使用Block执行UIVew动画?

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

写出上面代码的Block的定义。

什么是block

block 实现原理

使用typed声明block
typedef void(^didFinishBlock) (NSObject *ob);
这就声明了一个didFinishBlock类型的block,
然后便可用
@property (nonatomic,copy) didFinishBlock finishBlock;
声明一个blokc对象,注意对象属性设置为copy,接到block 参数时,便会自动复制一份。
__block是一种特殊类型,
使用该关键字声明的局部变量,可以被block所改变,并且其在原函数中的值会被改变。

关于block

答: 面试时,面试官会先问一些,是否了解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();});

解释以下代码的内存泄漏原因

@implementation HJTestViewController

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

文章如有问题,请留言,我将及时更正。

满地打滚卖萌求赞,如果本文帮助到你,轻点下方的红心,给作者君增加更新的动力。

上一篇 下一篇

猜你喜欢

热点阅读