碾压面试之再提Objective-C Block

131 阅读4分钟

之前写过OC Block的两篇文章,主要围绕在语法备忘和通过深入学习Block结构(如capture变量原理、__block变量、内存循环引用)

本来觉得已经够用了,但实际面试中会问及更多底层的内容

本文就做个汇总

苹果公开Block源码了吗?

block对应的runtime library(libclosure)代码是开源的

因为block的结构和行为很像OC中的对象(比如copy、内存管理),所以它的一些行为(如发送消息)是依赖于runtime的。从这个角度讲,可以认为block是开源的

clang -rewrite-objc

查看网上的技术文章,基本都会提到使用clang -rewrite-objc命令将一段涉及block的代码翻译(重写)为C++代码,然后依据这个C++代码来分析block的底层实现

首先关于该命令的官方说法就一句话--Rewrite Objective-C source to C++(来自Clang command line argument reference

该命令的用途是什么?

我所查到的作用是,这是clang编译器提供的用于将OC语言编写的程序运行在Windows平台的工具,因为Windows平台中没有编译器能支持OC语言,所以可以使用该命令将程序转为C++代码的程序,再通过其他编译器编译后在Windows下运行。

--来自What's the relationship between Objective-C source code and after clang -rewrite-objc C++ code?

Block的历史

我们看一下Wikipedia上的介绍

Blocks are a non-standard extension added by Apple Inc. to Clang's implementations of the C, C++, and Objective-C programming languages that uses a lambda expression-like syntax to create closures within these languages. Blocks are supported for programs developed for Mac OS X 10.6+ and iOS 4.0+.

block是否是OC对象

可以认为block是OC对象,原因有:

  • 从libclosure源码可以看出,block最终也是翻译为一个struct,且第一个成员变量是一个名为isa的指针,表示block的类型,这和其他的NSObject或子类对象结构时相似的
  • 在行为上和普通OC对象也很类似
    • 如可以对block对象发送消息([block copy])
    • 可以当做对象存入集合容器中

苹果官方文档中也说过--Blocks are Objective-C objects(from Working with Blocks )

block和普通OC对象还是有些不同的

  • 内存管理不同
    • 虽然Heap block也是引用计数来管理内存
    • 但Global block和Stack block则并非如此
  • 使用起来更像是是function
  • 且还能capture外部变量
  • 还有像__block变量的支持

forwarding指针的巧妙之处

首先,只有使用__block修饰的变量,对应runtime下的结构体中才有forwarding,该结构体大致是这样:

struct _block_byref_foo {
    void *isa;
    struct Block_byref *forwarding;
    int flags;   //refcount;
    int size;
    typeof(marked_variable) marked_variable;
};

要理解forwarding的巧妙之处,需要先看一段普通代码

__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
NSLog(@"%d", val);

该代码所要表达的意思是,val变量既可以在block中修改,也可以在block以外修改

  • block已经拷贝到heap上了,由于要始终持有val,所以val变量所对应的结构体也将被拷贝到heap上
  • 我们知道以上代码块是在stack空间上执行的
  • 当代码执行到++val一句时,stack上的变量val的结构体并没有销毁
  • 就是说此时在stack和heap上出现了连个val对应的结构体,它们是如何共享或共同修改一个val的值的呢

看下图中,答案就是,stack和heap上的forwarding指针都指向heap上的val了

forwarding指针的设计对软件设计有一定帮助。 但不适合用作面试题,候选人知道forwarding能说明什么?能说明看过《Pro Multithreading and Memory Management for iOS and OS X》这本书。 这种记忆型知识点,容易记也容易忘

capture时机与原理

capture的时机是block初始化时

block有可能capture三种类型的变量:局部变量、__block的局部变量、全局变量

前面两种情况最为常见

之前的文章中详细说过capture局部变量和__block变量的区别:值传递和引用传递的区别

在Block Implementation Specification也明确提到过

Variables of auto storage class are imported as const copies. Variables of __block storage class are imported as a pointer to an enclosing data structure. Global variables are simply referenced and not considered as imported.

该问题作为面试题比较合适,在实际应用开发中很常见,也很容易出现问题。比如看如下代码,找错

Task *task;
task = [Task startWithBlock:^{
	// finish task
	[task cancel];
}];

多层block嵌套时,应如何写weakSelf

一段代码就可以清晰描述该问题

@weakify(self); // 1
[self dosth: ^{
	@strongify(self); // 2
	[self doOtherthing:^{
		// 3
		self.propertyA = xxx;
	}];	
}]

此处@weakify(self)等价于__weak typeof(self) weakSelf = self; @strongify(self)等价于__strong typeof(self) self = weakSelf;

  • 问题:请问3处是否要weakSelf来避免内存循环引用
  • 答案:要写,@strongify(self)

其实只要能理解capture的原理,该问题也就容易解决

doOthering中的block在capture外部self时,肯定选择当前代码块下的self,当前的self是一个strong的self,所以capture后,block也会强持有self

参考