block 专题--基础

#######以前只是看博客园,这么长时间了,也要有点干货分享给大家,不能只索不予,希望对大家有点帮助########

一.block的基本概念

/**
 1. block的概念和特点:
     1> block是 C语言中的一种数据类型
     2> block是一个提前准备好的能工作的代码块,可以在任何需要的时候被执行。
     3> 本质上是轻量级的匿名函数,可以作为其他函数的参数或返回值。
     4> block本身可能有一个参数列表,也可能有一个返回值。
     5> 可以将block赋给一个变量,并在需要的时候调用,就像调用一个普通函数一样。
 */

二.定义block

1、无参无返回值的block

(1) 格式:
     void (^block的名称)() = ^ { 代码实现; };
(2) 定义block    
       // 定义block
        void(^myBlock)() = ^ {
            NSLog(@"hello");
        };
       // 定义函数
        void demo() {
        }
(3) 定义block有以下特点:
  1. 类型比函数多了一个^ 和 小括号。
  2. 设置数值,需要有一个等号, 等号右边有一个 ^, 内容是{}括起来的一段代码。
  3. 如果block没有参数,等号右边的小括号可以省略。
  4. block定义完成之后,只是准备好的一个代码块,不能自己执行,这一点跟函数类似,函数必须被调用才会执行。
(4) 注意
        定义block的时候,把它当成数据类型。
        执行block的时候,把它当成函数。

2、带参无返回值的block

(1) 格式
     void (^block的名称)(参数类型) = ^(参数列表) { 代码实现; };  
(2) 定义block
   // 定义带参数的block
   void (^sumBlock)(int, int) = ^(int x, int y) {
       NSLog(@"%d", x + y);
   };
   // 执行block
   sumBlock(1, 5);
 
注意: 等号右边必须设置参数的名称。
 
3、带参带返回值的block
(1) 格式
返回值类型 (^block的名称)(参数类型) = ^返回值类型(参数列表) { 代码实现; };
(2) 定义block
     int (^sumBlock2)(int, int) = ^ int (int a, int b) {
            return a + b;
     };
     int sum = sumBlock2(10, 20);
NSLog(@"%d", sum);

4、block定义的速记符号

速记符号:inlineBlock 能够快速敲出一个block的基本结构
returnType (^blockName)(parameterTypes) = ^(parameters) {
            statements;
};
注意:可以利用inlineBlock辅助记忆,但是不要依赖速记符号,必须能够手写定义block。

三.block常见面试题

1、第一道:block引用外部变量

void blockDemo1() {
    int x = 10;
    void (^myBlock)() = ^ {
        NSLog(@"%d", x);
    };
   
    x = 20;
   
    myBlock();
}
(1) 提问: 输出是多少?
(2) 答案: 输出是10.
(3) 原因: 定义block的时候,如果引用了外部变量,会对外部变量做一个copy,记录住定义block时变量的数值。
(4) 如果后续在block外部修改x的值,不会影响block内部的数值变化!
为什么?
因为我们定义的变量x是保存在栈区的,而block引用外部变量,对其做copy操作,会将外部变量copy到堆区。这样的话,在block外部修改的只是栈区的x,当然不会影响到block内部的x。
(5) 验证:
void blockDemo1() {
    int x = 10;
    NSLog(@"定义前: %p", &x);              // 栈区
   
    void (^myBlock)() = ^ {
        NSLog(@"%d", x);
        NSLog(@"in block: %p", &x);       // 堆中的地址
    };
   
    NSLog(@"定义后: %p", &x);              // 栈区
    x = 20;
   
    myBlock();
}
 
执行结果如下:
 

2、第二道:block内部修改外部变量的值

仍然是第一道题,不过如果就想在block内部修改x,该怎么做?
void blockDemo2() {
    int x = 10;
    NSLog(@"定义前: %p", &x);             
   
void (^myBlock)() = ^ {
   x = 80;
        NSLog(@"%d", x);
        NSLog(@"in block: %p", &x);      
    };
   
    NSLog(@"定义后: %p", &x);             
    x = 20;
   
    myBlock();
}
(1) 提问: 上面的代码有没有问题?若有问题,该怎么更正?更正后的输出结果是多少?
(2) 答案:
/**
 (1) 如果在block内部直接修改x,会报错。因为默认情况下,不允许block内部修改外部变量的值。
 (2) 如果想在block内部修改外部变量的值,需要使用__block修饰外部变量。
 */
(3) 更正:
 
void blockDemo2() {
    // 使用 __block 说明不再关心x数值的变化
    // 定义block的时候,如果引用了外部使用__block修饰的变量,block定义之后,外部变量的地址同样会变成堆中的地址
    __block int x = 10;                   // 在类型前面添加 __block
    NSLog(@"定义前: %p", &x);              // 栈区
   
    void (^myBlock)() = ^ {
        x = 80;
        NSLog(@"%d", x);
        NSLog(@"in block: %p", &x);       // 堆中的地址
    };
   
    NSLog(@"定义后: %p", &x);              // 堆中的地址
    x = 20;
   
    myBlock();
}
3> 输出结果是 80。
执行结果如下:   
 
注意:
(1) 使用 __block 说明不再关心x数值的变化
(2) 定义block的时候,如果引用了外部使用__block修饰的变量,block定义之后,外部变量的地址同样会变成堆中的地址
也就是说:只要使用__block修饰了变量x,那么在block定义完成之后,再去修改x,修改的都是堆区的x。

3、第三道

void blockDemo3() {
    // 指针记录的是地址
    NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
 
    void (^myBlock)() = ^ {
        [strM setString:@"lisi"];
    };
    myBlock();
    NSLog(@"%@", strM);
}
  1. 提问: 以上代码有没有问题?
  2. 答案: 没有问题
  3. 分析: 如下 
void blockDemo3() {
    // 指针记录的是地址
    NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
    NSLog(@"定义前: %p %p", strM, &strM);
   
    void (^myBlock)() = ^ {
        [strM setString:@"lisi"];
        NSLog(@"in block: %p %p", strM, &strM);
//        strM = [NSMutableString stringWithString:@"lisi"];
    };
   
    NSLog(@"定义后: %p %p", strM, &strM);
   
    myBlock();
    NSLog(@"%@", strM);
}
执行结果如下:
 
1>.首选要明确以下关系:
 
 
 
 
strM 本质是一个指针变量,表示的是字符串对象的地址 0x1001038d0
&strM 表示的是指针strM在栈区的地址
block内部默认不能修改外部变量的值。在这道题中,外部变量是strM,它的值是0x1001038d0。
当我们在block内部使用 [strM setString:@”lisi”];这句代码,首先会将strM拷贝到堆区,这个时候strM的值并没有改变,改变是&strM(即strM的地址,因为strM由栈区来到了堆区)然后做 setString的操作,只是在修改strM指向的字符串对象的值,也没有修改strM的值。所以,如果block内部是[strM setString:@”lisi”];程序是没有问题的。
        但是,如果block内部是: strM = [NSMutableString stringWithString:@”lisi”];相当于重新创建了一个字符串对象,让strM指向它,这样的话,很显然strM的指向发生了改变,那么它保存的地址也就发生了改变,即外部变量的值被改变了。因此会报错。
 
 
Block的内存管理:
 
 
 
 

 
循环遍历   
   我们常用的循环遍历有三种:  
for循环
for-in循环
 
 
block遍历循环:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 先有一个数组
    NSArray *array = @[@"1", @"2", @"3", @"4", @"5",];
   
    // enumerateObjectsUsingBlock: 是 NSArray 的方法  :用于block的枚举对象
    // obj: 数组元素
    // idx: 数组下标
    // *stop: 是否停止遍历
   
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@", obj);
       
        if (idx == 2) {
            *stop = YES;
        }
    }];
}
 
控制台输出结果:
2016-01-29 18:44:47.312 block 遍历[5011:700719] 1
2016-01-29 18:44:47.312 block
遍历[5011:700719] 2
2016-01-29 18:44:47.312 block 遍历[5011:700719] 3 
posted @ 2016-05-02 21:25  行者江湖远  阅读(213)  评论(0编辑  收藏  举报