该文章参考多篇文章,已记不清,如有问题请联系我。

参考:http://blog.csdn.net/zm_yh/article/details/51469275

Block理解

1. Block执行的代码,是在编译的时候已经生成好的;

2.  Block是一个包含执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。

Block的修饰

ARC情况下

1.如果用copy修饰Block,该Block就会存储在堆空间。则会对Block的内部对象进行强引用,导致循环引用。内存无法释放。

解决方法:

新建一个指针(__weak typeof(Target) weakTarget = Target )指向Block代码块里的对象,然后用weakTarget进行操作。就可以解决循环引用问题。 ARC中 GCD 中并不需要对self 弱引用,因为self  并不持有 GCD 的block 直接self即可。

MRC情况下

用copy修饰后,如果要在Block内部使用对象,则需要进行(__block typeof(Target) blockTarget = Target )处理。在Block里面用blockTarget进行操作。

Block的定义格式

  返回值类型     Block变量名            形参列表          传递         形参列表

NSString* (^ TestBlock)(id parameA,id parameB…) = ^(id parame1,id parame2…) { };

调用Block的代码:block变量名(实参);

安全调用block

一般调用block调用时都要对其是否为nil进行判断,是处于安全的考虑,如下

if(block) {

block();

}

Block的模式

1.无参数无返回值的Block

/**

*  无参数无返回值的Block

*/

- (void)func1{

/**

*  void :就是无返回值

*  emptyBlock:就是该block的名字

*  ():这里相当于放参数。由于这里是无参数,所以就什么都不写

*/

void (^emptyBlock)() = ^(){

NSLog(@"无参数,无返回值的Block");

};

emptyBlock();

}

2.有参数无返回值的Block

/**

*  调用这个block进行两个参数相加

*  @param int 参数A

*  @param int 参数B

*

*  @return 无返回值

*/

void (^sumBlock)(int ,int ) = ^(int a,int b){

NSLog(@"%d + %d = %d",a,b,a+b);

};

/**

*  调用这个sumBlock的Block,得到的结果是20

*/

sumBlock(10,10);

3.有参数有返回值的Block

/**

*  有参数有返回值

*  @param NSString 字符串1

*  @param NSString 字符串2

*

*  @return 返回拼接好的字符串3

*/

NSString *(^logBlock)(NSString *,NSString *) = ^(NSString * str1,NSString *str2){

return [NSString stringWithFormat:@"%@%@",str1,str2];

};

//调用logBlock,输出的是 我是Block

NSLog(@"%@", logBlock(@"我是",@"Block"));

4. Block作为参数使用

typedef long (^BlkSum)(int, int);

- (BlkSum) sumBlock {

int base = 100;

BlkSum blk = ^ long (int a, int b) {

return base + a + b;

};

return [blk copy];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

BlkSum tttt = [self sumBlock];

tttt(10,20);

NSLog(@"tttt----%ld",tttt(10,20));

}

Block结合typedef使用,作为属性

.h

/**

*  定义了一个logBlock的Block。这个logBlock必须带2参数,这个参数的类型必须为NSString类型的

*  返回值类型   NSString

*  @param NSString

*/

#import <UIKit/UIKit.h>

typedef NSString* (^logBlock)(NSString *,NSString *);

@interface ViewController : UIViewController

@property (nonatomic,copy)logBlock testBlock;

@end

.m

- (void)viewDidLoad {

[super viewDidLoad];

self.testBlock = ^(NSString * str1,NSString *str2){

return [NSString stringWithFormat:@"%@%@",str1,str2];

};

}

// 调用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

NSLog(@"gggggg----%@",self.testBlock(@"gggg",@"tttt"));

}

block变量定义时为什么用copyblock是放在哪里的?

  • block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,可能被随时回收,而不是在堆(heap)上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。通过copy可以把block拷贝(copy)到堆,保证block的声明域外使用。

特别注意:在把block放到集合类当中去的时候,如果直接把生成的block放入到集合类中,是无法在其他地方使用block,必须要对block进行copy。

[array addObject:[[^{

NSLog(@"hello!");

} copy] autorelease]];

Block与变量

1. Block与局部变量

  1. 官方文档:写到
  2. Stack (non-static) variables local to the enclosing lexical scope are captured as const variables.
  3. Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.
  1. 意思是:在block 使用中,位于一块封闭函数内存(非静态的)栈变量  被捕捉为const变量。
  2. 它们的值是在程序中的块表达式的点处取的。在嵌套块中,该值从最近的封闭范围捕获。(也就是block代码块上面,最近的值。)

// 局部变量,在Block中是只读的。Block定义时会copy变量的值到栈上,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。

- (void)testParameBlock222

{

int number = 100;

void(^TestBlock)(int) = ^(int x){

// number = number+x;

// 这句报错,局部变量没用__block 修饰时,在block中只能使用,不能修改它的值。

NSLog(@"number:%d",number+100);

};

number = 200;  // 这里虽然修改的number的值为200。但是不能影响block 中的值。因为在编译block时已经将number 的值只读copy了一份。

TestBlock(100);

}


参考:http://www.jianshu.com/p/a0adbaca7f69  这个文章更助于我们理解。

我们可以反编译看一下block里面匿名函数的实现:

  1. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  2. int a = __cself->a; // bound by copy
  3. printf("%d\n",a);
  4. }
  5. bound by copy这里的注释表示,block对它引用的局部变量做了只读拷贝,也就是说block引用的是局部变量的副本。所以在执行block语法后,即使改写block中使用的局部变量的值也不会影响block执行时局部变量的值。

Block与局部变量__block 

// 在Block中修改局部变量的值,使用__block。

// MARK: __block修饰 修改局部变量的值

- (void)testParameBlock333

{

__block int a = 10;

void (^helloBlock)(void) = ^(){

NSLog(@"%d\n",a);

};

a = 11;

helloBlock();

}

引入__block关键字后,运行结果是11,我们再编译看看block里面匿名函数的实现

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

__Block_byref_a_0 *a = __cself->a; // bound by ref

printf("%d\n",(a->__forwarding->a));

}

struct __Block_byref_a_0 {

void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

int a;

};

我们看到局部变量a变成一个构造体对象,而不再是一个整形变量,结构体里包含它原来的整形变量。bound by ref这个注释也表明此时已变成引用传递。

深入研究Block捕获外部变量和__block实现原理http://www.halfrost.com/ios_block/)写的有点深,没看完。

2. Block与全局变量

// MARK: Block与全局变量

// 全局变量存储在,因为全局变量或静态变量在内存中的地址是固定的,Block在读取改变量值的时候是直接从其所在的内存读出的,获取得道是最新值,而不是在定义时copy的常量。

int value = 100;

- (void)testParameBlock444

{

void (^TestBlock)(int)=^(int x){

value = value+100;

NSLog(@"看看是不是喽%d",value);

};

TestBlock(100);

NSLog(@"value----%d",value);

}

3. Block与静态变量

// MARK: Block与静态变量

static int number = 100;

- (void)testParameBlock555

{

int (^TestBlock)(int) = ^(int x){

number = number+x;

NSLog(@"静态变量----%d",number);

return number;

};

NSLog(@"用static修饰 使用局部变量的结果:%d",TestBlock(100));

number = 50;

NSLog(@"在外面改变number的值,再次调用block的结果:%d",TestBlock(100));

}

Block变量,被__block修饰的变量称作Block变量。基本类型的Block变量等效于全局变量、或静态变量。

1.__block对象在block中是可以被修改、重新赋值的。

2.__block对象在block中不会被block强引用一次,从而不会出现循环引用问题。

使用了__weak修饰符的对象,作用等同于定义为weak的property。自然不会导致循环引用问题,因为苹果文档已经说的很清楚,当原对象没有任何强引用的时候,弱引用指针也会被设置为nil。

因此,__block和__weak修饰符的区别其实是挺明显的:

1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。

2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。

3.__block对象可以在block中被重新赋值,__weak不可以。

在block中能否定义新的变量

可以,而且在block内部定义的变量是在【栈区】分配空间的

//定义一个外部变量

int sum = 2;

//定义一个有参有返回值的block的别名

typedef int (^myBlock)(int, int);

NSLog(@"sum = %p",&sum);//此时sum在栈区

myBlock b1 = ^(int a, int b){

int sum = 100;

NSLog(@"内部sum = %p",&sum);//此时的地址在栈区

return sum + a + b;

};

NSLog(@"a + b = %d", b1(1,2));

NSLog(@"外部sum = %p",&sum);//此时sum在栈区

为什么系统的block,AFN网络请求的block内使用self不会造成循环引用? 而自定义的block如果直接使用了self,或者成员变量的话,都会形成强引用,造成内存泄漏?

关于这个问题,UIView和AFN还是不一样的。

首先循环引用发生的条件就是持有这个block的对象,被block里边加入的对象持有。当然是强引用。所以UIView的动画block不会造成循环引用的原因就是,这是个类方法,当前控制器不可能强引用一个类,所以循环无法形成。

而AFN无循环是因为绝大部分情况下,你的网络类对象是不会被当前控制器引用的,这时就不会形成引用环。当然我不知道AFN是否做了别的处理,按照这样来说的话,如果你的控制器强引用了这个网络类的对象,而且在block里面引用了当前控制器,也是会发生循环引用的。

其实只要抓住循环引用的本质,就不难理解。所谓循环引用,是因为当前控制器在引用着block,而block又引用着self即当前控制器,这样就造成了循环引用。系统的block或者AFN等block的调用并不在当前控制器中调用,那么这个self就不代表当前控制器,那自然也就没有循环引用的问题。以上引用均指强引用

用UIView啥的因为是类方法,AFNetworking是因为人家大神自己封装了一个completionBlock,不管你传进来是啥,都给你把循环引用打破。看图

如果块是一个单例持有的,块内又使用了ViewController这个类,会引起循环引用。例子:

[[OutsidePacketsSchedule shareInstance] sendParameters:dict requestCmd:@"addCustomEmoReq"responseCmd:@"addCustomEmoRsp"complete:^(idresponse,NSError*error) {

if(!error) {

[weakSelf.viewsetToast:@"添加自定义表情成功"];

}

}];

上例中的单例持有的代码块中要用弱引用,原因是:单例不会被释放掉,它会一直持有block,导致该block所在的ViewController释放不掉。

(3) 如果是方法中的参数是block,不会造成循环引用,因为方法中的block是位于栈内存的,方法返回后,block将会无效。

Cell使用block潜在的循环引用

回到标题说的情况,使用UITableViewCell或者UICollectionViewCell的子类定制cell时,会遇到cell上有个独立的按钮事件需要回调,当使用block来实现这个回调的设计时就会发生一个容易忽略的循环引用,Xcode编译器无法发现这类隐形循环引用,没有警告提醒,如下图没有警告,但循环引用已经产生,当该控制器被pop出去时不会销毁dealloc。

分析其原因在于cell实际是tableView的子视图,每个子视图都是会被其父视图的subviews(NSArray *)属性所强引用,即tableView ~> subviews ~> cell,而cell因为使用block作为回调强引用了block内部的对象,形成了这样的循环引用链条,即 controller ~> tableView ~> cell ~> controller,解决的方法同样是使用弱引用传入block,如下图所示。

__weak typeof(self) weakSelf = self;

cell.cellBlock = ^(id cell) {

[weakSelf iLog];

};

return cell;

值得注意的是,在这个例子中,即使tableView属性的声明为weak,循环引用仍然会产生,原因在于tableView还是controller的view属性的子视图,强引用链接同样存在,因此最好是在block内部切开强引用链条。

(0032) iOS 开发之Block 的基础用法及注意事项1相关推荐

  1. (0033) iOS 开发之Block 的基础用法及注意事项2

    循环引用之String 当在block内部使用成员变量的时候,比如 @interface ViewController : UIViewController { NSString *_string; ...

  2. iOS开发之Objective-C(基础篇)-李飞-专题视频课程

    iOS开发之Objective-C(基础篇)-232人已学习 课程介绍         该系列课程是iOS开发之Objective-C基础入门视频.课程中会详细的讲解OC语法特点,面向对象的使用,循环 ...

  3. ioS开发之c语言基础-一维数组,字符数组

    // //  main.m //  C4-一维数组,字符数组 // //  Created by dllo on 15/10/8. //  Copyright (c) 2015年 dllo. All ...

  4. IOS开发之UI基础LOL英雄展示-15

    IOS开发之UI基础LOL英雄展示-15 // // ViewController.m // 15-英雄展示-单组数据 // // Created by 鲁军 on 2021/2/3. //#impo ...

  5. ios开发之OC基础-类和对象

    ios开发之OC基础-类和对象 本系列的文章主要来自于个人在学习前锋教育-欧阳坚老师的iOS开发教程之OC语言教学视频所做的笔记,边看视频,边记录课程知识点.建议大家先过一遍视频,在看视频的过程中记录 ...

  6. (0045) iOS 开发之MBProgressHUD 源码学习

    (0045) iOS 开发之MBProgressHUD 源码学习 第一部分:学习所得和分析线程 1.  学习到了kvo 的使用 和屏幕方向的旋转判断. 2. 如果调起这个 HUD 的方法不是在主线程调 ...

  7. 李洪强iOS开发之RunLoop的原理和核心机制

    李洪强iOS开发之RunLoop的原理和核心机制 搞iOS之后一直没有深入研究过RunLoop,非常的惭愧.刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研 ...

  8. iOS开发之ARC(自动引用计数)

    iOS开发之ARC(自动引用计数) 英文原文:Automatic Reference Counting on iOS 参与翻译(4人): 纶巾客, showme, 李远超, 王宇龙 自动引用计数(AR ...

  9. iOS开发之Objective-C(面试篇)-李飞-专题视频课程

    iOS开发之Objective-C(面试篇)-132人已学习 课程介绍         这个系列,我会选取实际面试过程中会问到的难点问题.几乎都是在面试大公司或者技术要求比较高的公司会问到的问题.希望 ...

最新文章

  1. 2021年大数据ELK(八):Elasticsearch安装IK分词器插件
  2. C#Post文件上传
  3. 机器人建图、感知和交互的语义研究综述
  4. 玩聚SR和FriendFeed的区别
  5. 基于基站定位数据的商圈分析代码详细解释
  6. Z字形变换(LeetCode第6题)
  7. c语言 指针 pdf,深入理解c指针 PDF扫描版[33MB]
  8. python元组取值_Python基础之元组
  9. (Python)零起步数学+神经网络入门
  10. nodejs图片读取
  11. 如何在Linux系统上刷抖音
  12. python笔记:random模块中的函数
  13. kafka集群为什么需要三个节点_Kafka突然宕机了?稳住,莫慌!
  14. 游戏筑基开发之测试篇2(C语言)
  15. 信息熵与二进制--信息论系列
  16. poj 3295 Tautology【离散数学之重言式】
  17. c语言表示反正弦函数,[原创]正弦和反正弦函数
  18. 3.图灵学院-----阿里/京东/滴滴/美团整理----高频JVM调优篇
  19. JavaScript开发——文件夹的上传和下载
  20. 微信废品回收小程序开发上门回收废品小程序开发

热门文章

  1. 【vue插件篇】vue-form-check 表单验证
  2. 一个简单的生产消费者示例
  3. 这才是我想要的云盘工具
  4. js高级程序设计笔记——DOM扩展
  5. EasyUI——常见用法总结
  6. 10个小动作帮你简化生活
  7. CF232C Doe Graphs
  8. ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'
  9. Mybatis批量添加对象List
  10. DataGrid控件读取具体某行某列的值、获取总列数