上次说到 dispatch queue,一般情况下 dispatch queue 操作的对象就是 dispatch block,它的声明如下:

typedef void (^dispatch_block_t)(void);

也就是一个没有返回值、没有参数的函数,一般情况下我们可以直接通过如下方式创建一个 block:

dispatch_block_t block = ^{
    NSLog(@"test");
};

创建 block

不过在 libdispatch 中还提供了一个创建 block 的函数:

dispatch_block_t
dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);

区别在于通过 dispatch_block_create 可以给 block 添加一些属性,当将 block 交给队列执行的时候,可以根据属性做一些额外的操作。这里就有一个比较有意思的点,就是 block 本身只是一个函数对象,并不能存储数据,但在 libdispatch 中利用了 block 捕获在 block 中使用的对象这一特点,通过地址偏移完成了 block 属性的存取:

// create 时,在 block 中通过 (void)dbpds 将其捕获到堆中
dispatch_block_t
_dispatch_block_create(dispatch_block_flags_t flags, voucher_t voucher,
		pthread_priority_t pri, dispatch_block_t block)
{
	struct dispatch_block_private_data_s dbpds(flags, voucher, pri, block);
	return reinterpret_cast<dispatch_block_t>(_dispatch_Block_copy(^{
		// Capture stack object: invokes copy constructor (17094902)
		(void)dbpds;
		_dispatch_block_invoke_direct(&dbpds);
	}));
}

// 然后通过地址偏移,直接拿到 dbpds 的地址,再强转成 dispatch_block_private_data_t
static inline dispatch_block_private_data_t
_dispatch_block_get_data(const dispatch_block_t db)
{
	if (!_dispatch_block_has_private_data(db)) {
		return NULL;
	}
	// Keep in sync with _dispatch_block_create implementation
	uint8_t *x = (uint8_t *)db;
	// x points to base of struct Block_layout
	x += sizeof(struct Block_layout);
	// x points to base of captured dispatch_block_private_data_s object
	dispatch_block_private_data_t dbpd = (dispatch_block_private_data_t)x;
	if (dbpd->dbpd_magic != DISPATCH_BLOCK_PRIVATE_DATA_MAGIC) {
		DISPATCH_CLIENT_CRASH(dbpd->dbpd_magic,
				"Corruption of dispatch block object");
	}
	return dbpd;
}

而从 _dispatch_block_create 的实现也可以看出,dispatch_block_create 完成对另一个 block 包装的方式,就是新建一个 block,然后在这个 block 中再调用被封装的 block,同时加上一些其他操作和一些保存在 dispatch_block_private_data_s 中的属性,而这些属性是要到我们将 block 提交给 dispatch queue 执行的时候才会使用到。

提交 block

之前说过的同步/异步任务的执行,都是没有考虑到通过 dispatch_block_create 创建 block 的,现在再去回顾一下就会发现新的东西:

void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
	uintptr_t dc_flags = DC_FLAG_BLOCK;
	if (unlikely(_dispatch_block_has_private_data(work))) {
		return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
	}
	_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

static inline bool
_dispatch_block_has_private_data(const dispatch_block_t block)
{
	return (_dispatch_Block_invoke(block) == _dispatch_block_special_invoke);
}

在 dispatch_sync 中并不是直接调用 _dispatch_sync_f 的,它会先判断 block 是否有 private_data 这一属性,也就是说,而判断的方式就是这个 block 是否是由 dispatch_block_create 创建。当判断确认之后,这个 block 就被用另一种方式提交给队列了,

static void
_dispatch_sync_block_with_privdata(dispatch_queue_t dq, dispatch_block_t work,
		uintptr_t dc_flags)
{
	dispatch_block_private_data_t dbpd = _dispatch_block_get_data(work);
	pthread_priority_t op = 0, p = 0;
	dispatch_block_flags_t flags = dbpd->dbpd_flags;

	if (flags & DISPATCH_BLOCK_BARRIER) {
		dc_flags |= DC_FLAG_BLOCK_WITH_PRIVATE_DATA | DC_FLAG_BARRIER;
	} else {
		dc_flags |= DC_FLAG_BLOCK_WITH_PRIVATE_DATA;
	}

	op = _dispatch_block_invoke_should_set_priority(flags, dbpd->dbpd_priority);
	if (op) {
		p = dbpd->dbpd_priority;
	}
	voucher_t ov, v = DISPATCH_NO_VOUCHER;
	if (flags & DISPATCH_BLOCK_HAS_VOUCHER) {
		v = dbpd->dbpd_voucher;
	}
	ov = _dispatch_set_priority_and_voucher(p, v, 0);

	// balanced in d_block_sync_invoke or d_block_wait
	if (os_atomic_cmpxchg2o(dbpd, dbpd_queue, NULL, dq, relaxed)) {
		_dispatch_retain_2(dq);
	}
	if (dc_flags & DC_FLAG_BARRIER) {
		_dispatch_barrier_sync_f(dq, work, _dispatch_block_sync_invoke,
				dc_flags);
	} else {
		_dispatch_sync_f(dq, work, _dispatch_block_sync_invoke, dc_flags);
	}
	_dispatch_reset_priority_and_voucher(op, ov);
}

比如在这里,创建 block 时的 flag 会被取出来判断这是否是一个 barrier 类型的 block,然后决定采用哪种方式继续提交。而在创建新 block 的时候也提过,新的 block 会对原来的 block 进行一些包装,如下:

void
_dispatch_block_invoke_direct(const struct dispatch_block_private_data_s *dbcpd)
{
	dispatch_block_private_data_t dbpd = (dispatch_block_private_data_t)dbcpd;
	dispatch_block_flags_t flags = dbpd->dbpd_flags;
	unsigned int atomic_flags = dbpd->dbpd_atomic_flags;
	if (unlikely(atomic_flags & DBF_WAITED)) {
		DISPATCH_CLIENT_CRASH(atomic_flags, "A block object may not be both "
				"run more than once and waited for");
	}
	if (atomic_flags & DBF_CANCELED) goto out;

	pthread_priority_t op = 0, p = 0;
	op = _dispatch_block_invoke_should_set_priority(flags, dbpd->dbpd_priority);
	if (op) {
		p = dbpd->dbpd_priority;
	}
	voucher_t ov, v = DISPATCH_NO_VOUCHER;
	if (flags & DISPATCH_BLOCK_HAS_VOUCHER) {
		v = dbpd->dbpd_voucher;
	}
	ov = _dispatch_set_priority_and_voucher(p, v, 0);
	dbpd->dbpd_thread = _dispatch_tid_self();
	_dispatch_client_callout(dbpd->dbpd_block,
			_dispatch_Block_invoke(dbpd->dbpd_block));
	_dispatch_reset_priority_and_voucher(op, ov);
out:
	if ((atomic_flags & DBF_PERFORM) == 0) {
		if (os_atomic_inc2o(dbpd, dbpd_performed, relaxed) == 1) {
			dispatch_group_leave(dbpd->dbpd_group);
		}
	}
}

当 block 执行的时候,它还会再取出 dispatch_block_private_data_t 数据,比如判断 block 是否需要执行(atomic_flags & DBF_CANCELED),同时还会更新一些状态,以供其他方法查看。

取消 block

之前做多线程方案对比的时候就提过,NSOperation 相对于 GCD 的一大就是就是封装了很好的管理,比如依赖,比如对任务的取消,在任务可取消这一块,GCD 在后来是有补齐的,但能用于操作的也仅限使用它提供的函数创建的 block,原因也很容易理解,要做管理,就需要额外的属性,而普通的 block 不能附带属性,只有 GCD 创建的 block 可以用类似黑科技的手段给 block 增加属性,所以这就是为什么,GCD 里面操作 block 相关的函数,都要求是它自己创建的原因。

至于取消 block,GCD 提供了俩函数,dispatch_block_cancel 供取消,dispatch_block_testcancel 供查看这个 block 是否被取消。

intptr_t
dispatch_block_testcancel(dispatch_block_t db)
{
	dispatch_block_private_data_t dbpd = _dispatch_block_get_data(db);
	if (unlikely(!dbpd)) {
		DISPATCH_CLIENT_CRASH(0, "Invalid block object passed to "
				"dispatch_block_testcancel()");
	}
	return (bool)(dbpd->dbpd_atomic_flags & DBF_CANCELED);
}

void
dispatch_block_cancel(dispatch_block_t db)
{
	dispatch_block_private_data_t dbpd = _dispatch_block_get_data(db);
	if (unlikely(!dbpd)) {
		DISPATCH_CLIENT_CRASH(0, "Invalid block object passed to "
				"dispatch_block_cancel()");
	}
	(void)os_atomic_or2o(dbpd, dbpd_atomic_flags, DBF_CANCELED, relaxed);
}

俩函数的实现都很简单,就是通过判断/更改 block 的属性,dispatch_block_private_data_t->dbpd_atomic_flags 的值来完成,然后在 _dispatch_block_invoke_direct 中,真实的 block 执行之前,根据 dispatch_block_private_data_t->dbpd_atomic_flags 判断这个 block 是否被取消来决定是否调用真实的 block。所以从这里可以看出,GCD 中对 block 的取消也并不是完全的取消,我们能取消的只是通过 dispatch_block_create 创建新的 block 时穿进去的那个 block,无论如何我们新创建的 block 是一定会执行的。

等待/监听 block

除了取消 block,GCD 还提供了 block 的等待、监听,一定程度上算是支持了 block 之间的依赖吧,当然,这些功能也是基于 dispatch_block_create 才有的,先看 dispatch_block_wait,

intptr_t
dispatch_block_wait(dispatch_block_t db, dispatch_time_t timeout)
{
	dispatch_block_private_data_t dbpd = _dispatch_block_get_data(db);
	if (unlikely(!dbpd)) {
		DISPATCH_CLIENT_CRASH(0, "Invalid block object passed to "
				"dispatch_block_wait()");
	}

	unsigned int flags = os_atomic_or_orig2o(dbpd, dbpd_atomic_flags,
			DBF_WAITING, relaxed);
	if (unlikely(flags & (DBF_WAITED | DBF_WAITING))) {
		DISPATCH_CLIENT_CRASH(flags, "A block object may not be waited for "
				"more than once");
	}

	// <rdar://problem/17703192> If we know the queue where this block is
	// enqueued, or the thread that's executing it, then we should boost
	// it here.

	pthread_priority_t pp = _dispatch_get_priority();

	dispatch_queue_t boost_dq;
	boost_dq = os_atomic_xchg2o(dbpd, dbpd_queue, NULL, relaxed);
	if (boost_dq) {
		// release balances dispatch_{,barrier_,group_}async.
		// Can't put the queue back in the timeout case: the block might
		// finish after we fell out of group_wait and see our NULL, so
		// neither of us would ever release. Side effect: After a _wait
		// that times out, subsequent waits will not boost the qos of the
		// still-running block.
		dx_wakeup(boost_dq, _dispatch_qos_from_pp(pp),
				DISPATCH_WAKEUP_BLOCK_WAIT | DISPATCH_WAKEUP_CONSUME_2);
	}

	mach_port_t boost_th = dbpd->dbpd_thread;
	if (boost_th) {
		_dispatch_thread_override_start(boost_th, pp, dbpd);
	}

	int performed = os_atomic_load2o(dbpd, dbpd_performed, relaxed);
	if (unlikely(performed > 1 || (boost_th && boost_dq))) {
		DISPATCH_CLIENT_CRASH(performed, "A block object may not be both "
				"run more than once and waited for");
	}

	long ret = dispatch_group_wait(dbpd->dbpd_group, timeout);

	if (boost_th) {
		_dispatch_thread_override_end(boost_th, dbpd);
	}

	if (ret) {
		// timed out: reverse our changes
		os_atomic_and2o(dbpd, dbpd_atomic_flags, ~DBF_WAITING, relaxed);
	} else {
		os_atomic_or2o(dbpd, dbpd_atomic_flags, DBF_WAITED, relaxed);
		// don't need to re-test here: the second call would see
		// the first call's WAITING
	}

	return ret;
}

实现比较简单,首先更改一下 flag,然后直接用 dispatch_group_wait 就完了,从这里也可以看出,实际上 block 的 wait/notify 功能就是包装了一下 dispatch group 的 wait/notify 功能,而这个 group 就是在 dispatch_block_private_data_s 构造的时候创建的,所以也可以理解为,每一个 block 内部都是有一个 dispatch group 存在的,关于 group 的功能,后面再说。

总结

总的来看 block 相关的也就这些了,它最主要的一点,就是通过将原有的 block 封装成一个新的 block,并利用 block 能捕获内部数据的方式,实现了给 block 加属性这一目的,从而进一步能够支持 block 的取消、监听啊等,但是从这些功能的实现可以看出,这些功能从实现的角度并不是很好,尤其是使用内存偏移取属性这一点,虽然说本质上一个类的实现也是使用内存偏移来计算变量位置的,但强行给这么一个 block 通过这种方式添加属性让人不是很理解。

从功能上来看,据此实现的功能在 iOS 中都是有 NSOperation 作为替代的,相对来说 NSOperation 拥有更好的流程控制,个人感觉专门为实现这些功能这么搞出个新的 block 不是很有必要,不过考虑到 libdispatch 不是 iOS 独有的功能,可能在 iOS 中只是给搬运了过来而已。