Block


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容