iOS之深入探究Block的使用和外部变量捕获

一、前言

  • 闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」; Block是Objective-C对于闭包的实现。
  • 其中,Block:
    • 可以嵌套定义,定义Block方法和定义函数方法相似;
    • Block 可以定义在方法内部或外部;
    • 只有调用Block时候,才会执行其{}体内的代码;
    • 本质是对象,使代码高聚合。
  • 使用 clang 将 OC 代码转换为 C++ 文件查看block的方法:
    • 在命令行输入代码 clang -rewrite-objc 需要编译的OC文件.m;
    • 这时查看当前的文件夹里多了一个相同的名称的 .cpp 文件,在命令行输入 open main.cpp 查看文件。

二、Block的定义与使用

① 无参数无返回值
	// 无参数,无返回值,声明和定义
	void( ^MyBlockOne)(void) = ^(void){
    
    
		NSLog(@"无参数,无返回值");
	};
	// block的调用
	MyBlock0ne();
② 有参数无返回值
	// 有参数,无返回值,声明和定义
	void(^MyblockTwo)(int a) = ^(int a){
    
    
		NSLog(@"@ = %d我就是block,有参数,无返回值", a);
	;
	MyblockTwo(1);
③ 有参数有返回值
	// 有参数,有返回值
	int (^MyBlockThree)(int,int) = ^(int a,int b){
    
     
		NSLog (@"%d我就是block,有参数,有返回值", a + b);
		return a + b;
	};
	MyBlockThree(1,2);
④ 无参数,有返回值
	int(^MyblockFour)(void) = ^{
    
    NSLog(@"无参数,有返回值");
		return45;
	}
	MyblockFour();
⑤ 实际开发中常用typedef定义Block
  • 用typedef定义 一个block:
	typedef int (^MyBlock)(intint);
  • 这时,MyBlock 就成为一种Block类型;
  • 在定义类的属性时可以这样:
	@property (nonatomic, copy) MyBlock myBlockOne;
  • 使用时:
	self.myBlockOne = ^int(int, int){
    
    

	}
⑥ 为什么Block语法中不能使用数组?

因为结构体中的成员变量与自动变量类型完全相同,所以结构体中使用数组截取数组值,而后调用时再赋值给另一个数组。也就是数组赋值给数组,这在C语言中是不被允许的。

三、Block与外界变量

① 捕获自动变量(局部变量)值(auto变量)
  • (1) 默认情况
    • 所谓捕获外部变量,意思就是在block内部,创建一个变量来存放外部变量,这就叫做捕获。
    • auto变量:自动变量,离开作用域就会销毁,一般我们创建的局部变量都是auto变量,比如 int age = 10,系统会在默认在前面加上auto int age = 10。
    • 对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说 block 的自动变量截获只针对 block 内部使用的自动变量,不使用则不截获,因为截获的自动变量会存储于block的结构体内部,会导致block体积变大。
    • 特别要注意的是默认情况下block只能访问不能修改局部变量的值。
      在这里插入图片描述
    • 如下示例:
	int age = 10;
	myBlock block = ^{
    
    
		NSLog(@"age = %d", age);
	};
	age = 18;
	block();
	输出结果:
	age = 10
  • (2) _ _block 修饰的外部变量
    • 对于用_ _block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。
    • block可以修改_ _block 修饰的外部变量的值。
      在这里插入图片描述
    • 如下示例:
	_block int age = 10;
	myBlock block = ^{
    
    
		NSLog(@"age = %d",age);
	age = 18;
	block();
	输出为:
	age = 18
  • (3)为什么使用_ block 修饰的外部变量的值就可以被block修改呢?
    • 使用clang将OC代码转换为C++文件:clang -rewrite- -objc源代码文件名,便可揭开其真正面纱;
    • 会发现一个局部变量加上_ block修 饰符后竟然跟block-样变成了一个_ Block_ byref. val 0结构体类型的自动变量实例。如下所示:
	_block int val = 10;
	转换成
	__Block_ byref_ _val_ _0 val = {
    
    
	)&val,
	sizeof(_ Block_ byref. val_ 0),
	10
  • 此时我们在block内部访问val变量则需要通过一个叫_ _forwarding的成员变量来间接访问val变量。
  • 带__block的自动变量和静态变量就是直接地址访问,所以在Block里面可以直接改变变量的值。
② 捕获static变量
  • 如下示例代码:
	auto int age = 10;
	static int height = 20;
	void (^block)(void) = ^{
    
    
	    NSLog(@"age is %d, height is %d",age,height);
    };
	age = 20;
    height = 20;
    block();
  • 打印的结果是
	age is 10, height is 20
  • 使用clang将OC代码同样转换 C++ 代码,查看底层:
	{
    
    
        auto int age = 10;
        static int height = 20;
        void (*block)(void) = &__main_block_impl_0(
                                                   __main_block_func_0,
                                                   &__main_block_desc_0_DATA,
                                                   age,
                                                   &height
                                                   );
        age = 20;
        height = 20;
        (block->FuncPtr(block);
    }
  • 可以看到:在定义block时,调用block的构造函数,传递参数时,age传递的是值,而height传递的是指针,看看构造函数内部:
	struct __main_block_impl_0 {
    
    
  	struct __block_impl impl;
  	struct __main_block_desc_0* Desc;
  	int age;//定义 age 变量
  	int *height;//定义一个指针变量,存放外部变量的指针
  	__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    
    
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  • 在block内部,定义了两个变量age、height,不同的是,height是一个指针指针变量,用于存放外部变量的指针。再来看看执行block代码块的内部:
	static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
    
	int age = __cself->age; // bound by copy
  	int *height = __cself->height; // bound by copy
 	// *height : 取出指针变量所指向的内存的值
   	NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_bf6cae_mi_0,age,(*height));
  • 对于age是捕获到内部,把外部age的值存起来,而对于height,是把外部变量的指针保存起来,所以,在修改height时,会影响到block内部的值。
  • 思考:为什么会出现这两种情况?原因很简单,因为auto是自动变量,出了作用域后会自动销毁的,如果我们保留他的指针,就会存在访问野指针的情况。
	// 定义block类型
	void(^block)(void);

	void test() {
    
    
	    int age = 10;
	    static int height = 20;
		// 在block内部访问 age , height
	    block = ^{
    
    
	        NSLog(@"age is %d, height is %d",age,height);
	    };
	    age = 20;
	    height = 20;
	}

	// 在main函数中调用
	int main(int argc, const char * argv[]) {
    
    
        test();
 	// test调用后,age变量就会自动销毁,如果block内部是保留age变量的指针,那么我们在调用block()时,就出现访问野指针
        block();
	}
③ 全局变量
  • 全局变量哪里都可以访问,所以block内部是不会捕获全局变量的,直接访问,这个很好理解,我们直接看代码:

在这里插入图片描述

  • 为什么全局变量不需要捕获?因为全局变量无论哪个函数都可以访问,block内部当然也可以正常访问,所以根本无需捕获。
  • 为什么局部变量就需要捕获呢?因为作用域的问题,我们在一个函数中定义变量,在block内部访问,本质上跨函数访问,所以需要捕获起来。
  • 在Person类中写一个test()方法,在test()方法中定义一个block并访问self,请问block会不会捕获self:
	@implementation Person
	
	- (void)test {
    
    
	    void(^block)(void) = ^{
    
    
	        NSLog(@"会不会捕获self--%@",self);
	    };
	    block();
	}
	
	@end
  • 结果是会捕获self,我们看看底层代码:
	struct __Person__test_block_impl_0 {
    
    
  	struct __block_impl impl;
 	struct __Person__test_block_desc_0* Desc;
  	Person *self;
  	__Person__test_block_impl_0(void *fp, struct 	__Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    
    
    	impl.isa = &_NSConcreteStackBlock;
    	impl.Flags = flags;
    	impl.FuncPtr = fp;
    	Desc = desc;
	  }
	};
  • 很显然block内部的确声明了一个Person *self用于保存self,既然block内部捕获了self,那就说明self肯定是一个局部变量。那问题就来了,为什么self会是一个局部变量?它应该是一个全局变量呀?我们看一下转换后的test()方法:
	static void _I_Person_test(Person * self, SEL _cmd) {
    
    
    	void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
    	((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
	}
  • OC 中的test()方法时没有参数的,但是转换成 C++ 后就多了两个参数self,_cmd,其实我们每个 OC 方法都会默认有这两个参数,这也是为什么我们在每个方法中都能访问self和_cmd,而参数就是局部变量,所以block就自然而然的捕获了self。

四、Block的copy操作

① Block的存储域及copy操作
  • 在开始研究Block的copy操作之前,先来思考一下: Block是存储在栈上还是堆上呢?
  • 我们先来看看一个由C/C+ +/OBJC编译的程序占用内存分布的结构:

在这里插入图片描述

  • 其实,block有 三种类型:
    • 全局块( _NSConcreteGlobalBlock)
    • 栈块(_ NSConcreteStackBlock)
    • 堆块( NSConcreteMallocBlock)
  • 这三种block各自的存储域如下图:

在这里插入图片描述

  • 说明:
    • 全局块存在于全局内存中,相当于单例。
    • 栈块存在于栈内存中,超出其作用域则马上被销毁。
    • 堆块存在于堆内存中,是一个带引用计数的对象,需要自行管理其内存。
  • 简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块,即:
    • block直接存储在全局区;
    • 如果block访问外界变量,并进行block相应拷贝,即copy;
    • 如果此时的block是强引用,则block存储在堆区,即堆区block;
    • 如果此时的block通过__weak变成了弱引用,则block存储在栈区,即栈区block;
  • 那么,遇到一个Block,我们该怎么访问这个Block的存储位置呢?
    • (1) Block不访问外界变量(包括栈中和堆中的变量)
      Block既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。
    • (2) Block访问外界变量
      • MRC环境下:访问外界变量的Block默认存储栈中。
      • ARC环境下:访问外界变量的Block默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。
  • ARC下,访问外界变量的Block为什么要自动从栈区拷贝到堆区呢?栈上的Block,如果其所属的变量作用域结束,该Block就被废弃, 如同一般的自动变量。当然,Block中的_ _block变量也同时被废弃。 如下图:

在这里插入图片描述

  • 为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当地进行判断是否有需要将Block从栈复制到堆,如果有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。如下图:

在这里插入图片描述

  • 如下面一个返回值为Block类型的函数:
	typedef int (^blk_t)(int);
	blk_t func(int rate) {
    
    
		return ^(int count) {
    
     
			return rate * count; 
		};
	}
  • 分析可知:
    • 上面的函数返回的Block是配置在栈上的,所以返回函数调用方时,Block变 量作用域就结束了,Block会被废弃。但在ARC有效,这种情况编译器会自动完成复制。
    • 在非ARC情况下则需要开发者调用copy方法手动复制,由于开发中几乎都是ARC模式,所以手动复制内容不再过多研究。
    • 将Block从栈上复制到堆上相当消耗CPU,所以当Block设置在栈上也能够使用时,就不要复制了,因为此时的复制只是在浪费CPU资源。
  • Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法的效果如下表:
Block类 副本源的配置存储域 复制效果
__NSConcreteStackBlock 从栈复制到堆
__NSConcreteGlobalBlock 程序的数据区域 什么也不做
__NSConcreteMallocBlock 引用计数增加
  • 根据表得知,Block在 堆中copy会造成引用计数增加,这与其他Objective-C对象是一样的。虽然Block在栈中也是以对象的身份存在,但是栈块没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。
  • 不管Block存诸域在何处,用copy方 法复制都不会引起任何问题。在不确定时调用copy方法即可。
  • 在ARC有效时,多次调用copy方法完全没有问题:
	// 经过多次复制,变量blk仍然持有Block的强引用,该Block不会被废弃。
	blk = [[[[blk copy] copy] copy] copy];
② _ block变量与_ forwarding
  • 在copy操作之后,既然_ block变量也被copy到堆上去了,那么访问该变量是访问栈上的还是堆上的呢?_ forwarding 终于要闪亮登场了,如下图:

在这里插入图片描述

  • 通过_ forwarding,无论是在block中还是block外访问_ block变量,也不管该变量在栈上或堆上,都能顺利地访问同一个_block变量。

五、防止Block循环引用

① 解决循环引用

Block循环引用的情况:某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,如下:

	self.myBlock = ^(void){
    
    
		[self doSomething]; 
	};

解决办法:

  • (1) ARC下:使用__weak,此时的 weakSelf 和 self 指向同一片内存空间,且使用__weak 不会导致 self 的引用计数发生变化。
	typedef void(^YDWBlock)(void);
	@property (nonatomic, copy) YDWBlock myBlock;

	__weak typeof(self) weakSelf = self;
	self.myBlock = ^(void){
    
    
		[weakSelf doSomething];
	};
  • (2)如果 block 内部嵌套 block,需要同时使用__weak 和 __strong:其中strongSelf 是一个临时变量,在 myBlock 的作用域内,即内部 block 执行完就释放strongSelf,这种方式属于打破 self 对 block 的强引用,依赖于中介者模式,属于自动置为nil,即自动释放。
	__weak typeof(self) weakSelf = self;
	self.myBlock = ^(void){
    
    
	    __strong typeof(weakSelf) strongSelf = weakSelf;
	    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
	        NSLog(@"%@", strongSelf.name);
	    });
	};
	self.myBlock();
  • (3) MRC下:使用__block
	__block typeof(self) blockSelf = self;
	self.myBlock = ^(void) {
    
     
		[blockSelf doSomething];
	};
  • (4)值得注意的是,在ARC下,使用_ block 也有可能带来循环引用,如下:
	// 循环引用 self ->_ attributBlock -> tmp -> self
	typedef void (^Block)();
	
	@interface TestObj : NS0bject {
    
    
		Block_ attributBlock;
	}
	@end
	
	@implementation TestObj
	
	- (id)init {
    
    
		self = [super init];
		__block id tmp = self;
		self.attributBlock = ^{
    
    
			NSLog(@"Self = %@", tmp);
			tmp = nil;
		};
	}
	- (void)execBlock {
    
    
		self.attributBlock();
	}

	@end

	// 使用类
	id obj = [[Test0bj alloc] init];
	[obj execBlock]; // 如果不调用此方法,tmp 永远不会置 nil, 内存泄露会一直在 
  • (5)__block修饰变量:这种方式同样依赖于中介者模式,属于手动释放,是通过__block修饰对象,主要是因为__block修饰的对象是可以改变的,需要注意的是这里的 block 必须调用,如果不调用 block,vc 就不会置空,那么依旧是循环引用,self 和 block 都不会被释放。
	__block ViewController *vc = self;
	self.myBlock = ^(void){
    
    
	    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
	        NSLog(@"%@",vc.name);
	        // 手动释放
	        vc = nil;
	    });
	};
	self.myBlock();
  • (6)对象 self 作为参数:将对象 self 作为参数,提供给 block 内部使用,不会有引用计数问题。
	typedef void(^YDWBlock)(ViewController *);
	
	@property(nonatomic, copy) YDWBlock myBlock;
	
	self.myBlock = ^(ViewController *vc){
    
    
	    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
	        NSLog(@"%@",vc.name);
	    });
	};
	self.myBlock(self);
② NSProxy 虚拟类
  • OC 是单继承的语言,但是它是基于运行时的机制,所以可以通过 NSProxy 来实现 伪多继承,填补了多继承的空白;
  • NSProxy 和 NSObject 是同级的一个类,也可以说是一个虚拟类,只是实现了NSObject 的协议;
  • NSProxy 其实是一个消息重定向封装的一个抽象类,类似一个代理人,中间件,可以通过继承它,并重写下面两个方法来实现消息转发到另一个实例,如下:
	- (void)forwardInvocation:(NSInvocation *)invocation;
	- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
  • NSProxy 的使用场景主要有两种:
    • 实现多继承功能;
    • 解决了NSTimer&CADisplayLink创建时对self强引用问题,参考YYKit的YYWeakProxy。
  • 循环引用解决原理:主要是通过自定义的 NSProxy 类的对象来代替 self,并使用方法实现消息转发。
  • 自定义一个 YDWProxy 继承于 NSProxy,如下:
	@interface YDWProxy()
	
	@property(nonatomic, weak, readonly) NSObject *objc;
	
	- (id)transformObjc:(NSObject *)objc;
	+ (instancetype)proxyWithObjc:(id)objc;
	
	@end
	
	@implementation YDWProxy
	
	- (id)transformObjc:(NSObject *)objc{
    
    
	   _objc = objc;
	    return self;
	}
	
	+ (instancetype)proxyWithObjc:(id)objc{
    
    
	    return  [[self alloc] transformObjc:objc];
	}
	
	// 查询该方法的方法签名
	- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    
    
	    NSMethodSignature *signature;
	    if (self.objc) {
    
    
	        signature = [self.objc methodSignatureForSelector:sel];
	    } else{
    
    
	        signature = [super methodSignatureForSelector:sel];
	    }
	    return signature;
	}
	
	// 有了方法签名之后就会调用方法实现
	- (void)forwardInvocation:(NSInvocation *)invocation{
    
    
	    SEL sel = [invocation selector];
	    if ([self.objc respondsToSelector:sel]) {
    
    
	        [invocation invokeWithTarget:self.objc];
	    }
	}
	
	- (BOOL)respondsToSelector:(SEL)aSelector{
    
    
	    return [self.objc respondsToSelector:aSelector];
	}
	
	@end
  • 自定义如下两个类:
	// ********Cat类********
	@interface Cat : NSObject
	@end
	
	@implementation Cat
	- (void)eat {
    
    
	   NSLog(@"猫吃鱼");
	}
	@end
	
	// ********Dog类********
	@interface Dog : NSObject
	@end
	
	@implementation Dog
	- (void)shut {
    
    
	    NSLog(@"狗叫");
	}
	@end
  • 通过 YDWProxy 实现多继承功能:
	- (void)proxyTest {
    
    
	    Dog *dog = [[Dog alloc] init];
	    Cat *cat = [[Cat alloc] init];
	    YDWProxy *proxy = [YDWProxy alloc];
	    
	    [proxy transformObjc:cat];
	    [proxy performSelector:@selector(eat)];
	    
	    [proxy transformObjc:dog];
	    [proxy performSelector:@selector(shut)];
	}
  • 通过 YDWProxy 解决定时器中 self 的强引用问题:
	self.timer = [NSTimer timerWithTimeInterval:1 target:[YDWProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
	[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

六、Block的使用示例

① Block作为变量(Xcode快捷键: inlineBlock)
	int (^sum) (int, int); // 定义一个 block 变量 sum
	// 给 Block 变量赋值
	// 一般返回值省略: sum = ^(int a,int b)..
	sum = ^int (int a,int b) {
    
    
		return a+b;
	}; // 赋值语句最后有分号
	int a = sum(10,20); // 调用 block 变量
② Block作为属性(Xcode 快捷键: typedefBlock)
	// 1.给Calculate 类型sum变量赋值「下定义」
	typedef int (^Calculate)(int, int); // calculatet 就是类型名
	Calculate sum = ^(int a,int b)[
		return a+b;
	;
	int a = sum(10,20); // 调用 sum 变量
	// 2.作为对象的属性声明,copy 后 block 会转移到堆中和对象一起
	@property (nonatomic, сору) Calculate sum; // 使用 typedef
	@property (nonatomic, сору) int (~sum)(int, int); // 使用 typedef
	// 声明,类外
	self.sum = ^(int a,int b) {
    
    
		return a+b;
	}
	// 调用,类内
	int a = self.sum(10,20);
③ 作为OC的方法参数
	// 无参数传递的Block
	// 实现
	- (CGFloat )testTimeConsume: (void(^)())middleBlock {
    
    
		// 执行前记录下当前的时间
		CFTimeInterval startTime = CACurrentMediaTime();
		middleBlock();
		// 执行后记录下当前的时间
		CFTimeInterval endTime = CACurrentMediaTime();
		return endTime - startTime;
	}
	// 调用
	[self testTimeConsume:^{
    
    
	
	};
	
	// 有参数传递的Block
	// 实现
	- (CGFloat)testTimeConsume: (void(^) (NSString * name) )middleBlock {
    
    
		// 执行前记录下当前的时间
		CFTimeInterval startTime = CACurrentMediaTime();
		NSString *name = @"有参数";
		middleBlock(name);
		// 执行后记录下当前的时间
		CFTimeInterval endTime = CACurrentMediaTime();
		return endTime - startTime;
	}
	// 调用
	[self testT imeConsume :^(NSString *name) {
    
    
		// 放入block中的代码,可以使用参数name
		// 参数name是实现代码中传入的,在调用时只能使用,不能传值
	}];
④ Block回调
  • Block回调是关于Block最常用的内容,比如网络下载,我们可以用Block实现下载成功与失败的反馈。开发者在block没发布前,实现回调基本都是通过代理的方式进行的,比如负责网络请求的原生类NSURLConnection类,通过多个协议方法实现请求中的事件处理。
  • 而在最新的环境下,使用的 NSURLSession 已经采用block的方式处理任务请求了。各种第三方网络请求框架也都在使用block进行回调处理。这种转变很大一部分原因在于block使用简单,逻辑清晰,灵活等原因。如下:
	// DownloadManager.h
	#import <Foundat ion/ Foundat ion. h>
	@interface DownloadManager : NS0bject <NSURL .Sess ionDownloadDelegate>
	// block 重命名
	typedef void (^DownloadHandler) (NSData * receiveData, NSError * error);
	- (void)downloadWithURL: (NSString * )URL parameters: (NSDictionary *)parameters handler: (DownloadHandler hanlder);
	@end
	
	// DownloadManager.m
	#import "Down loadManager.h"
	@implementation DownloadManager
	
	- (void)downloadWithURL: (NSString * )URL parameters : (NSDictionary *)parameters handler: (DownloadHandler hanlder) {
    
    
	NSURLRequest * request = [NSURLRequest reques tWithURL: [NSURL URLWithString:URL]];
	NSURLSession * session = [NSURLSession sharedSession] ;
	// 执行请求任务
	NSURLSess ionDataTask * task = [session dataTaskWithRequest: request complet ionHandler:^(NSData *data) {
    
    
		if (handler) (
			dispatch_ async(dispatch_ get_ _main_ _queue(), ^{
    
    
				handler(data,error);
			));
		}
	)};
	[task resume];
  • 上面通过封装 NSURLSession 的请求,传入一个处理请求结果的 block 对象,就会自动将请求任务放到工作线程中执行实现,我们在网络请求逻辑的代码中调用如下:
	- (IBAction)buttonClicked: (id)sender {
    
    
	#define DOWNLOADURL @"https://codeload. github. com/ AFNetworking/ AFNetworking/zip/master"
	// 下载类
	DownloadManager *downloadManager = [[DownloadManager alloc] init];
	[downloadManager downloadWithURL: DOWNLOADURL parameters:nil handler:^(NSData *receiveData, NSError *error) {
    
    
		if (error) {
    
    
			NSLog(@"下载失败:%@", error);
		} else {
    
    
			NSLog(@"下载成功:%@", receiveData);
		}];
	}

为了加深理解,再来一个简单的小例子:

  • A、B两个界面,A界面中有一个label,一个buttonA。点击buttonA进入B界面,B界面中有一个UlTextfield和一个buttonB, 点击buttonB退出B界面并将B界面中UlTextfield的值传到A界面中的label。
  • A界面中,也就是ViewController类中:
	// 关键demo:
	 - (IBAction)buttonAction {
    
    
		MyFirstViewController *myVC = [[MyFirstViewController alloc] init];
		[self presentViewController:myVC animated:YES completion:^{
    
    
			
		]]; 
		// 防止循环引用
		__weak typeof(self) weakSelf = self;
		// 用属性定义的注意:这里属性是不会自动补全的,方法就会自动补全
		[myVC setBlock: (NSString *string) ^{
    
    
			weakSelf.labelA.text = string;
		)};
	}
  • B界面中,也就是MyFirstViewController类中.m文件:
	- (IBAction)buttonAction {
    
    
		[self dismissViewControllerAnimated: YES completion:^{
    
    
			
		)];
		self.block(_myTextfielf.text);
	}
  • .h文件:
	#import <UIKit/UIKit.h>
	
	// typedef定义一下block,为了更好用
	typedef void(^MyBlock) (NSString *string);
	@interface MyFirstViewController : UIViewController
	@property (nonatomic, сору) MyBlock block;
	@end
  • 看到以上的两个block回调,是不是感觉比delegate清爽好多?

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/114832834