IOS开发 block(代码块)基本使用

1. block基本概念:

(开篇废话)
Block是C级别的语法和运行时特性。Block比较类似C函数,但是Block比之C函数,其灵活性体现在栈内存、堆内存的引用。

Block是苹果推荐的类型,效率高,可以帮助我们组织独立的代码段,并提高复用性和可读性。主要是用来在运行中封装代码和保存代码用的。

Block可以在任何时候被执行。

和c语言的比较:

1、可以保存代码。
2、有返回值。
3、有参数
4、调用方式一样

(函数和函数是同级的关系,函数里面不能定义行数,但是block可以定义在程序的任何地方,只要遵循一条原则:代码是从上到下执行的,先定义后使用)

最简单地理解:block就是一个用来保存代码的变量,可以在你需要的使用的时候通过block 来使用你保存的代码,通常用来做并发任务、遍历、以及回调。

格式说明:

(返回类型)(^块名称)(参数类型列表) = ^(形参列表) {代码实现};
如果没有参数,等号后面参数列表的()可以省略

2. block 在开发或者系统架构中的使用

从 xcode 4.0 开始,系统类库中的函数越来越多的开始使用 block 作为参数,以下是在系统函数中使用代码块的部分情况
    a. 遍历数组和字典
    b. 排序
    c. 视图动画
    d. 结束回调
    e. 错误处理
    f. 多线程等

3. 必须了解的东西

废话说完后来点重点:
a. block(代码块)
   是 oc中的一种数据类型,可以被当做参数传递,可以有返回值,是一个能工作的代码单元,可以在任何需要的时候被执行,就像调用函数一样调用。
   在 ios 开发中广泛使用。

b. ^ 是 block 的特有的标记。
c. block   熟练了解block 的定义(块代码的定义),记得实现代码包含在 {} 之间。
d. block 是以内联 inline 函数的方式被定义使用。
e. 本质上是轻量级的匿名函数。

c. 块代码的使用注意点
    i. 默认情况下,不允许在块代码内部修改外部的变量的数值
    ii. __block,让外部的变量能够在block中修改。
    iii. 循环引用的问题  __weak (ios5.0以下的版本使用__unsafe_unretained(废话))
    iv. 默认情况下,block 外部的变量,在 block 中是只读的。
    v. 块代码与代理的区别

4、block的定义

1、block定义和指向函数的指针的对比

定义除了一个符号发生了改变基本是一样的:
这里写图片描述

指向函数的指针和block定义的代码对比:

#import <Foundation/Foundation.h>

// 测试函数
void test(){
    NSLog(@"%s",__func__);
}
/*
 对于指针不理解的:函数名就是指向函数的指针
 */
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 这个是定义了一个函数的指针并赋值
        void (*Mytest)() = test;

        // 调用函数
        Mytest();

        // 定义一个没有返回值的block
        void(^Myblock)() = ^{
            NSLog(@"这个是Myblock");
        };

        // 调用block
        Myblock();

        /*
         void (*Mytest)();
         void(^Myblock)();

         1、指向函数的指针和block的定义的对比,基本只有一个符号的区别。
         2、使用函数的指针和block的使用基本是一样的。需要调用才会执行。

         指向函数的指针是通过函数名的指向的地址调用函数。
         block是直接执行一段保存的代码。
         */
    }
    return 0;
}
如果要纠结啥子有返回值没有返回值有参数没有参数的block和指向函数的指针的对比那就自己去玩去。

打印结果:
2015-04-22 23:03:18.965 block[4274:1360943] test
2015-04-22 23:03:18.966 block[4274:1360943] 这个是Myblock

2、block定义—— 没有参数没有返回值的block的定义

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定义block ————> 没有参数没有返回值的
        /*
         定义的时候,将block当成数据类型

         特点:
         1、类型比函数多了一个 ^
         2、设置数值,有一个^ ,内容是{}括起来的一段代码

         最简单的方式:不带返回值不带参数。
         void (^Myblock)() = ^ {
            // 要保存的代码实现;
         };
         */

        // 定义一个block 并保存一段代码
        void (^Myblock)() = ^{
            NSLog(@"Myblock");   // 这个是保存的代码
        };  // 这个; 号是不能少的

        // 调用block
        Myblock(); // 像调用函数一样调用block

    }
    return 0;
}

打印结果:
2015-04-22 23:22:04.593 block[4306:1412796] Myblock

3、block定义—— 有参数没有返回值的block的定义

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定义block ————> 有参数没有返回值的
        void (^Myblock)(int) = ^(int num){  // 当有参数的时候 ^(int num){ 中间的()是不能少的
            NSLog(@"Myblock,传入的参数是:%d",num );
        };

        // 调用block
        Myblock(10);
    }
    return 0;
}

打印的结果:
2015-04-22 23:28:48.488 block[4317:1439967] Myblock,传入的参数是:10

4、block定义—— 有参数有返回值的block的定义

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定义block ————> 有参数有返回值的
        int (^Myblock)(int) = ^(int num){
            NSLog(@"Myblock,传入的参数是:%d",num );
            return num; //当定义的block 是有返回值的时候一定要返回要不就会报错
        };

        // 调用block
        Myblock(10);  // block有返回值并不一定要接收
        NSLog(@"%d", Myblock(20));
    }
    return 0;
}

打印的结果:
2015-04-22 23:34:34.298 block[4354:1462221] Myblock,传入的参数是:10
2015-04-22 23:34:34.301 block[4354:1462221] Myblock,传入的参数是:20
2015-04-22 23:34:34.301 block[4354:1462221] 20

5、block定义—— 有参数有返回值的block的定义可能看见的还有一种写法

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定义block ————> 有参数有返回值的
        int (^Myblock)(int,int) = ^int(int num1 ,int num2){
            NSLog(@"Myblock,传入的参数num1是:%d num2是%d",num1,num2);
            return num1 + num2; //当定义的block 是有返回值的时候一定要返回要不就会报错
        };

        // 调用block
        Myblock(10,20);  // block有返回值并不一定要接收
        NSLog(@"%d", Myblock(20,30));
    }
    return 0;
}

 ^int( 这个中间的int时返回值类型可以省略不写

打印的结果:
2015-04-22 23:43:48.958 block[4378:1501255] Myblock,传入的参数num1是:10 num2是20
2015-04-22 23:43:48.959 block[4378:1501255] Myblock,传入的参数num1是:20 num2是30
2015-04-22 23:43:48.959 block[4378:1501255] 50

其他的什么数据类型参数和返回值的排列组合就不一一列之举,想玩的自己去试试。

6、block的秘籍——block的书写是我们最蛋疼的事,一招解决所有

使用inlineblock这个速记符号可以快速的敲出block的基本结构
这里写图片描述

注意:block的书写一定要熟记。
inlineblock这个速记符号只能用来辅助记忆。
(如果你不知道填空大话,我也是醉了)

5、关于block几个疑惑和容易出错的问题

1、block在内存中的位置

栈里面的东西是不需要我们程序员管理的,内存中唯一一个需要程序员管理的是堆,我们在写代码的时候new出来的东西都在堆中。
block默认情况下是在栈中的,是不需要我们程序员管理的。如果多block进行了一次copy操作,就会将block转移到堆中。

注意点:
如果block 在栈里,那么block中用到了外界的对象,不用我么管理。
但是如果block在堆中,那么block中如果用到外界对象,会对对象进行一次retain操作,也就是会进行强引用。
如果想让堆中的block不对使用到的外界对象进行retain,那么就只需要在外界对象的前面加__block.

2、关于block引用外部变量操作的问题

block 使用,如果引用了外部变量,就会对外部变量做一个copy的操作。记录住定义block时候的值。如果后续再修改外部变量的值,不会影响block内部的数值的变化!

外部变量本来是在栈区中,block引用的那一刻,就将外部变量copy 到堆中了,block里面使用的时copy 后堆中的变量的值。
所有在block 调用之前修改外部变量的值,不会影响block里面值的原因。

如果要验证:可以通过打印地址值的方式来验证,栈区是高位地址值,相对于栈,堆在低位地址。
通过打印地址发现,block里面的变量的地址值比block外面的地址值要小很多。
示例程序:
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int x = 10;
        void (^Myblock)() = ^{
            NSLog(@"%d",x); // 引用外部变量 (已经copy,记录了外部变量的值)
        };
        x = 20;   // 修改外部变量的值
        Myblock();   
    }
    return 0;
}

打印结果:
2015-04-23 00:37:15.045 block[4470:1683462] 10

3、关于在block内部修改外部变量的值的问题

在默认的情况下,是不允许在block内部修改外部变量的值。
原因是:会破坏代码的可读性,不易于维护。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool { 
        int x = 10;
        void (^Myblock)() = ^{
            x = 80;  // 这样是不能修改的,这样写直接报错
        };  
       Myblock();
    }
    return 0;
}

如果我们一定要再block的内部修改外部变量的值,必须在外部变量的前面添加 _ _block ,这样才会允许修改。
使用__block,说明不在关系外部变量数值的具体变化。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {  
       __block int x = 10; //  必须在前面加 __block 才可以在block 中修改外部变量的值
        void (^Myblock)() = ^{
            x = 80;
        }; 
        Myblock();
    }
    return 0;
}

为什么使用__block 会达到这个效果(可以通过跟踪地址值发现问题)?
在定义block时,如果引用了外部变量使用了__block的变量。block定义之后,外部变量同样会被copy到堆中,不同的是栈中的那一份没有了,只保留了堆中的那一份。在block 中修改的那一份和 保留的那一份是同一份。所以可以修改。

3、关于在block内部修改外部变量的值 —— 一个蛋疼的问题

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 指针记录的是地址
        NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
        NSLog(@"定义前 %p %p", strM, &strM);

        void (^myBlock)() = ^ {
            // 修改strM指针指向的内容
            [strM setString:@"lisi"];
            NSLog(@"inblock %p %p", strM, &strM);

            // 这句代码是修改strM指针指向的地址
            //        strM = [NSMutableString stringWithString:@"wangwu"];
        };
        NSLog(@"定义后 %p %p", strM, &strM);

        myBlock();
        NSLog(@"%@", strM);

    }
    return 0;
}

打印的结果:
2015-04-23 01:30:30.900 block[4566:1798733] 定义前 0x100406b30 0x7fff5fbff7d8
2015-04-23 01:30:30.901 block[4566:1798733] 定义后 0x100406b30 0x7fff5fbff7d8
2015-04-23 01:30:30.902 block[4566:1798733] inblock 0x100406b30 0x1004074f0
2015-04-23 01:30:30.902 block[4566:1798733] lisi

block copy的知识指针没有copy变量的地址。在block修改的是变量。所以结果会变。

4、关于在block在MRC 中的使用注意(重要的面试题(在ARC开发的时代基本没有主要是为了测试功底))

(小白略过)

#import <Foundation/Foundation.h>

// 块代码可以当作参数,也可以当作返回值
typedef void(^eBlock)();

/**
 问
 -以下代码在ARC中有问题吗?=》没有问题
 -在MRC中有问题吗?存在内存隐患,i和b都是局部变量,出了作用域就会被释放

 解决问题:
 -返回前使用     Block_copy
 -使用后,使用   Block_release

 网上错误答案 return [b copy];

 *********
 Product - Analyze (静态分析)

 从代码结构上分析是否存在缺陷!本身并不会运行程序!并不能够检测到真正的内存泄漏!

 但是:只要是静态分析工具发现的问题,通常都是需要提升的代码!

 静态分析工具,是MRC开发时的利器!提前发现内存隐患!

 另外,在ARC开发时,如果程序要上架之前,建议使用静态分析工具检测一下,通常可以发现一些不注意的警告,有助于提升代码质量!尤其在使用到C语言框架的代码!
 */
eBlock myBlock() {
    int i = 10;
    eBlock b = ^ {
        NSLog(@"hello %d", i);
    };

    // 利用Block_copy将block以及内部的变量拷贝到堆中
    return Block_copy(b);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        eBlock bb = myBlock();
        bb();

        // 释放堆中block
        Block_release(bb);
    }
    return 0;
}

6、block循环引用问题的解决

循环引用的结果就是导致对象无法释放。
我们测试的最好的办法是在对象中重写dellac方法,看这个方法是否被调用。没有调用说明存在循环引用。

在我们的IOS开发当中,什么时候会出现循环引用:
在我们使用block的时候,如果block中使用到了self ,这个时候就需要关心循环引用的问题。

解决方案:__weak typeof(self) weakSelf = self;
 // 示例代码SDWebImage 框架使用的使用用的代码:
   __weak typeof(self) weakSelf = self;
    SDWebImageManager *manage = [SDWebImageManager sharedManager];
    [manage downloadImageWithURL:url options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        if (finished) {
            // 这里是用 weakSelf 代替self 避免循环引用
            [weakSelf setupImageFrame: image];
        }
    }];

注意点:并不是所有的block中使用self都会有循环引用的问题。为了避免循环引用的问题,遇到block中用到self 。我们都这么写,就可以避免循环引用的问题。

7、代理和block在使用的时候我们是怎么选择的。

委托和block是IOS上实现回调的两种机制。Block基本可以代替委托的功能,而且实现起来比较简洁,比较推荐能用block的地方不要用委托。
单就编程过程而言,block对开发者处理逻辑,编程效率,代码阅读都有积极影响。

代理是一种很金典的模式,我们很多人都已经习惯了这种模式,若果对block的回调传值的过程不是很理解的话,建议使用代理。可以达到同样地效果。


一下是还未完善的区域,我会持续的更新的

8、 block 在 IOS 开发的实际运用

在实际的开发中block的使用基本就是传值和回调。这个也是难点和重点。

1、 block 在 IOS 开发的实际运用

3、asdfa

1、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值