• effective OC2.0 52阅读笔记(六 块)+ Objective-C高级编程 (二 Blocks)


    什么是Blocks

     blocks是带有自动变量(局部变量)的匿名函数

    (1)匿名函数:不带名称的函数。

    (2)带有自动变量(局部变量)

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

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

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

    1自动变量(局部变量)2函数参数    //1、2都属于局部变量,存储于栈分配空间;只有在函数执行期间可见。

    3静态变量(静态局部变量)     //3局部变量,静态存储区域;静态变量只被初始化一次,且只对定义自己的函数体可见。

    4静态全局变量  5全局变量       //4、5 都是全局作用域,分配内存空间于静态存储区域。 静态全局变量只作用于定义它的文件里,也称文件作用域(static修饰)。全局变量只在一个源文件中定义,就可以作用于所有的源文件,其它源文件需使用extern引入。

    static修饰符,全局->静态全局,限制了使用范围。变量->静态变量,改变了存储方式和生存期。

    使用Blocks可以不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量时的问题,仅用编写C语言函数的源代码量即可使用带有自动变量的匿名函数。

    Blocks模式

    Block语法

    完整形式的Block语法同c语言函数定义相比,仅有两点不同。

    (1)没有函数名

    (2)带有^

    以下为block语法的BN范式:

      ^ 返回值类型 参数列表 表达式

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

    其中返回值类型可省略变成:^(int count){return count+1;}

    如果不使用参数,参数列表也可省略^{printf("Blocks ");};

    Block类型变量

    在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型变量中。 如下:

    int func (int count)

    {

      return count+1;

    int (*funcptr)(int) = &func;

    同样可将Block语法赋值给声明为Block类型的变量中。如下:

    int (^blk)(int) = 

    ^ (int)(int count)

    {

      return count+1;

    };

    该Block类型变量与一般的c语言变量相同。

    int (^blk1)(int) = blk;

    void func(int(^blk)(int));//作为函数参数使用

    typedef int(^blk_t) (int);//作为函数返回值使用时候记述方式极为复杂。所以可以像函数指针那样使用typedef来解决问题。

    所以作为函数参数使用可变为void func(blk_t blk);

    作为返回值可以变为blk_t func();

    blk_t blk= 

    ^ (int)(int count)

    {

      return count+1;

    }

    blk_t *blkptr = &blk;

    (*blkptr)(10);//可以这样调用,同c相同

    截获自动变量的值

    带有自动变量值 在Blocks就表现为 截获自动变量的值。

    (块就是一个值,且自有其相关类型。块的强大之处是,在声明它的范围里,所有变量都可以为其所捕获,如果捕获的变量是对象类型,就会自动保留。且默认情况下被块所捕获的变量,是不可以在块里修改的,若想修改此变量。声明变量的时候可以加上__block。)

    例外:对于

    id array =[[NSMutableArray alloc]init];

    void (^blk)(void) = ^{

      id obj =[[NSObject alloc]init];

      [array addObject:obj];

    };

    虽然赋值给截获的自动变量array的操作会产生编译错误。但是使用截获的值却不会有任何问题。

    ps:截获自动变量的方法并没有实现对C语言数组的截获(查找:为什么Block不能截获c语言数组)

    const char text[] = "hello";

    void (^blk) (void) = ^{printf("%c ",text[2]);};

    所以以上代码会造成编译器错误,这时使用指针便可以解决该问题,改为const char *text = "hello";

    Block实现

    Block实现

    int main() {

        void (^blk)(void) = ^{};

        blk();

        return 0;

    }

    通过

    clang -rewrite-objc main.m -o main.cpp编译成如下形式

    #ifndef BLOCK_IMPL

    #define BLOCK_IMPL

    //表示今后版本升级所需的区域以及函数指针

    struct __block_impl {

      void *isa;

      int Flags;

      int Reserved;

      void *FuncPtr;

    };

    //表示今后版本升级所需的区域和Block大小

    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)};//这里直接定义了__main_block_desc_0_DATA

    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;

      }

    };

    //这里__cself表示指向Block值的变量(其实Block就是__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例)

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    }

    //实现

    int 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;

        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

      // (*blk->impl.FuncPtr)(blk);

        return 0;

    }

    这里__main_block_impl_0相当于objc_object

    __main_block_impl_0中的isa 相当于objc_object中的isa

    那么__main_block_impl_0中的isa指向的_NSConcreteStackBlock相当于objc_object中的isa指向的class_t(?objc_class)结构体实例。

    因此Block作为Objective-C的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。(block是一个仿对象)

    截获自动变量的值

    int main() {

        int dmy = 256;

        int val = 10;

        const char *fmt = "val = %d ";

        void (^blk)(void) = ^{fmt;val;};

        blk();

        return 0;

    }

    clang编译如下:

    struct __main_block_impl_0 {

      struct __block_impl impl;

      struct __main_block_desc_0* Desc;

      const char *fmt;

      int val;

      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {

        impl.isa = &_NSConcreteStackBlock;

        impl.Flags = flags;

        impl.FuncPtr = fp;

        Desc = desc;

      }

    };

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

      const char *fmt = __cself->fmt; // bound by copy

      int val = __cself->val; // bound by copy

    fmt;val;}

    //注意这里fmt和val被作为成员变量追加到了__main_block_impl_0的结构体中。这里在Block中没有使用的dmy并不会被追加,Block的自动变量截获只针对Block中使用的自动变量。

     这里可以解释为什么Block不能截获c语言数组

        char a[10] = {2};

        char b[10] = a;//C语言中这样赋值是不允许的,Blocks是更遵循C语言规范的。

    //以下是可以的

    //    char *a = "2";

    //    char *b = a;

    __block说明符

     Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。

    若是想改变的话有两种方法:

    (1)使用静态局部变量,静态全局变量,全局变量

    这里Block依然会捕获静态局部变量(不会捕获静态全局变量和全局变量),但是使用的是指针,如下:

    struct __main_block_iml_0{

      struct __block_impl impl;

      sturct __main_block_desc_0* Desc;

      int *static_val;

    }

    使用: int *static_val = __cself->static_val;

    (自动变量不使用指针的原因是,自动变量超过作用域后就会被废弃,通过指针也无法访问到原来的自动变量)

    (2)__block存储域类说明符 (C语言中有typedef extern static(静态变量数据区域) auto(栈) register)

    __block修饰后,增加了很多编译代码,直接将原来的局部变量,变为了(生成在栈上的)结构体实例。

    struct __Block_byref_val_0 {

      void *__isa;

    __Block_byref_val_0 *__forwarding;

     int __flags;

     int __size;

     int val;//原来的局部变量

    };//单独生成的原因,是可以在多个Block中使用__block变量。反过来也可以一个Block中使用多个_block变量。

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        __Block_byref_val_0 *val = __cself->val; // bound by ref

      (val->__forwarding->val) = 1;

    }

    Block和__block变量的存储域

    通过前面的说明,我们得知了Block与__block变量的实质分别如下:

    Block的实质是栈上的Block结构体实例;__block变量的实质是栈上的__block变量的结构体实例。

    将Block作为OC对象看时,该Block类可能为如下三种(存储区域):

    _NSConcreteStackBlock(栈):一般情况下都是这种。

    _NSConcreteGlobalBlock(数据区)(全局块,不捕捉任何状态,全部信息都能在编译期运行):(1)在记述全局变量的地方使用Block语法生成的Block对象(因为全局区不存在对自动变量的截获,因此Block结构体实例的内容不依赖于程序的执行状态,整个程序只需要一个实例,因此存储在数据去中)(2)Block语法的表达式中不使用应截获的自动变量时。(存疑?使用clang发现还是stackBlock)

    _NSConcreteMallocBlock(堆):

    (存在原因之一)这个结构式Block超出作用域存在的原因;(存在原因之二)也是之前__block变量用结构成员变量__forwading存在的原因。

     对于:

    void (^block)();//注意这种用法

    if (/*some condition*/){

      block = ^{

        NSLog(@"Block A");

      }

    }else{

      block = ^{

        NSLog(@"Block B");

      }

    }

    block();//由于block块只在if else语句范围内有效,这样写出来的代码可以编译,但是运行起来有时正确有时错误。此时可以给块对象发送copy消息以拷贝之。这样的话,可以把块从栈复制到堆(堆块)。

    下面详细描述_NSConcreteMallocBlock的存在原因之一:

    将Block从栈上复制到堆上,即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。

    (1)Block作为函数返回值时,编译器会自动生成复制到堆上的代码。例如编译器代码(和书上写的有所不同,这里是sel_registerName("copy");):

    return (blk_t)((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)((int (*)(int))&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA, rate)), sel_registerName("copy"));

    (2)Block作为函数或方法参数时,需要自己添加copy。例外不需要自己添加copy:Cocoa框架的方法切方法名中含有usingBlock时,GCD的api,因为已经实现。

    举个例子:

    id getBlockArray()

    {

        int val = 2;

        return [[NSArray alloc]initWithObjects:[^{NSLog(@"%d",val);} copy],[^{NSLog(@"%d",val);} copy],nil];//这里如果不加copy,就可能会有各种各样的错误。

    }

    int main(){

        id obj = getBlockArray();

        typedef void (^blk_t)(void);

        blk_t blk = (blk_t)[obj objectAtIndex:0];

        blk_t blk1 = (blk_t)[obj objectAtIndex:1];

        blk();

        blk1();

    }

    注意:将Block从栈上复制到堆上是相当消耗CPU资源的。pps:block语法和block变量都可以直接调用copy方法。

    对于Block进行copy:只对_NSConcreteStackBlock是从栈复制到堆。对_NSConcreteMallocBlock只是引用计数增加。对_NSConcreteGlobalBlock什么也不做。

    (3)还有一种情况栈上的Block会被复制到堆:

    将Block赋值给类中的(以__strong修饰)成员变量时候。(在ARC状态下都是以__strong修饰的)

    综上:所以说在ARC状态下只要指针过一下__strong指针或者由函数返回都会将block栈移动到堆上。

    下面详细描述_NSConcreteMallocBlock的存在原因之二:(__block变量存储域)

    当Block从栈复制到堆时,__block变量也会从栈复制到堆,并被Block持有。(注意在栈中的Block只是使用在栈中的__block,而不是持有关系)。如果__block变量在堆中,那么copy会增加引用计数。

    且对于在栈中的__block变量结构体中的__forwading是指向自己本身的指针。而当复制到堆之后,栈中__block变量的__forwading和堆中的__block变量的__forwading都会指向堆中的__block变量本身。

    截获对象

    对于id array = [[NSMutableArray alloc]init];

    在Block结构体中截获的是id __strong array;(虽说c语言的结构体不能含有赋有__strong修饰符的变量,因为编译器不知道应合适进行c语言结构体的初始化和废弃操作,不能很好的管理内存。但是Block结构体总即使含有__strong或__weak修饰的变量,也可以恰当的进行初始化和废弃,因为其在__main_block_desc_0中增加了copy(__main_block_copy_0其实调用的是_Block_object_assign相当于retain)和dispose (__main_block_dispose_0其实调用的是_Block_object_dispose相当于relesase)成员变量))

    这里注意:

    单纯截获,不加修饰符:对于截获的对象会产生copy和release方法(且copy和release方法的最后一个参数是BLOCK_FIELD_IS_OBJECT),对于简单的变量int val这种则不会产生。

    加__block修饰符:对于无论是对象还是简单变量都会产生copy和dispose方法,最后一个参数是BLOCK_FIELD_IS_BYREF。不同的是对象还会调用

    __Block_byref_id_object_copy_131这个方法?131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT。

    注意:__block与__weak一起使用和__weak单独使用效果是一样的。__autoreleasing 不能与__block一同使用。

    避免循环引用

    (1)__weak修饰 (2)也可使用__block修饰tmp,只是需要在函数语法中tmp=nil(缺点是如果不调用的话,就会造成内存泄漏。优点是可控制对象的持有期间)。

     (以下注意self不能在类方法中使用)

    如果将块定义在了OC类的实例方法里,那么除了可以访问类的所有实例变量之外,还可以使用self变量。块总能修改实例变量,所以在声明时也无需加_block。不过,如果通过读取或写入操作捕获了实例变量(这里所指的并不单单指用self,所以说只要是用到了实例变量就会捕捉self?),那么也会自动把self变量一并捕获,因为实例变量与self所指代的实例是关联在一起的。定义块的时候其所占内存区域是分配到栈中的(栈块)。

    MRC下用_block修饰不会引起循环引用(相反MRC下用__block来解除引用)。ARC下用_block修饰就会引起循环引用。

    38 为常用的块类型创建typedef

    总结:每个块都具备其“固有类型”(inherent type),这个由块所接受的参数及其返回值组成。可以为同一个块签名定义多个类型别名。

    39 用handler块降低代码分散程度

    总结:(注意161页推荐网络请求时候将成功和失败的情况放在一个块中处理)。当某些代码必须运行在特定线程上,可以用handler来实现。设置api时如果用到了handler块,可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行。

    40 用块引用其所属对象时不要出现保留环

    总结:一定要找个适当的时机解除保留环,而不能把责任推给API的调用者。

    番外:NSString *a = @“hello”;a为常量变量(存储在内存中的常量区)。@“hello”为常量。不加__block会引用常量的地址(浅拷贝)。加__block类型block会去引用常量变量的地址。

    如下示例:

    NSString *str = @"hello";

    NSLog(@"hello======%p",str);

    void (^print)(void) = ^{

        NSLog(@"block=str======%p",str);

    };

    str = @"hello1";

    NSLog(@"hello1======%p",str);

    print();

    不加__block打印:(此时引用的是常量@“hello”地址)

    2017-04-12 19:56:38.667 BAFParking[27300:1591355] hello======0x105c0e5f0

    2017-04-12 19:56:38.667 BAFParking[27300:1591355] hello1======0x105c0e650

    2017-04-12 19:56:38.668 BAFParking[27300:1591355] block=str======0x105c0e5f0

    加__block打印:(此时引用的是常量变量a的地址)

    2017-04-12 19:55:21.262 BAFParking[27244:1590097] hello======0x10d9345f0

    2017-04-12 19:55:21.263 BAFParking[27244:1590097] hello1======0x10d934650

    2017-04-12 19:55:21.263 BAFParking[27244:1590097] block=str======0x10d934650

     

    block会拷贝变量内容到自己的栈内存上,以执行时可以调用。但并不是重新申请内存。

  • 相关阅读:
    jar包启动的日志管理问题
    docker常用命令-----镜像与容器
    Maven+Nexus私服的搭建
    公允价值变动损益期末处理
    递延所得税资产怎么算
    固定资产转投资性房地产的差额为什么计入其他综合收益
    固定资产转投资性房地产
    固定资产折旧方法
    SublimeText注册码(亲测可用)
    未分配利润和净利润的区别是什么?
  • 原文地址:https://www.cnblogs.com/encoreMiao/p/5126727.html
Copyright © 2020-2023  润新知