Block注意点:
- 区分声明和定义
- 声明没有等号
- 定义有等号
何时用block,何时用delegate
- 异步和回调用block
- 比如AFNetwork,MJRefesh
- 接口很多,用delegate进行解耦。其实用block也行,不过这时候有点麻烦,因为接口很多嘛。
- 比如UITableViewDelegate
- 能用代理一定能用block,能用通知一定能用block,所以block完全可以替换代理和通知,即block是万能的。block虽然简洁,但不好理解,只要用的多了就好理解了。block其实就是个函数
block和delegate相同点
- 具体实现都是在代理类中、初始化Block的类中处理的。具体算法都是在算法类中实现的。
- 比如我想让A控制器显示block的代码块中的内容,那么我必须把初始化block的代码块写在A控制器中。具体如何执行block中的代码,由我自己决定,我把block()这句代码C方法中,那么程序只要执行C方法,就会立刻执行block,A控制就会显示block代码块中的内容
Block核心理解
- 1.block中的代码何时执行?只有理解这个,才理解了block
- 只有执行了类似block();这段代码,才会真正调用block,才会执行block中保存的代码。
- 2.block中保存的代码在哪里?
- 只要见到{代码块;};或者(参数){代码块;}; 这两行种形式。保存的代码块就存在{}里面
- 1+2的结合你就完全理解了何时执行block,执行哪里的block。
block中的实参和形参
- 实参: 调用block方法时,方法中的参数就是实参,例如block(10,20)。
- 形参: 执行block中的代码块时,^后面括号中的内容就是形参,例如 ^(int a,int b){代码块}
- 和C语言的形参、实参差不多。
block的应用场景
- 动画
- 多线程
- 网络请求回调
- 集合遍历
- 第三方框架多数都使用的是block
block是地址传递的理解
- 通过block();的形式调用block,本质是调用block中保存的代码。类似c语言函数,例如 eat(),就会调用eat函数;
- block是指向结构体的指针,也就是说block存的是地址,block是一种特殊的数据类型。注意数据类型的理解。
- block是地址传递不是值传递,所以block中{}的内容,内容中的变量如果是 全局变量/用__block修饰/static修饰,满足三种情况之一,那么外界修改变量的值,就会修改block中对应的变量,因为这三种情况是地址传值。内容中的变量如果是用普通的局部变量修饰(例如在方法内容仅仅用int修饰),那么外界修改变量对block中的内容无影响,因为是值传递。
- 总结:地址传递可以修改以block中{}的内容,值传递不可以修改block中{}的内容
情况1:num是全局变量
void test1();
int main(int argc, const char * argv[])
{
test1();
return 0;
}
int num = 10;// 全局变量(在方法外部定义的num)
void test1()
{
void (^block)() = ^{// 定义并初始化block
// block内部能够一直引用全局变量
NSLog(@"----num=%d", num); // 20
};
num = 20;
block();// 调用Block
}
情况2:用__block修饰age
void test2()
{
__block int age = 10;
void (^block)() = ^{// 定义并初始化block
// block内部能够一直引用被__block修饰的变量
NSLog(@"----age=%d", age);// 20
};
age = 20;
block();// 调用Block
}
情况3:用static修饰的局部变量height
void test3()
{
static int height = 10;
void (^block)() = ^{ // 定义并初始化block
// block内部能够一直引用被static修饰的变量
NSLog(@"----height=%d", age);// 20
};
height = 20;
block();// 调用Block
}
情况4:用int修饰普通的局部变量weight
void test2()
{
int weight = 10;
void (^block)() = ^{// 定义并初始化block
// 普通的局部变量,block内部只会引用它初始的值(block定义那一刻),不能跟踪它的改变
NSLog(@"----weight=%d", weight);// 10
};
age = 20;
block();// 调用Block
}
block相关的内存管理( Block_copy(block变量名) 、 __block )
- 1.block是存储在堆中还是栈中?
- 默认情况下block存储在栈中,block中访问了外界的对象p, 不会对对象进行retain操作
- 如果对block进行一个copy操作,即Block_copy(block变量名), block会转移到堆中
- block存储在堆中, block中访问了外界的对象p, 那么会对外界的对象进行一次retain(计数器加1)
- 例:对block进行一个copy操作,block会转移到堆中,此时,block中访问了外界的对象p,会retain
Person *p = [[Person alloc] init];// +1
void (^myBlock)() = ^{// 定义并初始化block
NSLog(@"block retainCount = %lu", [p retainCount]);// 计数器会+1
};
Block_copy(myBlock);// 对block进行一次copy操作,那么block就会转移到堆中
myBlock();// 调用block
[p release]; // 2-release一次= 1,所以会造成内存泄露
- 2.如果在block中访问了外界的对象p, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain。例如:
__block Person *p = [[Person alloc] init];// +1
void (^myBlock)() = ^{// 定义并初始化block
NSLog(@"block retainCount = %lu", [p retainCount]);// p让__block修饰了,所以计数器不会+1
};
Block_copy(myBlock);// 对block进行一次copy操作,那么block就会转移到堆中
myBlock();// 调用block
[p release]; // 1-release一次= 0,所以不会造成内存泄露
oc代码如何转成c/c++的运行时代码
步骤1:创建一个命令行项目
步骤2:将代码写在main.m文件中
步骤3:打开终端
步骤4:cd 到main.m的上一级目录
步骤5:clang -rewrite-objc main.m
步骤6:open main.cpp
block声明
// 声明:无返回值(^block变量名)(形参)
void(^block)();
void:block中保存的代码没有返回值
(^block):block是一个变量,这个变量里保存着一段代码
():block保存的代码没有形参
block定义的同时并初始化block
- 等号左边是block的定义。等号右边是初始化block,等号左边+等号右边一起出现的形式就是block定义的同事并初始化block。当然也可以分开写,即先定义block,再初始化block
- 方式一:无返回值((^block变量名)(形参)= ^(形参)
// 定义并初始化block
void(^block1)() = ^(){ //不会执行代码块中的内容,只有执行block1才会调用block1保存的代码块
// 代码块
NSLog(@"调用block1");
};
// 调用Block1,这个Block1就是block变量名,然后去执行下Block1保存代码,就是等号右侧的代码^(){代码块}
block1();
- 方式二:无返回值+block2的定义中没有参数(形参),那么等号右侧的()可以省略
//类型:void(^)()
// 定义并初始化block
void(^block2)() = ^{//不会执行代码块中的内容,只有执行block2才会调用block2保存的代码块
// 代码块
};
// 调用Block2,这个Block2就是block变量名,然后去执行下Block2保存代码,就是等号右侧的代码^{代码块}
block2();
- 方式三:有返回值+block3的定义中没有参数(形参)
// 类型:int(^)()
// 定义并初始化block
int(^block3)() = ^{//不会执行代码块中的内容,只有执行block2才会调用block2保存的代码块
// 代码块
return 1;
};
// 调用Block3,这个Block3就是block变量名,然后去执行下Block3保存代码,就是等号右侧的代码^int{代码块}
block3();
- 方式四:有返回值+block4的定义中有两个参数(形参)
// 类型:int(^)(int,double)
// 定义并初始化block
int(^block4)(int,double) = ^(int age ,double height){//不会执行代码块中的内容,只有执行block2才会调用block2保存的代码块
// 代码块
return 2;
};
// 调用Block4,这个Block4就是block变量名,然后去执行下Block4保存代码,就是等号右侧的代码^(int age ,double height){// 代码块 };
block4();
block作用:
- 作用:保存一段代码
- 类型:int(^)()
Block和typedef具体用法
- Block方式一
// 定义block+初始化block
int main(int argc, const char * argv[]) {
int (^sumBlock)(int , int );// 定义block
sumBlock = ^(int value1, int value2){// 初始化block
return value1 + value2;// 代码块
};
NSLog(@"sum = %i", sumBlock(20, 10));// 30 调用block
int (^minusBlock)(int , int); // 定义block
minusBlock = ^(int value1, int value2){ // 初始化block
return value1 - value2;// 代码块
};
NSLog(@"minus = %i", minusBlock(20, 10));//10 调用block
return 0;
}
- Block方式二
// 定义block的同时并初始化block
类比 int a; // 定义a
a = 10;// 把a初始化为10
int a = 10;// 定义a的同时,并把a初始化为10
int main(int argc, const char * argv[]) {
int (^sumBlock)(int , int ) = ^(int value1, int value2){// 定义block的同时并初始化block
return value1 + value2;// 代码块
};
NSLog(@"sum = %i", sumBlock(20, 10));// 30 调用block
int (^minusBlock)(int , int) = ^(int value1, int value2){// 定义block的同时并初始化block
return value1 - value2;// 代码块
};
NSLog(@"minus = %i", minusBlock(20, 10));// 10 调用block
return 0;
}
- block+typedef的方式
- 注意: 利用typedef给block起别名,block变量的名称就是别名,也就是类型的名称,以后可以用calculteBlock这个类型去定义并初始化一个block
typedef int (^calculteBlock)(int , int);
int main(int argc, const char * argv[]) {
// 由于用了typedef,所以此句代码的含义:用calculteBlock类型定义sumBlock,并初始化sumBlock
calculteBlock sumBlock = ^(int value1, int value2){
return value1 + value2;// 代码块
};
NSLog(@"sum = %i", sumBlock(20, 10));// 30 调用block
// 由于用了typedef,所以此句代码的含义:用calculteBlock类型定义sumBlock,并初始化sumBlock
calculteBlock minusBlock = ^(int value1, int value2){
return value1 - value2;// 代码块
};
NSLog(@"minus = %i", minusBlock(20, 10));// 10 调用block
return 0;
}
block实战演练
#import <Foundation/Foundation.h>
// 如果外界没有调用goToWork方法,那么goToWork的参数仅仅是定义一个block
// 如果外界调用了goToWork方法,那么goToWork的参数的含义是:定义并初始化block。
// 因为外界调用goToWork方法,必定传进来一个"初始化block"的代码
// 我们只需在goToWork方法中调用block();就可以执行block中的代码,从而输出block中的内容
void goToWork(void (^workBlock)())
{
// 参数为void (^workBlock)(),那么外界将初始化的代码块作为形参赋值给这个参数时,本质就会变成如下代码
/*
void (^workBlock)() = ^{// 定义并初始化block。 所以下面的workBlock();就会执行block中的内容
NSLog(@"写代码喽");
});
*/
// 总结:定义block 作为goToWork方法的参数时,外界把代码块传递给这个参数时,那么就变成了定义并初始化block,
NSLog(@"起床");
NSLog(@"驾车去上班");
// 不一样
workBlock(); // 执行block,就是执行block中的代码
NSLog(@"驾车回家");
NSLog(@"吃晚饭");
NSLog(@"睡觉");
}
int main(int argc, const char * argv[]) {
goToWork(^{
NSLog(@"认识新同事");
});
return 0;
}
创建block的快捷方式:
// 快捷方式:inline
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
- 若parameterTypes有类型,那么parameters必须是参数类型+值的形式,否则提示错误
//例如:
void(^block4)(int) = ^(int a) {//正确
};
void(^block4)(int) = ^( a ) {//错误 a前面必须加上返回值类型
};
void(^block4)() = ^( ) {//正确 没有参数类型,那么参数也不需要参数
};
循环引用的问题[详细看JS_OC_JavaScriptCore这个demo]
-
对象未变为弱指针,控制台没有打印-[XXX dealloc],所以控制器没有被释放,造成了循环引用
对象未变为弱指针.gif -
对象变为了弱指针,控制台打印-[XXX dealloc],所以控制器被释放,解决了循环引用
对象变为了弱指针.gif