iOS 关于Block代码块的详解

概述

block

上图就是一个block简单使用,它包括了block的声明赋值实现调用 三个部分,其中,实现部分可以看作是一种匿名函数;跟函数一样,block也是需要调用才能执行内部代码的;赋值的行为又让block看起来跟数据类型类似

代码块Block是在iOS4开始引入的,是对C语言的扩展,用来实现匿名函数的特性

Block是一种特殊的数据类型,可以像基本数据类型一样定义成变量、作为参数、返回值来使用

Block还可以保存一段代码,在需要的时候调用

在iOS开发中,Block被系统应在很多地方,例如:GCD、UIView动画、排序等,我们开发者也可以应用在各类回调、传值、传消息等

Block的声明、赋值实现、调用

Block的声明样式。

返回类型 (^Block名称)(参数列表)

void(^myBlock)(NSString *, NSString *)

Block的返回类型分为有返回类型和无返回类型(void),参数列表也可有也可以没有,具体看需求

// 无返回类型无参数列表
void(^block)();
// 无返回类型有参数列表
void(^block)(int);
// 有返回类型无参数列表
int(^block)();
// 有返回列表有参数列表
int(^block)(int);

Block的简单使用:声明、赋值、调用

Block变量的赋值格式为:

Block变量 = ^(参数列表){函数体}; // 这里的参数列表一定要和声明时的参数列表一致

// 声明
void(^block)();
// 赋值
block = ^(){
	NSLog(@"Hello World");
};
// 调用
block();

也可以在声明时完成赋值

// 声明、赋值
void(^block)() = ^(){
	NSLog(@"Hello World");
};
// 调用
block();

定义Block类型

前面提到过,Block是一种特殊的数据类型,我们可以使用 typedef 来定义 Block 类型,这样我们就可以使用该类型来声明很多相同的Block变量了。

typedef 返回类型(^Block名称)(参数列表);

示例:

// 声明一个Block类型
typedef void(^Block)();

// 使用定义两个block变量
Block myBlock,myNewBlock;
// 赋值实现
myBlock = ^(){
	NSLog(@"Hello World");
};
myNewBlock = ^(){
	NSLog(@"Hello World, I am lolita0164.");
};
// 调用
myBlock();
myNewBlock();

ARC模式下简单应用

  • 作为对象属性实现消息传递

前面说到,Block保存一段代码,在需要的时候调用。我们可以将使用Block的三个步骤拆开,实现消息传递、传值功能。

// 定义Block类型
typedef void(^Block)(NSString *);

@interface Person : NSObject
// 声明Block变量
@property (nonatomic, copy) Block myBlock;
-(void)sayHello;
@end

@implementation Person
-(void)sayHello{
    // Block调用
    self.myBlock(@"Hello, I am lolita0164");
}
@end

在定义声明、调用之后,还缺少实现的部分,这一步通常由外部实现。

Person *p = [Person new];
// Block赋值实现
p.myBlock = ^(NSString *string) {
	NSLog(@"%@",string);
};
[p sayHello];

这样,我们就可以在Block的赋值实现部分里拿到 p类里的数据了。

  • 作为函数参数实现数据回调

我们将之前的例子稍加改动

// 定义Block类型
typedef void(^Block)(NSString *);
@interface Person : NSObject
// 将Block作为参数
-(void)sayHelloUseBlock:(Block)myBlock;
@end

@implementation Person
-(void)sayHelloUseBlock:(Block)myBlock{
	// Block调用
    myBlock(@"Hello, I am lolita0164");
}
@end

在外部进行实现。

Person *p = [Person new];
// Block实现
[p sayHelloUseBlock:^(NSString *string) {
	NSLog(@"%@",string);
}];

作为参数和作为属性传递消息,在应用场景稍稍有些不同。
作为参数时,通常和当前的方法有着紧密的联系,函数体内部需要与调用的外部进行交互。例如在请求方法中,经常会使用到block进行回调,而这个block和当前的方法关系紧密,通常是该方法的结果回调。又或者是方法执行期间需要外部提供一定的信息,从而通过block获取外部提供的数据。
作为属性时,通常是和当前类相关,作为类与类之间的交互代表。

  • 作为返回值实现链式语法

将block作为返回值的经典例子就是约束库 masonry,这个库在做完每次约束设置之后通过 block 将实例再次回调,就形成了链式语法。

[view makeConstraints:^(MASConstraintMaker *make) {
	make.left.equalTo(view1.mas_right).offset(10); 
	make.top.equalTo(view1).offset(0);
	make.right.equalTo(-10);
	make.size.equalTo(viewWidth);
}];

下面通过创建颜色类来演示 block 作为参数的使用。

// block 作为返回值
+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
    // block 的声明和实现
    UIColor* (^rgbBlock)(CGFloat, CGFloat, CGFloat) = ^id(CGFloat r, CGFloat g, CGFloat b) {
        return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
    };
    return rgbBlock;
}

解析

返回值是一个有返回值参数有三个的block:UIColor * (^)(CGFloat, CGFloat, CGFloat)。我们在该方法的内部进行了block的声明和具体实现,并且将其作为返回值返回了出去,那么外部在调用该方法之后接收到的是一个block,可以使用该值。

那么外部使用情况如下。

// 接收 block 类型
UIColor* (^colorBlock)(CGFloat, CGFloat, CGFloat) = [UIColor rgb];
// 使用 block 获取到颜色
UIColor* color = colorBlock(10,33,65);
self.view.backgroundColor = color;

在丢弃不需要的部分后,代码如下。

+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
    return ^id(CGFloat r, CGFloat g, CGFloat b) {
        return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
    };
}
// 使用block作为参数的方法
self.view.backgroundColor = UIColor.rgb(10, 33, 65);

Block 和 变量

Block访问局部变量问题

block 内部可以访问局部变量。

int global = 100;
void (^Block)() = ^(){
	NSLog(@"global = %i", global);
};
Block(); // 输出 "global = 100"

但是 block 会把变量 复制 为自己私有的const变量,也就是说block会捕获栈上的变量(或指针),将其复制为自己私有的const变量,当变量被修改时,不会影响到block自己私有的const变量。

int global = 100;
void (^Block)() = ^(){
	NSLog(@"global = %i", global);
};
global = 101;
Block(); // 输出 "global = 100"

在Block中不可以直接修改局部变量。

int global = 100;
void (^Block)() = ^(){
	global ++; // 这句报错
	NSLog(@"global = %i", global);
};
Block();

但是可以通过 __block 修饰符修改局部变量。

__block int global = 100;
void (^Block)() = ^(){
	NSLog(@"global = %i", global);
};
global = 101;
Block();   //输出 "global = 101"
__block int global = 100;
void (^Block)() = ^(){
	global ++; // 这句正确
	NSLog(@"global = %i", global);
};
Block();   //输出 "global = 101"

原因:在局部变量前使用 __block修饰 ,在Block定义时便是将局部变量的指针传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时内部的值也是可以修改的。

Block访问全局变量、静态变量问题

可以访问和修改。

全局变量所占用的内存只有一份,供所有函数共同调用,在Block定义时并未将全局变量的值或者指针传给Block变量所指向的结构体,因此在调用Block之前对全局变量进行修改会影响Block内部的值,同时内部的值也是可以修改的。

在Block定义时便是将静态变量的指针传给Block变量所指向的结构体,因此在调用Block之前对静态变量进行修改会影响Block内部的值,同时内部的值也是可以修改的。

ARC下的内存管理

在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,我们只需要避免循环引用即可。

// 当Block变量出了作用域,Block的内存会被自动释放
void(^myBlock)() = ^{
    NSLog(@"------");
};
myBlock();

如果在Block中引用了外面的对象,会对所引用的对象进行强引用,但是在Block被释放时会自动去掉对该对象的强引用,因此比并不会造成内存泄漏问题。

Person *p = [[Person alloc] init];
void(^myBlock)() = ^{
    NSLog(@"------%@", p);
};
myBlock();
// Person对象在这里可以正常被释放
// 注:这里的Block只是单方面的强引用,所以不会产生循环引用,也不会内存泄漏

如果对象内部引用一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,导致内存泄漏。

self.block = ^{
	NSLog(@"------%@", self);
};

解决办法:使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样就避免了Block对对象进行强引用。

__weak typeof(self) weakSelf = self;
weakSelf.block = ^{
	NSLog(@"------%@", weakSelf);
};

提示:如果只是Block单方面地对外部变量进行强引用,并不会造成内存泄漏。

补充

1、声明block属性的时候为什么用copy呢?

在说明为什么要用copy前,先思考下block是存储在栈区还是堆区呢?其实block有3种类型:

  1. 全局块(_NSConcreteGlobalBlock)
  2. 栈块(_NSConcreteStackBlock)
  3. 堆块(_NSConcreteMallocBlock)

全局块存储在静态区(也叫全局区),相当于OC中的单例;栈块存储在栈区,超出作用域则马上被销毁。堆块存储在堆区中,是一个带引用计数的对象,需要自行管理其内存。

关于内存分配,请看这篇:C语言内存分配

怎么判断一个block所在的存储位置呢?

  • block不访问外界变量(包括栈中和堆中的变量)

block既不在栈中也不在堆中,此时就为全局块,ARC和MRC下都是如此。

  • block访问外面变量

MRC环境下:默认存储在栈区
ARC环境下:默认存储在堆中,实际上是先放在栈区,在ARC情况下自动又拷贝到堆区,自动释放

因此,使用 copy 修饰符的作用就是将block从栈区拷贝到堆区

为什么要这么做呢?官方给出的答案是:

复制到堆区的主要目的就是 保存 block 的状态,延长其声明周期。因为block如果在栈上的话,其所属的变量作用域结束,该block就被释放掉了,block中的 __block 变量也同时被释放掉了,为了解决超出作用域就被释放的问题,我们就需要把block复制到堆中。

总结

OC 中的 block 是对 C 语言的匿名函数的一种特性是实现。block 具有函数特性,同时也可以作为变量使用。block 可以作为属性、参数、返回值使用。想要在 Block 内部修改外部变量时,需要使用 __Block 将变量指针传递给 block。在使用 Block 时需要特别注意内存泄漏的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值