Block初识

一、Block声明1、普通声明returnType为返回值 ,blockName为block的名称, parameter为参数,多个参数用,隔开returnType (^blockName) (parameter) = ^ returnType (parameter){};上面为Block的完整声明,像返回值,参数等是可以省略的,如下 void (^block1)(void) =...
摘要由CSDN通过智能技术生成

一、Block声明
1、普通声明

returnType为返回值 ,blockName为block的名称, parameter为参数,多个参数用,隔开
returnType (^blockName) (parameter) = ^ returnType (parameter){};

上面为Block的完整声明,像返回值,参数等是可以省略的,如下

   void (^block1)(void) = ^{
        NSLog(@"没有返回值,没有参数的最简写法");
    };
    void (^block2)(void) = ^void (void){
        NSLog(@"没有返回值,没有参数的完整的写法");
    };
    void (^block3)(void) = ^(void){
        NSLog(@"没有返回值,没有参数的只写参数");
    };
    //------------------------------------------
    NSInteger (^block4)(void) =  ^NSInteger (void){
        NSLog(@"有返回值,没有参数");
        return 3;
    };
    NSInteger (^block5)(void) =  ^NSInteger {
        NSLog(@"有返回值,没有参数简写");
        return 3;
    };
    
    //-------------------------------------------
    void (^block6)(NSInteger num) = ^void (NSInteger num){
        NSLog(@"没有返回值,有参数");
    };
    
    void (^block7)(NSInteger num) = ^(NSInteger num){
        NSLog(@"没有返回值,有参数简写");
    };

    //block调用
	    block1();
	    block2();
	    block3();
	    block4();
	    block5();
	    block6(2);
	    block7(3);

2、匿名声明

//匿名block调用时,直接在后面写上就可以,有参数写参数,无参数直接是()
   ^NSInteger {
       NSLog(@"匿名block");
       return 6;
   }();
    //匿名block有参数的形式
    ^NSInteger(NSInteger v){
        NSLog(@"匿名参数 -- %ld",v);
        return v;
    }(5555555555);

3、typedef简化Block(常用的block传值)

Test1ViewController.h

#import <UIKit/UIKit.h>
//
typedef void(^Block8)(void);

@interface Test1ViewController : UIViewController
//
@property (nonatomic ,copy) Block8 block8;
//
-(void)callBlock8;

@end

Test1ViewController.m
//
-(void)callBlock8{
    self.block8();
}

ViewController.m

Test1ViewController *test1VC = [[Test1ViewController alloc] init];
   //
    test1VC.block8 = ^{
        NSLog(@"用typedef简化block");
    };
    //调用
    [test1VC callBlock8];

4、typedef声明的block作为参数

Test1ViewController.h
#import <UIKit/UIKit.h>

//block做为参数
typedef void(^handleBlock)(void);

@interface Test1ViewController : UIViewController

//typedef简化的block作为参数
-(void)blockWithNum:(NSInteger)num handleBlock:(handleBlock)handle;

@end

Test1ViewController.m

-(void)blockWithNum:(NSInteger)num handleBlock:(handleBlock)handle{
    NSLog(@"block作为参数");
    //此方法一定要写,否则调用时不会走代码块中的内容
    handle();
}

ViewController.m

//typedef声明的代码块做为方法中的参数
    [test1VC blockWithNum:3 handleBlock:^{
        NSLog(@"走了吗");
    }];

5、匿名Block作为参数

匿名block可以直接在方法里做为参数进行传递
method为方法,冒号后面为匿名block做为参数
method:(returnType (^)(parmaer))block{
这句话一定要写,否则再调用此方法时,不会走代码块中的内容
block(parameter);
}

Test1ViewController.h
-(void)blockWithNoParamer:(NSInteger)num block:(void (^) (NSInteger num))block;

Test1ViewController.m
-(void)blockWithNoParamer:(NSInteger)num block:(void (^)(NSInteger nums))block{
    NSLog(@"隐匿block作为参数 - %ld",num);
    block(10);
}
ViewController.m
 [test1VC blockWithNoParamer:5 block:^(NSInteger num) {
        NSLog(@"%ld",num);
    }];

6、匿名Block作为返回值

Test1ViewController.h
-(void (^)(NSInteger))blockWithNums:(NSInteger)nums;

Test1ViewController.m
-(void (^)(NSInteger))blockWithNums:(NSInteger)nums{
    return ^void (NSInteger num1){
        NSLog(@"block作为返回值 - %ld",nums);
    };
}

ViewController.m
/**
     *  block作为返回值时 需要再次调用一次 才能走方法里的内容
     **/
    block6 = [test1VC blockWithNums:9];
    block6(99999);

二、Block截获变量
1、截获变量

Block所在函数中,捕获自动变量,但是不能修改他,不然就是编译错误,但是可以改变全局变量,静态变量,全局静态变量,理解如下:
*不能修改自动变量的值是因为Block捕获的是自动变量的const值,名字一样,不能修改
*可以修改静态变量的值是因为静态变量是属于类的,不是某一个变量,由于Block内部不用调用self指针,所以Block可以调用
*如果修改自动变量,就需要添加__block
***__block保证了栈上和Block内(通常在堆上)可以访问和修改“同一个变量”,将栈上用__block修饰的自动变量封装成一个结构体,让其在堆上创建,以便从栈上或堆上访问和修改同一份数据

 __block int val = 10;
    static int sVal = 6;
   testVal = 10;
    void (^block)(void) = ^{
        //如果没有添加__block,在这个里面是不可以对val进行修改的 会报编译错误
        val = 5;
        sVal = 89; //静态变量
        testVal = 45; //全局变量
        allSVal = 90; //全局静态变量
        NSLog(@"val = %d,staticVal = %d,testVal = %ld,allSval = %d",val,sVal,testVal,allSVal);
        
    };
    val = 3;
    //不加__block之前 打印的结果是10,加了__block之后结果就变成了3 前提是block中没有val = 5这句话 否则就是5
    block();

2、截获对象

block捕获OC对象时,不同于基本类型,Block会引起对象的引用计数变化

static NSObject *staticObject = nil;
    staticObject = [[NSObject alloc] init];
    allObject = [[NSObject alloc] init];
    NSObject *object1 = [[NSObject alloc] init];
    __block NSObject *blockObject1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];

    
    void (^testBlocks)(void) = ^{

        NSLog(@"staticObject1 - %ld",CFGetRetainCount((__bridge CFTypeRef)staticObject));
        NSLog(@"allObject1 - %ld",CFGetRetainCount((__bridge CFTypeRef)allObject));
        NSLog(@"bockeObject1 - %ld",CFGetRetainCount((__bridge CFTypeRef)blockObject1));
        NSLog(@"Object1 - %ld",CFGetRetainCount((__bridge CFTypeRef)object1));
        NSLog(@"object2 - %ld",CFGetRetainCount((__bridge CFTypeRef)object2));

    };

    testBlocks();

打印结果 在这里插入图片描述

  • 最终结果:除了block1和block2的retainCount是3以外,其他的都是1
    * 对象的引用计数并不是简单的+1,而是加2,这是由于block在创建的时候在栈上,而在赋值给全局变量的时候,被拷贝到了堆上
    * 全局对象和静态对象因为在内存中的位置是确定的,所以即便在block中调用了 对其retainCount也不会有影响
    * 对于使用了__block的本地变量,也不会对其retainCount产生影响

三、Block类型

根据Block创建的位置不同,Block有三种类型:
_NSContreateStackBlock:在栈上创建的Block对象
_NSConcreteMallocBlock:在堆上创建的Block对象
_NSConcreteGlobalBlock:全局数据区的Block对象

1、查看Block的类型
在这里插入图片描述

通过上面打印的结果可得出,Block是本质上也是一个OC对象,最终继承的是NSObject,它内部也有isa指针,而他的类型,则取决于isa指针,可通过class方法或isa指针查看具体类型

2、如何判断Block类型

(1)全局block储存在全局数据区,在函数栈上创建的block,如果没有截获自动变量,block的结构实例还是会被设置在程序的全局数据区,而非栈上
(2)截获了自动变量的block,是存储在堆上的,而非栈上
(3)没有返回值并且截获变量的block是存储在栈上的

void (^blk)(void) = ^{
    NSLog(@"Global Block");
};

#pragma mark - 查看block的类型
-(void)checkBlockType{
    
    //代码展示三种类型
    int age = 1;
    void (^block1)(void) = ^{
        NSLog(@"没有截获自动变量");
    };
    
    void (^block2)(void) = ^{
        NSLog(@"截获了自动变量:%d",age);
    };
    
    NSLog(@"\n%@\n%@\n%@\n%@",[block1 class],[block2 class],[^{
        NSLog(@"block3:%d",age);
    } class],[blk class]);

}

打印结果
在这里插入图片描述

四、Block管理内存

配置在栈上的block,如果其所属的栈作用域结束,该block就会被废弃,对于超出Block作用域仍需使用Block的情况,Block提供了将Block从栈上复制到堆上的方法来解决这种问题,即便Block栈作用域已结束,但被拷贝到堆上的Block还可以继续存在

将栈上的block自动复制到堆上的几种情况
1、调用Block的copy方法
2、将Block作为函数返回值时
3、将Block赋值给__strong修改的变量时
4、向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时
基于以上四条,ARC下很少能看到栈类中的block,大多数情况编译器都保证了block是在堆上创建的

MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

无论MRC还是ARC,栈空间上的block,不会持有对象;堆空间的block,会持有对象。

block的属性修饰词为什么是copy?
block一旦没有进行copy操作,就不会在堆上
block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期

五、其他
1、使用Clang查看Block源码

(1)cd 文件二级地址
(2)使用命令行进行转换:clang -rewrite-objc xxx.m
如果出现“UIKit/UIKit.h file not found”错误,则使用 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
(3)执行完后打开文件二级地址,就会看到里面生成的.cpp文件

在这里插入图片描述

2、__block 、__weak 、__strong的使用

__block
在代码块中,可以访问局部变量,但是不能修改局部变量,如果需要修改,则使用__block进行修饰,局部变量的值就可以修改了

关于下划线下划线block关键字在MRC和ARC下的不同
__block在MRC下有两个作用
1. 允许在Block中访问和修改局部变量
2. 禁止Block对所引用的对象进行隐式retain操作
__block在ARC下只有一个作用
3. 允许在Block中访问和修改局部变量

a、MRC情况下,用__block可以消除循环引用。
b、 ARC情况下,必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使__unsafe_unretained了,__unsafe_unretained缺点是指针释放后自己不会置
c、不知道 self 什么时候会被释放,为了保证在block内不会被释放,我们添加__strong。更多的时候需要配合strongSelf使用
d、并不是所有的Block里面的self必须要weak一下,有些情况下是可以直接使用self的,比如调用系统的方法:动画代码块

__weak
一个对象A有Block类型的属性,从而持有这个Block,如果Block的代码块中使用到这个对象A,或者仅仅是用用到A对象的属性,会使Block也持有A对象,导致两者互相持有,不能在作用域结束后正常释放。这时就可以使用__weak修饰,对对象A弱引用打破循环

一般常见的修饰方式:
__weak typeof(self) weakSelf = self
__weak Test1ViewController *wealself = self
Reactive Cocoa中的@weakify(self)

如果使用__block修饰的话 在代码块里就需要将其设置为nil,同时代码块必须调用一次
__block与__weak的区别
__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
__block对象可以在block中被重新赋值,__weak不可以。

__strong
这个修饰符要和__weak配对使用 : __strong typeof(self) strongSelf = weakSelf;
他的作用是 保证block在使用该修饰符修饰的对象前 对象不会被提前释放
常见的场景就是对象是一个局部变量 当仅用__weak修饰的时候 出了作用域对象就会被释放 如果没用__strong修饰 block内部就无法使用它了 因为block仅仅弱引用了这个对象
另一个场景就是虽然我们在对象作用域内调用了block 但是block内部使用对象的时机却超出了作用域 例子就是用dispatch_after的场景

MyClass *obj = [[MyClass alloc] init];
    __weak MyClass *weakObj = obj;
    NSLog(@"before block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
 
    void (^block)(void) = ^(){
        NSLog(@"TestObj对象地址:%@",weakObj);
        dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

            for (int i = 0; i < 1000000; i++) {
                // 模拟一个耗时的任务
            }

            NSLog(@"耗时的任务 结束 TestObj对象地址:%@",weakObj);
        });
        
    };
    NSLog(@"after block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));

    block();

打印结果
在这里插入图片描述

使用__strong后

MyClass *obj = [[MyClass alloc] init];
    __weak MyClass *weakObj = obj;
    NSLog(@"before block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
    
    void (^block)(void) = ^(){
        __strong  MyClass *strongObj = weakObj;
        if(! strongObj) return;
        NSLog(@"TestObj对象地址:%@",strongObj);
        dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
            
            for (int i = 0; i < 1000000; i++) {
                // 模拟一个耗时的任务
            }
            
            NSLog(@"耗时的任务 结束 TestObj对象地址:%@",strongObj);
        });

        
    };
    NSLog(@"after block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));

    block();

打印结果
在这里插入图片描述

练习Demo

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值