TRA-2


Tip: 统一隐藏 Type 的 property setter

在编写程序时, 经常需要仔细设计一个 model 的各个 property 的读写性质. 通常, 为了方便, 我们会把 model 的所有 property 设为 var, 来允许我们的一部分代码对 property 进行延迟赋值或者其他修改.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Model {
var value: Int = 0
var name: String = ""
}

/// implementation
func getModel() -> Model {
return Model()
}

/// client
var model = getModel()
print(model.value)
print(model.name)
model.value = 3
model.name = "default"

然而, 在大型程序中, 我们并不希望所有该 model 的访问者都可以随意修改 property, 它们只需要使用 getter, 而不应该调用 setter. 于是, 有些情况下会选择给使用者提供另一个只读的 model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct ClientModel {
let value: Int
let name: String

init(_ model: Model) {
self.value = model.value
self.name = model.name
}
}

/// implementation
func getModel2() -> ClientModel {
return ClientModel(Model())
}

/// client
var model2 = getModel2()
print(model2.value)
print(model2.name)
model2.value = 3 // compiler error
model2.name = "default" // compiler error

然而, 如果很多 model 都有这个需求, 将会导致 code 中出现很多重复的实现代码.

使用 Swift 中的 dynamicMenmberLookup 的 keyPath 静态版本, 可以大量简化这一工作: 我们只需要定义一个 generic 的 readonly wrapper, 就可以套用到任意的 model 类型上.
这里使用的 Swift 特性 dynamicMenmberLookup 可以起到类似 c++ 中重载 “.” 和 “->” 运算符的效果, 将对某个对象的 property access 转发到另一个内部对象上.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@dynamicMemberLookup
struct ReadOnly<T> {
private let _base: T

init(_ base: T) {
self._base = base
}

subscript<Value>(dynamicMember keyPath: KeyPath<T, Value>) -> Value {
return _base[keyPath: keyPath]
}
}

/// implementation
func getModel3() -> ReadOnly<Model> {
return ReadOnly(Model())
}

/// client
var model3 = getModel3()
print(model3.value)
print(model3.name)
model3.value = 3 // compiler error
model3.name = "default" // compiler error

在上面的实现中, 我们只提供了接受 KeyPath 参数的 subscript, 而没有提供 WritableKeyPath 参数的版本, 从而在接口上消除了所有 property setter.

Receive: 在 App 运行时收集 code coverage 信息

最近参加公司内部的一次技术分享, 该分享介绍了一个工具, 可以在 App 测试过程中收集开发编写的代码的运行情况, 做到动态 code coverage 收集.

之前我只了解, 使用 Xcode 跑 Unit Test 时, Apple 会插入一些新的编译/链接选项, 给运行的 code 进行插桩, 并在 code 运行完后根据运行时的记录信息生成报告; 但是我并不知道运行中的 App 本身是否能获取到自身运行的情况. 经过一些 searching, 原来 LLVM 是支持这一操作的, 也有官方文档详细描述了使用姿势. 核心要点摘录如下:

  • Source-based Code Coverage 描述了主要的 code coverage 流程
    • Using the profiling runtime without static initializers这一节主要描述了如何在运行时获取到 profile 信息
      • __llvm_profile_set_filename 可以设置 profile file 的路径
      • __llvm_profile_write_file 调用一次可以把当前的 profile 写到指定的路径
    • llvm 提供了 llvm-profdata 和 llvm-cov 等工具来处理生成的 profile 文件, 并解析成可读的 JSON 格式
  • Clang 文档也有关于 profile 的一些例子, 并且详细阐述了一些 compiler flag 的用法.

Algorithm: Divide Two Integers

题目

来自 leetcode.

1
2
3
4
5
6
7
8
9
10
Given two integers dividend and divisor, 
divide two integers without using multiplication,
division, and mod operator.

Return the quotient after dividing dividend by divisor.

Example:
Input: dividend = 10, divisor = 3
Output: 3
Explanation: 10/3 = truncate(3.33333..) = 3.

实现

禁止乘法和除法的情况下, 理论上计算 a/ba / b 只需要在 aa 上不断减去 bb, 直到无数可减. 但这样性能不高, 实际上随着商的位数增加, 每次可以减的可以比 bb 更多. 如果 a/b=ca / b = c, 把 c 按二进制方式写成 ckck1...c1c0c_k c_{k-1} ... c_1 c_0, 每一位的贡献是后一项的两倍.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func divide(_ dividend: Int, _ divisor: Int) -> Int {
if dividend <= Int32.min && divisor == -1 {
return Int(Int32.max)
}

let isPositive = (dividend > 0 ? 1 : 0) ^ (divisor > 0 ? 1 : 0) == 0
let dividend = dividend > 0 ? dividend : -dividend
let divisor = divisor > 0 ? divisor : -divisor

if divisor > dividend {
return 0
}

/// 构造余数各位的贡献, 对应余数每一位乘以除数后的值
var factors: [(Int, Int)] = []
var factor = divisor
var scale = 1

repeat {
factors.append((factor, scale))
factor *= 2
scale *= 2
} while (factor <= dividend)

var result = 0
var remaining = dividend

/// 从贡献高的位开始不断尝试, 够减就表示该位为 1
for (factor, scale) in factors.reversed() {
if remaining >= factor {
result += scale
remaining -= factor
}
}
return isPositive ? result : -result
}