您的位置:首页 > 移动开发 > IOS开发

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

2015-04-22 22:45 253 查看

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、

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: