iOS-Blocks「一」

108 阅读5分钟

什么是Blocks

Blocks是带有局部变量的匿名函数

Blocks也被称为闭包、代码块,Blocks就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候去调用

局部变量:作用域仅限于函数内部,离开函数后就是无效的,再使用就会报错

匿名函数:不带有名称的函数(但是C语言不允许存在这样的函数)

int fun(int a);

fun就是这个函数的名称,再调用的时候必须使用fun这个函数名来进行调用,例如

int result = fun(10);

我们还可以通过 函数指针来直接调用函数

int (*funPtr)(int) = &fun
int result = (*funPtr)(10);

可以看到我们在给函数指针赋值的时候,还是需要知道函数名

而我们通过使用Blocks,可以直接使用函数,不用给函数名

Blocks变量语法

我们使用 ^ 运算符来声明Blocks变量,并将Blocks对象主体部分包含在 {} 中,句尾加 ; 表示结束

官方示例

int multiplier = 7;
int (^ myBlock)(int) = ^(int num){
	return num * multiplier;
};

myBlock是声明的块对象,返回类型是整型值,myBlock块对象有一个参数,参数类型是整型值,参数名为num,myBlock块对象的主体部分为 return num * multiplier;

img

我们可以将Blocks表达式语法表述为

^ 返回值类型(参数列表){ 表达式 };

例如

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

Blocks可以省略返回值类型、参数列表

省略返回值类型:^(参数列表){ 表达式 };

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

返回值类型可以省略不写,也可以用void表示

省略参数列表:^ 返回值类型 { 表达式 };

^ int {return 1;};
^ int (void) {return 1;};

参数列表可以省略不写,也可以用void表示

省略返回值类型、参数列表:^ { 表达式 };

^ {return 1;};
^ {printf("Blocks!");};

Blocks变量的声明与赋值

语法

Blocks变量的声明与赋值语法可以总结为

返回值类型 (^变量名)(参数列表)= Blocks表达式

⚠️:此处返回值类型不可省略,若无返回值,则使用void作为返回值类型

举个例子:定义一个变量名为blk的Blocks变量

int (^blk)(int) = ^(int count){return count + 1;};
int (^blk1)(int);
blk1 = blk;

使用

作为局部变量

返回值类型(^变量名)(参数列表)= 返回值类型(参数列表){ 表达式 };

我们将Blocks变量作为局部变量,在一定范围内(函数、方法内部)使用

-(void)useBlockAsLocalVariable{
    void(^myLocalBlock)(void) = ^{
        NSLog(@"useBlockAsLocalVariable");
    };
    myLocalBlock();
}

作为带有property声明的成员变量

@property(nonatomic,copy) 返回值类型 (^变量名)(参数列表);

@property(nonatomic,copy) void(^myPropertyBlock)(void);

-(void)useBlockAsProperty{
   self.myPropertyBlock = ^{
        NSLog(@"useBlockAsProperty");
    };
    self.myPropertyBlock();
}

作为OC方法参数

-(void)someMethodThatTaskesABlock : (返回值类型 (^)(参数列表))变量名;

可以把Block是变量作为OC方法中的一个参数来使用,通常blocks变量写在方法名的最后

-(void)someMethodThatTaskesABlock : (void (^)(NSString *))block{
	block(@"someMethodThatTaskesABlock");
};

调用含有Blocks参数的OC方法

[someObject someMethodThatTaskesABlock : ^返回值类型(参数列表){表达式}];

-(void)useBlockAsMethodParameter {
	[someObject someMethodThatTaskesABlock : ^(NSString *str){
		NSLog(@"%@",str);
	}];
}

通过后面两个场景,Blocks变量作为OC方法参数的调用,我们同样可以实现类似delegate的作用,即Blocks回调

作为typedef声明类型

typedef 返回值类型(^声明名称)(参数列表);
声明名称 变量名 = ^返回值类型(参数列表){表达式};
//Blocks变量作为 typedef 声明类型
-(void)useBlockAsATypedef{
	typedef void (^TypeName)(void);
	//之后就可以使用 TypeName 来定义无返回值类型、无参数列表的block了
	TypeName myTypedefBlock = ^{
		NSLog(@"useBlockAsATypedef");
	};
	myTypedefBlock();
}

Blocks变量截获局部变量值特性

// 使用 Blocks 截获局部变量值
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;

    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d\n",a, b);
    };

    myLocalBlock();    // 打印结果:a = 10, b = 20

    a = 20;
    b = 30;

    myLocalBlock();    // 打印结果:a = 10, b = 20
}

会发现,在修改了a、b的值后,再次调用myLocalBlock();后使用的还是之前对应变量的值?

因为Block语法的表达式使用的是它之前声明的局部变量a、b

Blocks中,Block表达式截获所使用的局部变量的值,保存了该变量的瞬时值

因此之后即使改变局部变量的值,也不会影响Block表达式在执行所保存的局部变量的瞬时值

使用__block说明符

在使用block表达式的时候,只能使用保存的局部变量的瞬时值,不能直接对其进行改写,改写的话编译器会直接报错

那么如果我们想要改写Block表达式中截获的局部变量的值,该怎么办?

如果我们想在Block表达式中,改写Block表达式之外声明的局部变量,需要在该局部变量前加上__block的修饰符

- (void)useBlockQualifierChangeLocalVariables {
    __block int a = 10, b = 20;
    void (^myLocalBlock)(void) = ^{
        a = 20;
        b = 30;
        
        printf("a = %d, b = %d\n",a, b);  // 打印结果:a = 20, b = 30
    };
    
    myLocalBlock();
}

Blocks变量的循环引用

Block会对引用的局部变量进行持有,同样,Block也会对引用的对象进行持有,从而导致相互持有,引起循环引用

/* —————— retainCycleBlcok.m —————— */ 
#import <Foundation/Foundation.h>
#import "Person.h"
int main() {
    Person *person = [[Person alloc] init];
    person.blk = ^{
        NSLog(@"%@",person);
    };

    return 0;
}


/* —————— Person.h —————— */ 
#import <Foundation/Foundation.h>

typedef void(^myBlock)(void);

@interface Person : NSObject
@property (nonatomic, copy) myBlock blk;
@end


/* —————— Person.m —————— */ 
#import "Person.h"

@implementation Person    

@end

retainCycleBlcok.m中,main()函数的代码会导致一个问题

person持有成员变量myBlock blk,而blk也同时持有成员变量person,导致相互引用,永远无法释放

那么如何解决?

ARC:__weak

在ARC下,可声明附有__weak修饰符的变量,并将对象赋值使用

int main() {
    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;
    
    person.blk = ^{
    	NSLog(@"%@",weakPerson);
    };
    
    return 0;
}

通过__weak,person持有成员变量myBlock blk,而blk对person进行弱引用,从而消除了循环引用

MRC: __block

MRC模式下,不支持使用weak修饰符,但是我们可以通过block来消除循环引用

int main() {
    Person *person = [[Person alloc] init];
    __block typeof(person) blockPerson = person;
    
    person.blk = ^{
    	NSLog(@"%@",blockPerson);
    };
    
    return 0;
}

通过__block引用blockPerson,是通过指针的方式来访问person,因此blk没有对person进行强引用,所以不会造成循环引用


参考博客:bujige.net/blog/iOS-Bl…