精华内容
参与话题
问答
  • Block

    千次阅读 2013-08-02 15:51:14
    Button.h: #import @class Button; typedef void (^ButtonBlock) (Button *); @interface Button : NSObject ...@property(nonatomic,assign) ButtonBlock block; -(void)click; @end Button.m: #import "Butto

    Button.h:

    #import <Foundation/Foundation.h>
    @class Button;
    
    typedef void (^ButtonBlock) (Button *);
    
    @interface Button : NSObject
    
    @property(nonatomic,assign) ButtonBlock block;
    
    -(void)click;
    @end

    Button.m:

    #import "Button.h"
    
    @implementation Button
    -(void)click{
        _block(self);
    }
    @end

    main:

    #import <Foundation/Foundation.h>
    #import "Button.h"
    
    typedef int (^MySum) (int,int);
    
    //定义一个block方法,block相当于一个函数方法,也由参数和返回值
    void test(){
        //定义一个Block,^是block的标志
        //这个block返回类型是int类型,接受两个int类型的参数
        int(^Sum)(int,int)=^(int a,int b){
            return a+b;
        };
    
        //调用block方法
        int a=Sum(10,11);
        NSLog(@"%i",a);
    }
    
    void test2(){
        int c=15;
        //如果外面的变量已经用__block进行了修饰,就可以在block进行修改
        __block int d=16;
        //由于宏定义,这里MySum是一个block类型,sum是一个变量名
        MySum sum=^(int a,int b){
            //block可以访问外面定义的变量
            NSLog(@"我是外部变量C=%i",c);
            //如果不加__block修饰,默认是不好修改外面的变量的
            d=17;
            NSLog(@"我是外部变量d的修改值=%i",d);
            return a+b;
        };
        NSLog(@"%i",sum(10,10));
    }
    
    int main(int argc, const char * argv[])
    {
    
        @autoreleasepool {
            
            Button *btn=[[[Button alloc] init] autorelease];
            test();
            test2();
            btn.block=^(Button *btn){
                NSLog(@"按钮%@被点击了",btn);
            };
            [btn click];
        }
        return 0;
    }
    
    结果:

    2013-08-02 15:50:25.568 Block[1355:303] 21

    2013-08-02 15:50:25.577 Block[1355:303] 我是外部变量C=15

    2013-08-02 15:50:25.578 Block[1355:303] 我是外部变量d的修改值=17

    2013-08-02 15:50:25.578 Block[1355:303] 20

    2013-08-02 15:50:25.579 Block[1355:303] 按钮<Button: 0x100109950>被点击了


    展开全文
  • block

    千次阅读 2016-05-23 21:30:15
    一、Blocks概要Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数。 顾名思义,所谓的匿名函数就是不带有名称的函数。c语言的标准不允许存在这样的函数。例如:int func (int count);...

    一、Blocks概要

    Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数。
    顾名思义,所谓的匿名函数就是不带有名称的函数。c语言的标准不允许存在这样的函数。例如:

    int func (int count);
    int resule = func(10);

    如果想使用函数指针来代替直接调用函数,那么似乎不用知道函数名也能够使用该函数

    int func (int count);
    int (*funcptr)(int) = &func;
    int result = (*funcptr)(10);

    C语言函数中可能使用的变量:

    • 自动变量(局部变量);
    • 函数的参数;
    • 静态变量(静态局部变量);
    • 静态全局变量;
    • 全局变量;

    二、Blocks模式

    1、Block语法

    • (1)、^ 返回值类型 参数列表 表达式;

      ^void (int i){ printf("%d", i);}
      
    • (2)、^ 参数列表 表达式;

      ^(int i){printf("%d", i);}
      
    • (3)、^ 表达式;

      ^{printf("123");}
      

    当你省略返回值类型的时候,你的表达式里return返回什么类型,那么你的返回值类型就是什么。
    当你不适用参数的时候,(void) 参数列表可以省略。

    int (^myBlock) (int) = ^(int num){
            return num * num;
    };

    说明:

    • (1)、int:返回值类型,如果没有返回值则为void
    • (2)、(^myBlock):块定义需要有一个^标记,myBlock是块名称
    • (3)、(int):参数类型列表,如果没有参数则为void
    • (4)、^(int num):以^开头的参数列表,如果没有则为void,也可以省略
    • (5)、{}:block体(相当于函数体)

    2、Block类型变量

    (1)、使用block的时候,我们可以声明block变量,他同c中的函数指针:

    int f(int a) {  
         return a;  
    }  
    int (*fa)(int) = &f;  

    (2)、使用Block语法将Block赋值为Block类型变量;

    int (^blk)(int) = ^(int a){return a;};

    (3)、由^开始的Block语法生成的Block被赋值给变量blk中。因为与通常的变量相同,所有当然也可以由Block类型变量向Block类型变量赋值;

    int (^blk1)(int) = blk;
    int (^blk2)(int);
    blk2 = blk1;

    (4)、在函数参数中使用Block类型的变量可以向函数传递Block

    void func (int (^blk) (int)){
        NSLog(@"%d",blk(1));
    }

    (5)、在函数返回值中指定Block类型,可以将Block作为函数的返回值返回

    int (^funk())(int){
        return ^(int count){return count + 1;};
    }

    由此可见,在函数参数和返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef来解决该问题:

    typedef int (^blk_t)(int);

    3、获取自动变量值

    - (void)viewDidLoad {
        [super viewDidLoad];
        int myCount = 1;
        int (^blk)(void) = ^{
            NSLog(@"%d",myCount);
            return myCount;
        };
        myCount ++;
        blk();
        NSLog(@"%d",myCount);
    }

    打印结果如下:

    2016-05-20 15:26:43.499 class_01[42917:1552743] 1
    2016-05-20 15:26:43.500 class_01[42917:1552743] 2

    Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行自动变量的值。

    4、__block说明符

    实际上,自动变量值截获只能保存执行Block语法的瞬间的值。保存后就不能改写该值。若尝试改写截获的自动变量值,会出现编译错误。如下:

    int val = 0;
    void (^blk)(void) = ^{
        val = 1;
    }

    若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符。

    - (void)viewDidLoad {
        [super viewDidLoad];
        __block int myCount = 1;
        int (^blk)(void) = ^{
            NSLog(@"%d",myCount);
            myCount = 4;
            return myCount;
        };
        myCount ++;
        blk();
        NSLog(@"%d",myCount);
    }
    2016-05-20 15:33:30.154 class_01[42969:1556701] 2
    2016-05-20 15:33:30.155 class_01[42969:1556701] 4

    注意:
    使用附有__block说明符的自动变量可在Block中赋值,该变量称为__block变量。

    • (1)、__block是只针对局部变量生效的一种描述变量存储类型的关键字,因此__block类型的变量都是栈变量;
    • (2)、__block类型的变量在其定义的语法范围里,和该范围内的所有block共享存储空间,当block在被复制到heap区域时,同区域内的__block变量占用的内存不会随着退栈而销毁;
    • (3)、出于优化的考虑,栈中的block对象最开始和一般的栈局部变量是相同的,当使用Block_copy对block进行复制时,才被拷贝到heap区域;
    • (4)、__block变量不能是一个可变长数组;

    5、截获的自动变量

    由上述可得如果将值赋值给Block中截获的自动变量,就会产生编译;
    如果截获Objective-C对象,调用变更该对象的方法如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSMutableArray* arr = [NSMutableArray array];
        void (^blk)(void) = ^{
            [arr addObject:@"1"];
        };
        blk();
        NSLog(@"%@",arr);
    }

    打印:

    2016-05-20 16:10:02.051 class_01[43053:1570116] (
        1
    )

    arr是一个指针,指向一个可变长度的数组。在block里面,并没有修改这个指针,而是修改了这个指针指向的数组。换句话说,arr是一个整数,保存的是一块内存区域的地址,在block里,并没有改变这个地址,而是读取出这个地址,然后去操作这块地址控件的内容。这是允许的,因为声明的block的时候实际上是把当时的临时变量又复制了一份,在block里即使修改这些复制的变量,也不影响外面的原始变量,这就是所谓的闭包。但是当变量是一个指针的时候,block里只是复制了一份这个指针,两个指针指向同一个地址。所以在block里面对指针指向内容做的修改,再block外面也一样生效。

    Blocks默认不能修改相同作用域范围内的变量,但是如果这个相同作用域的变量如果使用了__block关键字进行修饰,则可以通过blocks进行修改。

    三、Blocks的实现

    1、Block的实质

    Block是”带有自动变量值的匿名函数”。
    在实际编译时无法转换成我们能够理解的源代码,但是clang(LLVM编译器)具有转换为我们可读源代码的功能。通过”-rewrite-objc”选项就能将含有Block语法的源代码转换为C++的源代码。

    首先进入该源文件的绝对路径;

    clang -rewrite-objc 源代码文件名

    我们先来转换一个简单的block代码:

    int main() {
        void (^blk)(void) = ^{
            printf("Block\n");
        };
        blk();
        return 0;
    }

    生成一个block.cpp的文件

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
            printf("Block\n");
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    
    int main() {
        void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
    }

    可以通过转换后的源代码看到,通过blocks使用的匿名函数,实际上被作为简单的c语言函数来处理。
    在转换后的代码的命名上,是根据block语法所属的函数名和该block语法在该函数出现的顺序值。

    来看block的语法:

    ^{printf();};

    对应的是

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
         printf();
    }

    这里的__cself相当于c++中的this。

    函数参数声明:struct __main_block_impl_0 *__cself
    与c++的this和oc的self相同,参数__cself 是 __main_block_impl_0结构体的指针。

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
    };

    这里是取出构造函数的代码,
    其中impl是__block_impl结构体。

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };

    Desc是指针,是__main_block_desc_0结构体的。

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    }

    下面初始化含有这些结构体的__main_block_impl_0结构体的构造函数;

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }

    _NSConcreteStackBlock用于初始化__block_impl结构体的isa成员。

    main中构造函数是如何调用的:

    void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

    拆分:

    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &tmp;

    这样就容易理解了。 该源代码在栈上生成__main_block_impl_0结构体实例的指针。我们将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量blk;

    注意到生成tmp的函数的参数, 第一个参数是由block语法转换的c语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。
    看一下结构体初始化:
    __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
    };
    这里使用__main_block_impl_0结构体实例的大小进行初始化。

    下面看看栈上的_main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。如果展开__main_block_impl_0结构体的__block_impl结构体,如下:

    struct __main_block_impl_0 {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
        struct __main_block_desc_0* Desc;
    }

    该结构体根据构造函数:

    isa = &_NSConcreteStackBlock;
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;

    2、__block说明符

    Block中所使用的被截获自动变量就如:带有自动变量值的匿名函数。 所说的,仅截获自动变量的值。
    Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。
    如果在Block中视图改变自动变量,将引起编译错误。
    但是这样,我们就不能再block中保存值了。
    解决方案有两种方法:
    (1)、c语言中有一个变量,允许Block改写值。具体如下:

    • 静态变量;
    • 静态全局变量;
    • 全局变量;

    在Block中,访问静态全局变量/全局变量没有什么改变,可以直接使用。
    静态变量中,转换后的函数原本就设置在含有block语法的函数外,所以无法从变量作用域访问。

    (2)、 使用__block说明符
    __block存储域类说明符 __block storage-class-specifier

    c中的存储域类说明符:

    • typedef
    • extern
    • static
    • auto
    • register

    __block说明符类似于static、auto和register,它们用于作为静态变量值设置到那个存储区域中。例如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储再数据区中。

    3、Block存储域

    Block和__block变量的实质就是在栈上的结构体实例。Block转换为Block的结构体型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓的结构体类型的自动变量,即栈上生成的该结构体的实例;

    名称 实质
    Block 栈上Block的结构体实例
    __block变量 栈上__block变量的结构体实例

    其中Block也是oc的对象,该OC的类为:_NSConreteStackBlock。

    类似的类还有:

    • (1)、_NSConcreteGlobalBlock:他与全局变量一样,设置在程序的数据区域(.data区)中;
    • (2)、_NSConcreteStackBlock:它的对象Block设置在栈上;
    • (3)、_NSConcreteMallocBlock:它的对象设置在由malloc函数分配的内存块中(堆);
    设置对象的存储域
    _NSConcreteStackBlock
    _NSConcreteGlobalBlock 程序的数据区域(.data区)
    _NSConcreteMallocBlock

    这里写图片描述

    (1)、_NSConcreteGlobalBlock

    void (^blk)(void) = ^{
        printf("Global Block\n");
    };

    该Block的类为_NSConcreteGlobalBlock类。此Block即该Block用结构体实例设置在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。由此Block用结构体实例的内容不依赖执行时的状态,所以整个程序只需要一个实例。因此将Block用结构体实例设置在于全局变量相同的数据区域中即可。
    只在截获自动变量时,Block用结构体实例截获的值才会根据执行时的状态变化。
    只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
    在以下情况Block为_NSConcreteGlobalBlock类对象:

    • 1、记述全局变量的地方有Block语法时;
    • 2、Block语法的表达式中不使用截获的自动变量时;

    (2)、_NSConcreteStackBlock

    NSArray *testArr = @[@"1", @"2"];
    void (^TestBlock)(void) = ^{
        NSLog(@"testArr :%@", testArr);
    };

    需要截获自动变量的为_NSConcreteStackBlock类对象,换句话说:
    除_NSConcreteGlobalBlock的Block语法生成的Block都为_NSConcreteStackBlock类对象;

    (3)、_NSConcreteMallocBlock

    • 1、Block超出变量作用域可存在的原因

    分配在栈上的Block和__block变量 其所属的变量作用域结束,该Block或者__block变量也会被废弃。
    但是Blocks提供了 将Block和__block变量从栈上复制到堆上的方法来解决这个问题, 这样即使语法记述其作用域结束,堆上的Block也能继续存在。
    如图:
    这里写图片描述
    这里写图片描述
    此时,赋值到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa;
    impl.isa = _NSConcreteMallocBlock;

    • 2、__block变量用结构体成员变量__forwarding存在的原因

    上面的情况下只是Block,而__block变量需要用结构体成员变量__forwarding可以实现 无论__block变量配置在栈上还是堆上时都能够正确地访问__block变量。
    在下面我们会说:在__block变量配置在堆上的状态下,也可以访问栈上的__block变量。此时,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,不管是从栈上的__block变量还是从堆上的__block变量都能正确访问。

    Blocks提供的复制方法,如何从栈上复制到堆上的? ARC有效的时候,编译器可以自动判断。
    来看Block函数:

    typedef int (^blk_t)(int);
    blk_t func(int rate) {
         return ^(int count){return rate*count;};
    }

    此时将会返回配置在栈上的Block的函数。 即 程序执行中,从该函数返回函数调用方时变量作用域结束,因此栈上的Block也被废弃。 虽然有问题,但是ARC的编译如下:

    blk_t func(int rate) {
        //将通过Block语法生成的Block, 即配置在栈上的Block用结构体实例, 赋值给相当于Block类型的变量tmp中,此处的tmp相当于blk_t __strong tmp;
        blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
        //_Block_copy函数   将栈上的Block复制到堆上,复制后,将堆上的地址作为指针赋值给变量tmp,此处的objc_retainBlock相当于_Block_copy
        tmp = objc_retainBlock(tmp);
        //将堆上的Block作为OC对象,  注册到autoreleasepool中,然后返回该对象。
        return objc_autoreleaseReturnValue(tmp);
    }

    将Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码。

    对于配置在堆上的Block以及配置在程序的数据区域上的Block,调用copy方法如下:

    Block的类 副本源的配置存储域 复制效果
    _NSConcreteGlobalBlock 程序的数据区域 什么也不做
    _NSConcreteStackBlock 从栈复制到堆
    _NSConcreteMallocBlock 引用计数增加

    注意:

    • ①、Block_copy与copy等效,Block_release与release等效;
    • ②、对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
    • ③、NSGlobalBlock:retain、copy、release操作都无效;
    • ④、NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[mutableAarry addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
    • ⑤、NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
    • ⑥、尽量不要对Block使用retain操作。

    4、__block变量存储区

    Block从栈复制到堆上时,对__block变量产生的影响

    __block变量的配置存储域 Block从栈复制到堆时的影响
    从栈复制到堆并被Block持有
    被Block持有

    (1)、如果一个Block中使用__block变量,当该Block从栈复制到堆时,使用的所有__block变量也必定配置在栈上。这些__block变量也全部被从栈复制到堆上。 此时Block持有__block变量。 即使在该Block已复制到堆的情况下,复制Block也对所使用的__block变量没有任何影响。
    (2)、在多个Block中使用__block变量时,因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数;
    (3)、如果配置在堆上的Block被弃用,那么它所使用的__block变量也就被释放。

    5、截获对象

    什么时候栈上的Block赋值到堆?

    • 调用Block的copy实例方法时;
    • Block作为函数返回值返回时;
    • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时;
    • 在方法名中有usingBlock的Cocao框架方法或Grand Central Dispatch的API中传递Block时;

    在调用Block的copy的时候,如果Block配置在栈上,那么该Block会从栈复制到堆。 当Block作为函数返回值的时候,将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量的时候,编译器会自动将对象的Block作为参数,并调用_Block_copy函数。这与调用Block的copy实例方法效果相同。在方法中含有usingBlock的Cocoa框架方法或Crand Central Dispatch的API中传递Block时,在该方法或函数内部对传递过来的Block调用Block的copy实例方法或者_Block_copy函数。
    也就是说,虽然从源代码来看,在上面这些情况下栈上的Block被复制到了堆上, 但其实可归结为_Block_copy函数被调用时Block从栈复制到堆。
    相反,释放复制到堆上的Block后,谁都不持有Block而使其被废弃时调用dispose函数, 相当于对象的dealloc实例方法。
    这样,通过使用附有__strong修饰符的自动变量,Block中截获的对象就能超出其变量作用域存在了。

    截获对象和使用__block变量时的不同:
    对象:BLOCK_FIELD_IS_OBJECT
    __block变量:BLOCK_FIELD_IS_BYREF
    通过标志来区分是对象还是__block变量。

    但是与copy函数持有截获的对象,dispose函数释放截获的对象相同,copy函数持有所使用的__block比那里,dispose函数释放所使用的__block变量。

    因此,Block中使用的赋值给附有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的Block所持有,因而可超出其变量作用域而存在。

    在Block中使用对象类型自动变量时,除了以下3种情况,推荐调用Block的copy实例方法。

    • ①、Block作为函数返回值返回时。
    • ②、将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量时。
    • ③、向方法名中含有usingBlock的Cocoa框架方法或Crand Central Dispatch的API中传递Block时。

    6、__block变量和对象

    __block id obj = [[NSObject alloc]init];

    clang 转化为

    //__block变量用结构体部分
    struct __Block_byref_obj_0 {
         void *__isa;
         __Block_byref_obj_0 *__forwarding;
         int __flags;
         int __size;
         void (*__Block_byref_id_object_copy)(void*, void*);
         void (*__Block_byref_id_object_dispose)(void*);
         __strong id obj;
    };
    
    static void __Block_byref_id_object_copy_131(void* dst, void *src) {
         _Block_object_assign((char*)dst + 40, *(void**)((char*)src + 40), 131);
    }
    
    static void __Block_byref_id_object_dispose_131(void *src) {
         _Block_object_dispose(*(void**)((vhar*)src + 40), 131);
    }
    
    //__block变量声明部分
    __Block_byref_obj_0 obj = {
         0,
         &obj,
         0x2000000,
         sizeof(__Block_byref_obj_0),
         __Block_byref_id_object_copy_131,
         __Block_byref_id_object_dispose_131,
         [[NSObject alloc] init]
    };

    ARC有效时:
    当Block从栈复制到堆时,使用_Block_object_assign函数,持有Block截获的对象。 当堆上的Block被废弃时,使用_Block_object_dispose函数,释放Block截获的对象;

    在__block变量为附有__strong修饰符的id类型或对象类型自动变量的情形下会发生同样的过程。当__block变量从栈复制到堆上,使用_Block_object_assign函数,持有赋值给__block变量的对象。当堆上的__block被废弃时,使用_Block_object_dispose函数,释放赋值给__block变量的对象。

    由此可见,即使对象复制到堆上的附有__strong修饰符的对象类型__block变量中,只要__block变量在堆上存在,那么该对象就会继续处于被持有状态。这与Block中使用赋值给附有__strong修饰符的对象类型自动变量的对象相同。

    7、Block循环引用

    ARC有效时:

    (1)weak消除循环引用

    • ①、self—-Block

    当我们在Block使用__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有。 这样容易引起循环引用。

    typedef void (^blk_t)(void);
    @interface MyObject:NSObject
    {
        blk_t blk_;
    }
    @end
    
    @implementation MyObject
    - (id)init {
        self = [super init];
        blk_ = ^{NSLog(@"self = %@", self);};
        return self;
    }
    - (void)dealloc {
        NSLog(@"dealloc");
    }
    @end
    int main() {
        id o = [[MyObject alloc] init];
        NSLog(@"%@", o);
        return 0;
    }

    源代码中,MyObject类的dealloc实例方法一直没有被调用;
    MyObject类对象的Block类型成员变量blk_持有赋值为Block的强引用。即MyObject类对象持有Block。init实例方法中执行Block语法使用附有_strong修饰符的id类型变量self。并且由于Block语法赋值在了成员变量blk中,因此通过Block语法生成在栈上的Block此时由栈赋值到堆,并持有所使用的self。self持有Block,Block持有self。这正是循环引用。

    为了避免循环引用,可声明附有__weak修饰符的变量,并将self赋值使用。

    - (id)init{
        self = [super init];
        id __weak tmp = self;
        blk_ = ^{NSLog(@"self = %@", tmp);};
        return self;
    }
    • ②、成员变量id——Block

    下面代码也会引起循环:

    @interface MyObject:NSObject {
         blk_t blk_;
         id obj_;
    }
    @end
    
    @implementation MyObject
    - (id) init {
         self = [super init];
         blk_ = ^{NSLog(@"obj_ = %@", obj_);};
         return self;
    }
    @end

    Block中没有self,也同样截获了self,引起循环。
    其实在block语法中使用了obj_,其实就已经截获了self: self->obj_。
    与前面一样,我们可以声明__weak的临时变量来避免循环引用;

    (2)、__block消除循环引用

    我们还可以使用__block变量来避免循环引用:

    typedef void (^blk_t)(void);
    @interface MyObject:NSObject{
         blk_t blk_;
    }
    @end
    @implementation MyObject
    - (id)init {
         self = [super init];
         __block id tmp = self;
         blk = ^{
              NSLog(@"self = %@", tmp);
              tmp = nil;
         };
         return self;
    }
    - (void)execBlock{
         blk_();
    }
    - (void)dealloc{
         NSLog(@"dealloc");
    }
    @end
    
    int main() {
         id o = [[MyObject alloc] init];
         [o execBlock];
         return 0;
    }

    这里并没有引起循环引用,但是如果不调用execBlock实例方法,即不执行赋值给成员变量blk_的Block,便会循环引用并引起内存泄露。原因如下:

    • MyObject类对象持有Block;
    • Block持有__block变量;
    • __block变量持有MyObject类对象。;

    (3)、__block,__weak,__unsafe_unretained

    ①、使用__block变量的优点:

    • 通过__block变量可控制对象的持有期间;
    • 在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可(不必担心悬垂指针);
    • 在执行Block时可动态地决定是否将nil或其他对象赋值在__block变量中。

    ②、使用__block变量的缺点

    • 为避免循环引用必须执行Block;
    展开全文
  • IndentationError: expected an indented block解决方案

    万次阅读 多人点赞 2018-07-05 12:32:09
    IndentationError: expected an indented block 翻译过来就是: 缩进错误: 期望一个缩进的块 贴下报错的代码的截图便于观察分析: 好了,直接开始单刀直入解决这个问题,解决方案,在报错的逻辑代码的前面加个...

    自学python,第一次遇到python的运行异常,竟然是这个鬼:
    异常一:

    IndentationError: expected an indented block

    把这段英文报错翻译过来就是: 缩进错误: 期望一个缩进的块
    贴下报错的代码的截图便于观察分析:
    这里写图片描述

    好了,直接开始单刀直入解决这个问题,解决方案,在报错的逻辑代码的前面加个空格就一切ok了,一个缩进就解决了这个异常,解决这个bug不是主要目的,了解python的语法结构和特点才是我们该做的事情。
    1.不像其他语言(C++, java 等等),就拿java举个例子:我们都知道在java里面方法后面是用{}来区分是否属于一个方法的,但在python里面方法体并不使用{}来区分,python的是用缩进来识别语法逻辑块的(i.e. if, while, for, def 等)。同一个if逻辑块下面的语句必须要有相同的缩进,否则python解析器怎么知道if块控制哪些语句?对吧,在python中,所有的逻辑代码块也就是一个方法中的代码,都必须使用相同的缩进来标识区分是同一个方法,否则编译会报错。贴个图解释下:
    这里写图片描述
    2、所谓缩进,就是每一行开头的空白。这个空白可以由多个空格或者制表符组成。python下面你怎么缩进都可以,比如3个空格,2个tab,这样都是合法的。但是同一个逻辑块下面必须用一样的。这一点在上面的图片和文字中已经做过解释和说明。

    异常二:
    安装后在代码编辑区不能写代码,如下图:
    这里写图片描述
    不管怎么做那个白色的块儿都在,不能进行编辑输入代码,后来百度后发现已经有人给出了解决方案,很简单,是因为PyCharm安装了Vim插件 你可以在tools Vim emulator将对勾去掉就可以了,尝试后确实是这样的,在PyCharm中点击Tools然后在弹出的列表中,去掉Vim emulator的对勾就可以正常编辑了。

    以上是本人的一点总结,以后有新的经验或者这方面的认知升级后,会重新完善该篇内容,如果有不对的地方欢迎留言指正改进,感激不尽!

    展开全文
  • BLock详解

    千次阅读 2016-03-08 17:35:22
    Block 关于block在之前的文章中提到过,这里就不多说了。一些关键点: block 是在栈上创建的 block 可以复制到堆上 ...(如果在Block中修改Block块外的)栈上的变量和指针,那么这些变量和指针必须用__block关键
    原文链接:http://www.jianshu.com/p/4fad8611c688
    Block
    关于block在之前的文章中提到过,这里就不多说了。
    
    一些关键点:
    block 是在栈上创建的
    block 可以复制到堆上
    Block会捕获栈上的变量(或指针),将其复制为自己私有的const(变量)。
    (如果在Block中修改Block块外的)栈上的变量和指针,那么这些变量和指针必须用__block关键字申明(译者注:否则就会跟上面的情况一样只是捕获他们的瞬时值)。
    如果 block 没有在其他地方被保持,那么它会随着栈生存并且当栈帧(stack frame)返回的时候消失。仅存在于栈上时,block对对象访问的内存管理和生命周期没有任何影响。
    如果 block 需要在栈帧返回的时候存在,它们需要明确地被复制到堆上,这样,block 会像其他 Cocoa 对象一样增加引用计数。当它们被复制的时候,它会带着它们的捕获作用域一起,retain 他们所有引用的对象。
    如果一个 block引用了一个栈变量或指针,那么这个block初始化的时候会拥有这个变量或指针的const副本,所以(被捕获之后再在栈中改变这个变量或指针的值)是不起作用的。(译者注:所以这时候我们在block中对这种变量进行赋值会编译报错:Variable is not assignable(missing __block type specifier),因为他们是副本而且是const的.具体见下面的例程)。
    当一个 block 被复制后,__block 声明的栈变量的引用被复制到了堆里,复制完成之后,无论是栈上的block还是刚刚产生在堆上的block(栈上block的副本)都会引用该变量在堆上的副本。
    
       ...
       CGFloat blockInt = 10;
       void (^playblock)(void) = ^{
            NSLog(@"blockInt = %zd", blockInt);
        };
        blockInt ++;
        playblock();
        ...
    
        //结果为:blockInt = 10
    self的循环引用
    当使用代码块和异步分发的时候,要注意避免引用循环。 
    // 这样做
    __weak __typeof(self) weakSelf = self;
    [self executeBlock:^(NSData *data, NSError *error) {
        [weakSelf doSomethingWithData:data];
    }];
    
    // 不要这样
    [self executeBlock:^(NSData *data, NSError *error) {
        [self doSomethingWithData:data];
    }];
    多个语句的例子:
    // 这样做
    __weak __typeof(self)weakSelf = self;
    [self executeBlock:^(NSData *data, NSError *error) {
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf doSomethingWithData:data];
            [strongSelf doSomethingWithData:data];
        }
    }];
    
    // 不要这样
    __weak __typeof(self)weakSelf = self;
    [self executeBlock:^(NSData *data, NSError *error) {
        [weakSelf doSomethingWithData:data];
        [weakSelf doSomethingWithData:data];
    }];
    你应该把这两行代码作为 snippet 加到 Xcode 里面并且总是这样使用它们。
    __weak __typeof(self)weakSelf = self;
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    在使用block的时候,分三种情况:
    
    
    直接在 block 里面使用关键词 self
    在这种情况下,首先要看这个block是什么样的。
    dispatch_block_t completionBlock = ^{
        NSLog(@"%@", self);
    }
    
    MyViewController *myController = [[MyViewController alloc] init...];
    [self presentViewController:myController
                       animated:YES
                     completion:completionHandler];
    在这种情况下,在block中直接使用self并没有问题。因为在block中对self进行引用,但是self并没有保留这个block。
    但是如果是下面这种:
    self.completionHandler = ^{
        NSLog(@"%@", self);
    }
    
    MyViewController *myController = [[MyViewController alloc] init...];
    [self presentViewController:myController
                       animated:YES
                     completion:self.completionHandler];
    在block里面引用了self,而这个block也被self保留(这个block是一个属性),那么会造成引用循环。解决方法就是使用__weak。
    
    
    在 block 外定义一个 __weak 的 引用到 self,并且在 block 里面使用这个弱引用。
    这样会避免循坏引用,也是通常情况下我们的block作为类的属性被self retain 的时候会做的。
    __weak typeof(self) weakSelf = self;
    self.completionHandler = ^{
        NSLog(@"%@", weakSelf);
    };
    
    MyViewController *myController = [[MyViewController alloc] init...];
    [self presentViewController:myController
                       animated:YES
                     completion:self.completionHandler];
    这个情况下self在属性里面 retain 了 block,但是block 没有 retain self。所以这样我们能保证了安全的访问 self。
    
    
    在 block 外定义一个 __weak 的 引用到 self,并在在 block 内部通过这个弱引用定义一个 __strong 的引用。
    和并发执行有关。当涉及异步的服务的时候,block 可以在之后被执行,并且不会发生关于 self 是否存在的问题。(关于这个我目前为止并不是很明白,希望有明白的朋友多多指教。)
    
    展开全文
  • ios block

    千次阅读 2013-09-04 15:28:26
    一、block内局部变量无法修改,但为什么可以添加数组? NSMutableArray *mArray = [NSMutableArray arrayWithObjects:@"a",@"b",@"abc",nil]; NSMutableArray *mArrayCount = [NSMutableArray arrayWithCapacity:1]...
  • 要从调用的block中返回整数: -(NSInteger)globalRecord { __block NSInteger globalRecord = 0; [GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray *leaderboards, NSError *error) { ...
  • Block用法解析

    万次阅读 2016-05-01 17:15:58
    Block代码块是对C做出的一个补充,初次体验代码块,无比的不适应。古怪的语法确实让人不易入门甚至精通。但若能够深入精通Block的使用,会给代码带来层次上的提升。Block就是一段在将来被执行的代码 定义块语法 ^[块...
  • data block address

    千次阅读 2016-06-23 20:01:43
    这里讨论的不是数据库管理员DBA,而是数据块地址DBA:Data Block Address! A Data Block Address (DBA) is the address of an Oracle data block for access purposes. ----源自《Oracle内核技术揭秘》的探索 DBA的...
  • block,inline和inline-block概念和区别

    千次阅读 2015-07-29 16:35:07
    block和inline这两个概念是简略的说法,完整确切的说应该是 block-level elements (块级元素) 和 inline elements (内联元素)。block元素通常被现实为独立的一块,会单独换一行;inline元素则前后不会产生换行,一...
  • 老师在前面加了一个用__weak __block 修饰的本类的指针 copy_self= self. 然后才在代码块里用copy_self来判断和调用 比如在room里: __weak __block room * copy_self = self; 请问为什么呢?新手...
  • java static block

    千次阅读 2015-01-22 21:57:06
    static{}(即static块),会在类被加载的时候执行且仅会被执行一次,一般用来初始化...1.StaticBlock.java public class StaticBlock {  public static int X = 100;  public final static int Y = 200; public
  • Oracle block cleanout

    千次阅读 2013-02-22 11:11:25
    简单点说,在oracle的block上都有活动事务的标志的,如果一个事务commit后,由于某些block在commit之前已经写回datafile,或者事务影响到的block数过多,则commit的时候只会清理undo segment header中的事务表信息,...
  • ----------------------------------------欢迎查看block连载博客:【专栏】---------------------------------------【block编程第一篇】block语法 【block编程第二篇】block捕获变量和对象(当前)【block编程第三...
  • db_block_checking与db_block_checksum

    千次阅读 2011-07-23 22:40:51
    db_block_checking与db_block_checksum两个参数都是对block进行检查,然而两者很容易混淆。 事实上,两个参数中前者是对块做逻辑性检查,后者则是做物理性检查。两者各司其职,并不矛盾。
  • 区块链Block创建时间

    千次阅读 2017-10-12 23:01:39
    区块时间获取无论BTC系列的区块还是ETH系列的区块,区块创建的时间存储单位都是unix时间戳。单位是秒而不是毫秒,这是我们在使用时必须注意的,如果直接用它来创建时间是肯定是错误的。Unix时间戳(Unix timestamp),...
  • ios 全面解析block

    千次阅读 2013-08-20 23:43:53
    typedef int(^MyBlock)(); void cFun(void(^blockName1)(), MyBlock blockName2){ //两种写法都可以 } -(void)ocFun:(void(^)())blockName1 andOtherBlock:(MyBlock)blockName2{   //注意第一种写法的特别之处...
  • Xcode省略block的参数名

    2013-01-22 02:26:13
    有一个block property: @property (nonatomic, copy) void (^indexChangeBlock)(NSInteger index); 我为这个property设置值的时候,xcode自动设置就会省略参数名,然后成为这样: [self.segmentedControl...
  • Block存储域探析

    千次阅读 2017-04-21 17:31:24
    接《Block截获自动变量实现与__block修饰符内部实现》我们继续探讨Block留下的问题 1,__Block_byref_i_0 *__forwarding;这个指向自身的指针是什么鬼,有什么作用,什么时候用? 2,Desc_0结构体中多出来的void (*...
  • expected an indented block

    万次阅读 2018-01-09 10:26:16
    导致excepted an indented block这个错误的原因一般有两个: 1. 冒号后面是要写上一定的内容的(新手容易遗忘这一点),例如 try: something else: #else后面的内容由于不是很重要而忘了写 2. 缩进不...
  • C# TextBlock 上标

    千次阅读 2017-02-13 14:32:46
    我需要做一个函数,显示 x^2 ,但是看起来用 TextBlock 做的不好看。 我用 WPF 写的上标看起来不好看,但是最后有了一个简单方法让他好看。
  • IOS学习之委托和block

    万次阅读 2014-05-14 10:04:38
    这篇文章建议和前一篇一起看, 另外先弄清楚IOS的block是神马东东。 委托和block是IOS上实现回调的两种机制。Block基本可以代替委托的功能,而且实现起来比较简洁,比较推荐能用block的地方不要用委托。 本...
  • block 内部结构分解

    千次阅读 2011-06-28 10:37:00
    Oracle block的详细物理结构图: 本文主要说明oracle block的物理结构,它是oracle的最小存储单元,由多个os数据块组成。主要由三个逻辑层组成(通过c语言描绘的结构,如下图一所示):the cache layer,the ...
  • block、inline和inline-block

    千次阅读 2019-01-07 21:49:53
    block、inline、inline-block行内元素和块级元素blockinlineinline-block行内元素和块级元素的区别常见用法 行内元素:又叫内联元素, 块级元素:是一个元素,占用了全部宽度,在前后都是换行符
  • iOS block之三种block

    千次阅读 2017-09-10 18:04:02
    本篇博文主要根据block在内存中存在的位置介绍block的种类以及其生命周期等特性

空空如也

1 2 3 4 5 ... 20
收藏数 172,169
精华内容 68,867
关键字:

block