block才会执行 mono_Block与GCD的那些事

34e8f5f5c9b7d1e2131a3dbef80320b3.png

Blocks

1884a081dde23d3f03f6ad4f959b202f.png

Blocks是C语言的扩充功能,是带用自动变量的匿名函数(不带有名称的函数)

1.Block语法

//C语言为例
int countAdd(int count)
{
  return count + 1;
}
//标准格式
// ^ 返回值类型 参数列表 表达式
^int (int count)
{
  return count + 1;
}
//可省略返回值类型
^(int count)
{
  return count + 1;
}
//当传入参数为空时,可省略参数列表
^{
  printf("Blocksn");
}

2.Block类型变量

在C语言中,可以将函数的地址赋值给函数指针类型变量

int func(int count)
{
  return count + 1;
}
//地址赋值
int (*funcopy)(int) = &func;

将Block赋值为Block类型变量

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

由Block类型变量向Block类型变量赋值

int (^block01)(int) = block;

可以使用typedef简化block代码量

typedef int (^block)(int);
block blk = ^(int a){return a + 1;};

3.Block截获自动变量值

截获瞬间值

int value = 10;
void (^blk) (void) = ^{printf("the value is %dn", value);};
blk();
//change the value
value = 7;
printf("the value is %dn", value);
blk();

4.__block

block 中给 block 外的其他变量赋值,需要在该自动变量上附加 __block 说明符

__block int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %dn", val);

截获 OC 对象,可以调用对象的方法

id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
  id object = [[NSObject alloc] init];
  [array addObject:object];
}

5.block本质上也是一个OC对象,它内部也有个isa指针

什么是 isa 指针

每个 Objective-C 对象实例都是指向某块内存数据的指针,而自身的数据就存于该内存地址中

//以NSString为例
NSString *str = @"hello world";
//以通用对象类型id为例
id idType = @"hello world";

id 类型定义如下:

typedef struct objc_object
{
  //每个对象结构体的首个成员是Class类的变量
  //该变量定义了对象所属的类
  Class isa;
} *id;
typedef struct objc_class *Class;

//该结构体存放类的元数据
struct objc_class 
{
  //该isa指向元类
  //元类是指类对象所属的类型
  Class isa;
  //该super_class定义了本类的超类
  Class super_class;
  const char *name;
  long version;
  long info;
  long instance_size;
  struct objc_ivar_list *ivars;
  struct objc_method_list **methodLists;
  struct objc_cache *cache;
  struct objc_protocol_list *protocols;
}

269a8d6efbc5c97837fb54686f1f8902.png

super_class指向父类(确立继承关系)

isa指向元类(描述了实例所属的类)

ced90f055deb23d78b1c6da1f0aa8084.png

6.Block存储域

Block语法执行时, Block底层会转化成Block的结构体类型的自动变量存储在栈上

__block变量初始化时, 会转化成__block变量的结构体类型的自动变量存储在栈上.

| 名称 | 实质 | | :---------- | :-------------------------- | | Block | 栈上Block的结构体实例 | | block变量 | 栈上block变量的结构体实例 |

Block对象有以下3种类型, 存储在应用程序的不同的内存区域:

| 名称 | 设置对象的存储域 | | ---------------------- | ---------------- | | _NSConcreteStackBlock | 栈 | | _NSConcreteGlobalBlock | 数据域 (.data区) | | _NSConcreteMallocBlock | 堆 |

通常情况下,Block对象存储在_NSConcreteStackBlock

Block对象存储在_NSConcreteGlobalBlock区:

Block出现在全局数据区
Block不使用截获的自动变量,如:
typedef int (^blk_t)(int);
for (int rate = 0; rate < 3; rate ++)
{
  blk_t blk = ^(int a){return a + 1;};
}

Block对象存储在_NSConcreteMallocBlock区:

Block或__block变量从栈上复制到堆上时

af439c684c6d3deabf2bbfe02b1290b4.png
  1. 当ARC有效时,大多数情况下编译器会进行判断,自动将Block从栈上复制到堆上
//举个 
typedef int (^blk_t)(int);
blk_t func(int rate)
{
  return ^(int count){return rate * count;};
}

//ARC有效时,转换如下
blk_t func(int rate)
{
  blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);

  //该函数就是_Block_copy函数
  tmp = objc_retainBlock(tmp);

  return objc_autoreleaseReturnValue(tmp);
}

Block的副本

baf25b7599b7abb99731a1343e9f3bc3.png
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return rate * count;};
//可以调用copy方法
blk = [blk copy];

7.__block变量存储域

Block从栈复制到堆时对__block变量产生的影响

ce3b7b597f36541294eab926e29a9ed1.png

1.如果Block中使用__block变量,则当该Block从栈复制到堆时,使用的所有__block变量也被复制到堆上

2.在多个Block中使用__block变量时,__block变量会随任何一个Block一起从栈复制到堆,如果还有其他的Block需复制,则增加__block变量的引用计数

在Block从栈复制到堆时,以及堆上的Block被废弃时会调用这些函数

1566afd1df507eb11108d8e58b0cc9b0.png

什么时候栈上的Block会复制到堆?

调用copy 方法
Block作为函数返回值返回时
将Block赋值给赋有__strong修饰符id类型的类或Block类型成员变量时

说到这里 不得不说说ARC

ARC是由编译器进行内存管理的,无需手动输入retainrelease

先说说手动管理(MRC

自己生成的对象,自己持有

allocnewcopymutableCopy

id obj = [[NSObject alloc] init];

非自己生成的对象,自己也能持有

retain

//对象存在,但自己不持有该对象
id obj = [NSMutableArray array];
[obj retain];

释放对象

release

id obj = [NSMutableArray array];
[obj retain];
[obj release];

自动释放

autorelease

可以人为设定变量的作用域,延迟内存的释放,即延长对象的生命周期,并且在合理的时机释放。

//举个 
- (NSObject *)initObject() {
    NSObject *obj = [[NSObject alloc] init];
    return obj;
}

//如何得到obj???
- (NSObject *)initObject() {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSObject *obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];
    return obj;
}

ARC

下面我们简单看看几个常用修饰符:

__strong

//ARC中,默认的为对象添加__strong
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];

__strong为默认修饰符,表示强引用。修饰的变量在被废弃(变量超出作用域成员变量所属对象被废弃变量赋值nil)时,会释放被赋予的对象。

//因为obj为强引用,所以自己持有对象
id __strong obj = [NSMutableArray array];

通过__strong变量赋值了解__strong

id __strong obj0 = [[NSObject alloc] init]; /*对象A*/
id __strong obj1 = [[NSObject alloc] init]; /*对象B*/
id __strong obj2 = nil;

//此时对象A没被持有,所以释放
//对象B被obj0,obj1持有
obj0 = obj1;

//对象B被obj0,obj1,obj2持有
obj2 = obj0;

//依此释放对象B
obj1 = nil;
obj0 = nil;
obj2 = nil;

__weak

为解决循环引用而生

先来看看互相强引用的实例:

//举个 
@interface Test : NSObject
{
  id __strong obj01;
}
- (void)setObject:(id __strong)obj;
@end

@implementation Test
- (id)init
{
  self = [super init]
  return self;
}
- (void)setObject:(id __strong)obj
{
  obj01 = obj;
}
@end

//循环引用
{
  id test0 = [[Test alloc] init];/*对象A*/
  id test1 = [[Test alloc] init];/*对象B*/
  [test0 setObject:test1];
  [test1 setObject:test0];
}
//自动释放对象A,B
//对象A的obj01持有对象B
//对象B的obj01持有对象A

//内存泄漏!!!

内存泄漏:应当废弃的对象在超出起生存周期后继续存在

__weak提供弱引用,弱引用不能持有对象实例

在持有某对象的弱引用时,如果该对象被废弃,则此弱引用被赋值为nil

id obj = [[NSObject alloc] init];

如果解决上面的问题 ???

id __weak obj01;

__unsafe_unretained__weak的不安全版本

注意一点:附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象,因此不建议使用

__unsafe_unretained__weak一样,不能持有对象实例(既不持有对象的强引用,也不持有弱引用)

__autoreleasing

ARC下,不能使用autorelease方法,也不能使用NSAutoReleasePool类,但通过新的语法,autorelease功能仍起作用。

/*ARC有效*/
@autoreleasepool{
  id __autoreleasing obj = [[NSObject alloc] init];
}

GCD

1.GCD简介

GCD是异步执行任务的技术之一。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。

2.GCD任务和队列

任务: 任务就是你在线程中执行的那段代码,在 GCD 中是放在 block 中的。

执行任务有两种方式:

1.同步执行(dispatch_sync)

  • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
  • 只能在当前线程中执行任务,不具备开启新线程的能力。

2.异步执行(dispatch_async)

  • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
  • 可以在新的线程中执行任务,具备开启新线程的能力。

队列(FIFO):

51bdc3f0b3b44f4bf80d39a59313be79.png

串行队列(Serial Dispatch Queue):

  • 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

2c7c511a1085eacf217a23cc0e18907f.png

并发队列(Concurrent Dispatch Queue):

  • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
  • 并发队列 的并发功能只有在异步(dispatch_async)函数下才有效

d0f2b092fef8a9e6fed03f0966d46f95.png

3.GCD 的使用步骤

1.创建一个队列

2.将任务追加到任务的等待队列中

队列的创建方法/获取方法:

1.可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

2.对于串行队列,GCD 默认提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)

  • 所有放在主队列中的任务,都会放到主线程中执行。
  • 可使用dispatch_get_main_queue()获得主队列。
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();

3.对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)

  • 可以使用dispatch_get_global_queue来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

任务的创建方法:

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

全局并发队列可以作为普通并发队列来使用。但是主队列有点特殊,所以一共有6种组合方式:

同步执行 + 并发队列
异步执行 + 并发队列
同步执行 + 串行队列
异步执行 + 串行队列
同步执行 + 主队列
异步执行 + 主队列

讲一讲 GCD死锁

什么是GCD死锁?

所谓死锁,通常指有两个线程A和B都卡住了,A在等B ,B在等A,相互等待对方完成某些操作。A不能完成是因为它在等待B完成。但B也不能完成,因为它在等待A完成。于是大家都完不成,就导致了死锁。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      //1
        dispatch_sync(dispatch_get_main_queue(), ^(void){
          //2
            NSLog(@"这里死锁了");
        });

    }
    return 0;
}

举几个

1.案例一: 当同步遇到了串行

NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2"); // 任务2
  });
NSLog(@"3"); // 任务3

该程序只执行任务1 WHY???

首先执行任务1,之后遇到同步线程,该线程将任务2添加到主队列后面,然后只有当任务2执行之后才会执行任务3。但是任务3又必须等到上一步的同步线程执行完后才能执行。因此,任务2等待任务3的结束,任务3等待任务2的结束。线程锁死。

2.案例二:同步 + 并行

NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

该程序输入什么?

该程序输出:

1
2
3

首先执行任务1,此后遇到同步线程,该线程将任务2添加到全局并发队列中,该队列会开辟新的线程去执行任务2,当任务2执行完成之后,再执行任务3。

3.案例三:同步 + 异步

//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
    NSLog(@"2"); // 任务2
    dispatch_sync(queue, ^{  
        NSLog(@"3"); // 任务3
    });
    NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

该程序输出什么?

1
2
5
//2,5的输出顺序不定

首先调用队列创建方法创建串行队列,然后执行任务1,进入异步线程,该线程将任务2,同步线程,任务4加入串行队列中。因为该异步线程与任务5一起执行,所以任务2和任务5执行结束的时间不定。但是其中的同步线程将任务3添加到串行队列的后面(任务4的后面),而任务4又必须等到任务3执行结束才能执行,所以线程锁死。

4.案例四:异步遇到同步回主线程

NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"2"); // 任务2
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"3"); // 任务3
    });
    NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

该程序输出什么?

1
2
5
3
4
//2,5的输出顺序不定

首先执行任务1,然后异步线程(其中有任务2,同步线程,任务4)和任务5同时执行,所以任务2和任务5的执行快慢不定,任务2执行完后,同步线程将任务3添加到任务5的后面,待任务5执行完后执行任务3,最后执行任务4.

5.案例五:主线程上出现无限循环

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"1"); // 任务1
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任务2
    });
    NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
while (1) {
}
NSLog(@"5"); // 任务5

该程序输出什么?

1
4
//1,4输出顺序不定

异步线程(任务1,同步线程,任务3)和任务4一起进行,所以任务1和任务4执行快慢未知。任务4执行完之后遇到死循环,所以任务5永远不会执行,而任务2又添加到任务5的后面,任务3又在任务2的后面。所以任务2,3,5都不会执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值