博客> iOS面试题之block
iOS面试题之block
2019-07-16 02:48 评论:0 阅读:306 折腾贞子

1、Block是OC中非常重要的一种技术手段 Block是C语音中的一个数据类型,一个函数体(匿名函数)、在需要时调用、可能有参数、可能有返回值。 2、从c函数来定义Block C函数写法: int add(int num1, int num2) OC函数写法:-(void)show:(int num1) 由C到Block的转变:void(^myBlock)() 3、Block的基本使用 创建一个命令行项目,选择OS X->Application->Command Line Tool 写代码之前牢记三句话 1、Block是C语言的 2、Block是一个数据类型 3、Block是一个提前准备好的代码,在需要的时候执行

(1、最简单的Block
//Block是一个提前好的代码,所以在赋值的时候要赋一段代码  (定义时,把Block当成一个数据类型)
void (^myBlock)() = ^{
    NSLog(@"hello");
};
//执行,执行时把Block当成函数
myBlock();

至此,最简单的Block就定义好了。它的两个特点是:
1、类型比函数多了一个‘^’符号。
2、设置数值时,有一个‘^’符号,内容是{}括起来的一段代码。

(2、定义带参数的Block
格式: ‘void (^Block名称)(参数列表) = ^(参数列表){//代码实现}’
在代码中,如果要使用到参数,就必须要带上变量名,所以定义x,y。在本代码中,把‘^’号后面的小括号当成一个参数。
void (^sumBlock)(int,int) = ^(int x,int y){
    NSLog(@"%d",x+y);
}
//执行
sumBlock(10,20);

(3、定义带返回值的Block
格式:‘返回值类型(^Block名称)(参数列表)=^返回类型(参数列表){//代码实现}’
int (^sumBlock2)(int,int)=^int(int a,int b){
    return a+b;
}
//执行
NSLog(@"%d",sumBlock2(4,3));

此处恰好说明,Block是一段提前准备好的代码,当调用它时,它将返回值返回。
block的速记符号:在代码中敲"inlineBlock",xCode会返回已经定义好的Block格式。

4、Block的应用场景
如果有这样的代码,每个函数都有很多重复的部分,这个时候我们通常会想到要重构代码。也就是说把相同的代码抽取出来,放在一个地方维护,如果需求变化就只要调整一个地方。

void day1(){
    NSLog(@"起床");
    NSLog(@"上班");
    NSLog(@"入职");
    NSLog(@"要账号");
    NSLog(@"看代码");
    NSLog(@"下班");
    NSLog(@"睡觉");
}
void day2(){
    NSLog(@"起床");
    NSLog(@"上班");
    NSLog(@"需求分析");
    NSLog(@"熟悉公司环境");
    NSLog(@"下班");
    NSLog(@"睡觉");
}
void day3(){
    NSLog(@"起床");
    NSLog(@"上班");
    NSLog(@"写代码");
    NSLog(@"开会");
    NSLog(@"测试");
    NSLog(@"下班");
    NSLog(@"睡觉");
}
void day4(){
    NSLog(@"写代码");
    NSLog(@"测试");
    }

重构的步骤:
1、新建一个方法
2、将重复的代码复制到新方法中
3、根据需求,调整参数。
重构的内容:建立一个新的方法

void myWork(){
    //这几句每个方法都一样的
    NSLog(@"起床");
    NSLog(@"出门");
    NSLog(@"坐车");
    //中间是每天的工作内容
    //…… 有可能有几条
    NSLog(@"下班");
    NSLog(@"回家");
    NSLog(@"睡觉");
}

在上面代码中,有很多重复的事情要做。但是每天的工作内容是不一样的,也就是说要传入不同的参数。于是问题来了,该怎样去确定重构的参数呢?
其实我们只要将代码放入其中,让它执行即可。而Block正是一段预先准备好的代码,在需要的时候执行。
在每天的工作内容中,调用一个定义好的block,每次调用都会执行block的内容,该问题就能迎刃而解。现在来写重构的代码。
void myWork(void(^dayWork)()){
    NSLog(@"起床");
    NSLog(@"出门");
    NSLog(@"坐车")
    //这里调用参数
    dayWork();
    NSLog(@"下班");
    NSLog(@"回家");
    NSLog(@"睡觉");
}

此时,在其他普通的方法中,就可以省去那些多余的代码,只要把不一样的代码传入到block即可。
void day1(){
    myWork(^{
        NSLog(@"入职");
        NSLog(@"要账号");
        NSLog(@"看代码");
    });
}

在主函数中调用函数day1()
int main(int argc,const cahr * argv[]){
    @autoreleasepool {
        day1();
    }
    return 0;
}

自此已经可以将重复的代码省去了。
其它方法也以此类推,写出每天工作中不同的操作。

void day2(){
    myWork(^{
        NSLog(@"需求分析");
        NSLog(@"熟悉公司环境")
    });
}
void day3(){
    myWork(^{
        NSLog(@"写代码");
        NSLog(@"开会")
    });
}
void day4(){
    myWork(^{
        NSLog(@"写代码");
        NSLog(@"开会")
        NSLog(@"测试")
    });
}

调用

int main(int argc,const cahr * argv[]){
    @autoreleasepool {
        day1();
        day2();
        day3();
        day4();
    }
    return 0;
}

分割线 几何形
于是,问题又来了。
day1()-day5()的调用实在太过死板,是否能实现一次调用呢?
思路:这个时候,我们应该创建一个新的方法,设置一个day的参数,根据day来决定具体执行哪个块的代码。
void dailyWork(int day){
    //定义一个block变量,没有设置代码
    void(^work)();
    //根据day来决定调用哪些代码
    switch(day){
     case 1:
        work = ^{
              NSLog(@"入职");
              NSLog(@"要账号");
              NSLog(@"看代码");
        };
        break;
    case 2:
        work = ^{
             NSLog(@"需求分析");
             NSLog(@"熟悉公司环境")
        };
        break;
     case 3:
        work = ^{
              NSLog(@"写代码");
              NSLog(@"开会")
        };
        break;
     case 4:
        work = ^{
            NSLog(@"写代码");
            NSLog(@"开会")
            NSLog(@"测试")
        };
        break;
    default:
    //不是day1-4时调用
    work = ^{
        NSLog(@"周末不加班");
    }
       break;
    }
    //***这里很重要**
    //上面已经做完赋值操作了,此时,调用block
    //此时正是体现了,block可以当做参数传递这一原理。
    myWork(work);
}

 调用
int main(int argc,const cahr * argv[]){
    @autoreleasepool {
       for(int i=0;i<7 xss=removed xss=removed xss=removed>
@interface EditViewController : UIViewController
@property(nonatomic,copy)void (^completion)(NSString *text);
@end

在.m文件中执行块代码
在这段代码中,需要将用户输入的文本属性传到上个控制器中。
//.m文件
@interface EditViewController ()
@property (weak, nonatomic) IBOutlet UITextField *nameText;
@end
@implementation EditViewController
- (IBAction)save:(id)sender {
    //block预先准备好的代码
    self.completion(self.nameText.text);
    [self.navigationController popViewControllerAnimated:YES];
}
@end

如此,被执行的这边已经完成,
第一个页面的准备工作
传值工作一般在prepareForSegue方法里面操作。在这个方法中,先获取到下一个页面的控制器。在它的block的值传给第一个界面的标签保存。

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    EditViewController *editVC = segue.destinationViewController;
    editVC.completion = ^(NSString *str){
        self.nameLabel.text = str;
    };
}

总结
调用方:准备块代码。类似于代理方式协议方法的实现,不同的是块代码都在一起并没有单独的实现一个方法。
被调用方: 执行块代码。前提是有要执行的代码,也就是在.h文件中定义一个块代码的属性,接着在需要的时候执行即可。
结果

6、Block常见面试题

1、下面这段代码输出为多少?
void demoBlock() {
int x = 10;   //外部变量x
void(^myBlock)() = ^ {
NSLog(@”%d”,x);
};
      x = 20;   //在外部修改x的值
      myBlock();
}

答案:输出为10。在定义block的时候,如果引用了外部变量,会对外部变量进行拷贝。记录住在block时变量的数值。如果之后在外部修改变量的值也不会影响block内部的数值变化。

 2、如果在block里面修改外部变量的值,会报错吗?
void demoBlock() {
int x = 10;   //外部变量x
void(^myBlock)() = ^ {
x = 80
NSLog(@”%d”,x);
};
      x = 20;   //在外部修改x的值
      myBlock();
}

答案:在默认情况下,不允许block内部修改外部变量的数值。因为拷贝之后它与原数值指向了不同的地址。若是能改变则会破坏代码的可读性。

3、需求千变万化。若我非要将x的值改成80又当如何?为什么?
void demoBlock() {    
  __block int x = 10
int x = 10;   //外部变量x
void(^myBlock)() = ^ {
};
        myBlock();
        NSLog(@”%d”,x);
     };

答案:如果要在block中,修改外部变量的值,需要用__block修饰符号。使用了__block,说明函数不关心具体数值的具体变化。
在block中,如果引用了外部使用的__block变量,block定义之后,外部变量的指针地址同样会变成堆区的地址。之所以可以修改是因为它们的指针指向的地址是相同的。

4、下面是一段能正确运行的代码。为啥定义成mutableString能在block内部对外部变量进行修改了?    
void demoBlock() {
    NSMutableString *strM = [NSMutableString stringWithString:@”张三”];
  void(^myBlock)() = ^{
[strM setString:@”lisa”];
};
    myBlock();
    NSLog(@”%@”,strM);
}
答案:在block中引用外部变量要拷贝一份到堆中。
这个时候拷贝到堆中的地址与在栈中的地址是一样的。因此,它指向zhangsan
在setString的时候,将指针指向的地址的值进行修改。

但是如果用stringWithString方法就会报错,因为这相当于又开辟了一个新的空间。在堆中将strM的地址指向了新的空间。
收藏
0
sina weixin mail 回到顶部