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

来源:互联网 发布:获奖证书制作软件 编辑:程序博客网 时间:2024/05/22 03:31

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] test2015-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,传入的参数是:102015-04-22 23:34:34.301 block[4354:1462221] Myblock,传入的参数是:202015-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是202015-04-22 23:43:48.959 block[4378:1501255] Myblock,传入的参数num1是:20 num2是302015-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 0x7fff5fbff7d82015-04-23 01:30:30.901 block[4566:1798733] 定义后 0x100406b30 0x7fff5fbff7d82015-04-23 01:30:30.902 block[4566:1798733] inblock 0x100406b30 0x1004074f02015-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、

0 0
原创粉丝点击