iOS OC Block详解

全文分两部分(认认真真看完,肯定有收获)

1.Block定义主要是介绍一下block的使用
2.详解Block,深入了解Block的用法

一.Block定义

1.Block格式

//等号左侧是声明一个block,右侧是定义一个block
(1).定义block块必须以^开头,声明块以返回值类型开头
(2).定义块的返回值类型可以省略,而且经常会省略,声明不能省略
(3).定义块无需指定名字,声明块必须得有
(4).如果块没有参数,参数部分可以省略但是参数部分的括号不能省略,
但是内部可以留空,通常建议使用void作为占位符

returnType(^blockName)(parameterTypes) = ^(parameters) {
    statements
};

void(^blockName)(NSString *) = ^void(NSString*str){
    NSLog(@"%@",str);
};
blockName(@"这是一个简单的无返回值的block");

//^告诉计算机这是一个block
NSString *(^blockParam)(NSString *) = ^NSString*(NSString*n){
    NSLog(@"%@",n);
    return @"123456789";
};
NSString * str = blockParam(@"这是一个简单的带返回值的block");
str = @"123456789";

2.Block作为属性

@property(nonatomic,copy) void (^propertyBlock)(NSString*);

3.typedef定义Block

typedef void (^typedefBlock)(NSString *);
@property(nonatomic, copy)typedefBlock block;

4.Block作为参数不带返回值

- (void)viewDidLoad{
    
    [self blockAsParam:^(NSString *name) {
        NSLog(@"%@",name);//123123123
    }];
}
- (void)blockAsParam:(void (^)(NSString * _Nullable))paramBlock{
    paramBlock(@"123123123");
}

5.Block作为参数带返回值

- (void)viewDidLoad{

    [self blockAsParamReturnValue:^NSString*(NSString *name){
        NSLog(@"%@",name);
        return @"123123123123";
    }];

}

- (void)blockAsParamReturnValue:(NSString *(^)(NSString *str))returnValue{

    NSString *s = returnValue(@"123123123");
    NSLog(@"%@",s);//123123123123

}

6.Block不带返回值,作为返回值

- (void)viewDidLoad{

    void(^blockNoReturn)(NSString*) = [self blockNoReturnValue:@"11111"];
    blockNoReturn(@"000000");
}

- (void(^)(NSString *))blockNoReturnValue:(NSString*)value{

    return ^(NSString *name){
        NSLog(@"%@,%@",name,value);//000000,11111
    };

}

7.Block带返回值,作为返回值

- (void)viewDidLoad{

    NSString *(^haveReturnValue)(NSString *) = [self blockHaveReturnValue:@"2222"];
    NSString *s = haveReturnValue(@"123");
    NSLog(@"%@",s);//11111111111111111111

}

- (NSString *(^)(NSString*))blockHaveReturnValue:(NSString*)value{

    return ^NSString *(NSString*n){
        NSLog(@"%@,%@",n,value);//123,2222
        return @"11111111111111111111"
    };

}

二.Block详解(2020.03.18更新)

1.使用copy和strong修饰block有什么区别吗?

block有三种存储类型__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__.

经测试在MRC环境下,任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型.
引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的,
如果使用copy修饰了属性并且使用了self.语法调用的为NSMallocBlock,否则是NSStackBlock类型.
(
举例
@property (nonatomic ,copy) void (^copyPropertyBlock)(NSString * _Nullable);
   
    self.copyPropertyBlock = ^(NSString *c){
        int d = globaln + 9;
    };//globaln为非静态变量,这个时候copyPropertyBlock类型是NSMallocBlock
)
在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
NSStackBlock类型的block变为NSMallocBlock,拷贝到了堆上.所以在MRC中需要使用copy修饰
否则在栈中会随时释放

在ARC环境下任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型,
引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的为NSMallocBlock
在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
NSMallocBlock的类型未变化(地址也未发生变化)

结论
在MRC中访问外部非静态变量的block必须需要使用copy对block进行修饰,将其拷贝到堆中.
ARC中系统已经帮我们将其从栈拷贝到堆了,所以我们不需要在进行copy处理
,使用copy或者strong修饰都可以.

网上一搜好多关于这方面的讲解,但是大多数都是复制的一篇文章,内容都一样,
而且还是比较老的文章,没有涉及ARC和MRC的对比,当然也有好的文章,建议大家自己试一下才会知道结果.

2.__weak和__block

__weak仅仅是将该对象赋值给weak修饰的对象,__weak还有一个作用就是将被修饰的对象加入缓存池
中,当缓存池释放时,会对其内部的对象release一次,因为对象是weak修饰的弱引用,所以会被释放.
不会引起循环引用的问题

__block
修饰的局部变量会包装成一个结构体对象,这个结构体对象里面包含着被修饰的对象,被block持有,
这个时候__block修饰的值是可以修改的.

3.block捕获变量
源码查看方式

#include "DDBlockStudy.h"
//全局的
int w = 23;
static int e = 10;
int f[] = {11,22,33,44};

void buildBlock(){
    
    int m = 1;
    static int n = 3;
    int arr[] = {1,2,3,4,5,6};
    
    void (^testBlock)(int)= ^(int d){
        printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,n);
        //打印的结果是12,14,32,1,10
    };
    w = 12;
    e = 14;
    *f = 32;
    m = 4;
    n = 10;
    testBlock(3);
    
}
上面的内容出现的结果可以总结一下
(1).静态变量(全局和局部)和全局变量并未捕获任何内容,用的时候直接取得,可以修改值
(2).属性值和成员变量捕获的是对象的持有者(self),可以修改值
(3).对于局部变量block捕获的是值.
理由见 static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d);函数
(补充,更确切的说是在__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)函数中可以看出,详细见下面的👇Block修改变量有解说)



//通过源码分析
int w = 23;
static int e = 10;
int f[] = {11,22,33,44};

//这是block的定义来自__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)
struct __buildBlock_block_impl_0 {
  struct __block_impl impl;
  struct __buildBlock_block_desc_0* Desc;
  //下面的内容是被捕获的参数
  int m;
  int *n;

  //void *fp 对应 (void *)__buildBlock_block_func_0 方法的指针
  //struct __buildBlock_block_desc_0 *desc 对应  &__buildBlock_block_desc_0_DATA 这个block的一些参数
  //impl.isa = &_NSConcreteStackBlock;可以得出当前的block在栈中
  //m(_m)将_m的值赋给m,n同理

  //从这里看出(int _m, int *_n)这个时候m和n被捕获了,但是m只是值被持有了,n被持有的是指针.  
  __buildBlock_block_impl_0(void *fp, struct __buildBlock_block_desc_0 *desc, int _m, int *_n, int flags=0) : m(_m), n(_n) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//下面的这个函数存储的是block执行的代码
static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d) {
  int m = __cself->m; // bound by copy
  int *n = __cself->n; // bound by copy

        printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,(*n));
}

static struct __buildBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __buildBlock_block_desc_0_DATA = { 0, sizeof(struct __buildBlock_block_impl_0)};
void buildBlock(){

    int m = 1;
    static int n = 3;
    int arr[] = {1,2,3,4,5,6};

    void (*testBlock)(int) = ((void (*)(int))&__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n));
    w = 12;
    e = 14;
    *f = 32;
    m = 4;
    n = 10;
    //这里是调用了这个block
    ((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 3);

}

4.Block修改变量

先上代码
#import "DDBlockController.h"

@interface DDBlockController ()
@property (nonatomic, assign) int a;
@property (nonatomic, copy) NSString *str;
@end

static int b = 11;
int c = 10;
NSString *str1 = @"aaaaaa";
static NSString *str2 = @"bbbbbb";


@implementation DDBlockController
{
    int d;
    NSString *str3;
    void (^testTwo)(void);
    
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.a = 123;
    self.str = @"eeeeeeeee";
    d = 4444444;
    str3 = @"rrrrrrrrrrrrrrrrr";
    [self blockTest];
    // Do any additional setup after loading the view.
}

- (void)blockTest{
    //如果修改局部非静态变量需要使用__block修饰.
    int e = 10;
    static int f = 12;
    NSString * str4= [[NSString alloc]init];;
    str4 = @"123123";
    static NSString * str5= @"1234asdads";

    void (^testOne)(void) = ^(void){
        
        NSLog(@"%d",_a);
        NSLog(@"%d",b);
        NSLog(@"%d",c);
        NSLog(@"%d",d);
        NSLog(@"%d",e);
        NSLog(@"%d",f);
        NSLog(@"%@",_str);
        NSLog(@"%@",str1);
        NSLog(@"%@",str3);
        NSLog(@"%@",str4);
        NSLog(@"%@",str5);

        
    };
    testOne();
}

- (void)dealloc{
    NSLog(@"aaaaaaaaaaaaaa");
}
@end

//*desc和flags之间的是被捕获的内容,self(_self)将_self赋值给self,  DDBlockController *self = __cself->self; // bound by copy,从__DDBlockController__blockTest_block_impl_0中取出self的值(C++->取值符号)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {



将block中的打印依次打开,从源码中可以看出(注捕获的意思就是持有的意思)
(1).使用当前类属性的时候self.a时,会将持有a的对象self捕获,可以修改值,但是注意循环引用(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(2).当使用全局静态变量b的时候未捕获任何值,用的时候直接使用,可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {

(3).当使用全局变量c的时候未捕获任何值,用的时候直接取,可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {

(4).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(5).当使用局部变量e的时候会捕获其值,未捕获其指针地址,不可修改值除非使用__block修饰(int _e)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int _e, int flags=0) : e(_e) {

(6).当使用局部静态变量f的时候,捕获的是f的指针,可以进行修改(int *_f,)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int *_f, int flags=0) : f(_f) {

(7).当使用全局的属性self.str时,捕获的是持有str的对象self,可以修改值,但是注意循环引用(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(8).当使用全局变量str1的时候,未捕获任何值,因此可以直接使用且可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {

(9).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(10).当使用局部变量str4的时候,捕获的是str4的值并没有捕获其指针地址,所以不可修改,除非使用__block修饰(NSString *_str4)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString *_str4, int flags=0) : str4(_str4) {

(11).当使用局部静态变量str5的时候,捕获的是str5的地址,所以可以修改(NSString **_str5)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString **_str5, int flags=0) : str5(_str5) {

总结
//auto变量(局部非静态变量)捕获的是值所以不能操作修改值.
//注意因为当前的block是局部的并未被self持有,所以使用self不会引起循环引用
//__block的作用是将修饰的变量包装成一个结构体对象
下面的这个是__block修饰的int e;被block捕获的后,系统为其创建的一个结构体
struct __Block_byref_e_0 {
    void *__isa;
  __Block_byref_e_0 *__forwarding;//__forwarding指向的是__Block_byref_e_0自己,自己指向了自己
   int __flags;
   int __size;
   int e;
  };
  
//对于可变变量(NSMutableArray,NSMutableString等)即使是局部变量也可以进行增删改查的操作,
//因为可变内容在初始话的时候分配了一块内存,操作的时候都是在这块内存操作的,只要是不改变该变量的指针对应的内容即可


总结一下就是对于局部非静态变量而言,可以修改局部变量的属性等操作,但是不能改变其指向的内存的地址,否则就会报错,
使用__block之后就是block持有了这个对象,没有使用__block的话只是copy了一份,但是只有只读权限,不能改变对象,但是可以改变对象的属性.

意思就是秘书来了权限不够不好使,只能动一部分内容.老板亲自来了权限就大了可以做的事就更多了.

5.Block循环引用,并不是所有的block都会循环引用
6.Block作为形参的时候不会引起循环引用,这也就解释了为什么GCD或者UIView动画这些操作不会引起循环引用
7.Block存储域

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 1、概述 闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block 是...
    DeerRun阅读 655评论 0 0
  • 1、block简介 block字面意思就是代码块 iOS4.0 Apple引入的特性 block是Objectiv...
    呆子四二阅读 1,573评论 1 6
  • 一、概述 闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block 是...
    简单_6509阅读 1,528评论 0 1
  • 第一部分:Block本质 Q:什么是Block,Block的本质是什么? block本质上也是一个OC对象,它内部...
    sheldon_龙阅读 553评论 0 0
  • 什么是typedef? typedef就是一种替换,与宏不同的是它还可以进行对象的声明。 typedef为C语言的...
    知本集阅读 623评论 0 0