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\n");};

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\n",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\n";

    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会拷贝变量内容到自己的栈内存上,以执行时可以调用。但并不是重新申请内存。

 

转载于:https://www.cnblogs.com/encoreMiao/p/5126727.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值