Tip: 按 function 类型直接引用成员方法
背景: 全局方法
在 Swift 中实名函数可以直接用作 function 类型, 这在 functional 编程中非常有用.
例如: Array.sort(by:)
接受一个类型为 (Element, Element) -> Bool
的 comparator, 如果我们有一个函数就是这样的类型, 那么可以如下直接调用.
1 |
|
问题: 成员方法
在实际开发中, 我们不可能把所有方法定义成全局的 free function 或 static function, 而是会把逻辑定义成某个类型的实例方法. 这种情况下, 也有办法直接引用方法的名字, 而不用显式创建一个临时的 closure.
self 不是 comparator 的参数
当我们想引用的成员函数, 其显式的参数和返回值与接受的 function 类型一致, 那么可以直接用 obj.foo
的方式引用. 当 function 为 non-escaping 时, 且 obj
为 self
时, 可进一步省略, 直接传递 foo
.
1 | class Manager { |
这种 Swift 语法称为 partial application: 在上述的例子中, 为了实际执行 compare
, 我们本需要提供 self
和 lhs
, rhs
3 个参数, 但是我们在传递给 sort
时, 只"部分地"提供了 self 参数, 而不提供 lhs
和 rhs
, 这也是 “partial” 的含义.
通用地:
一个 Object
类型的任何实例成员方法 foo
, 如果其签名是 (Param0, Param1, Param2...) -> ReturnType
.
那么对一个该类型的对象 object
, 可以直接引用 object.foo
, 得到的是一个隐式创建的 closure, 其类型就是 (Param0, Param1, Param2...) -> ReturnType
.
需要注意, 当 Object
是 class 时, 这里创建的隐式 closure 从语义上对 self 有一个强引用, 注意使用时不要在 code 中产生循环强引用.
self 是 comparator 的参数
有时候我们想引用的成员函数, 显式的参数和返回值与接受的 function 类型本不一致, 但算上隐式的 self
后, 就一致了. 这种情况也是很常见的, 例如:
1 | struct Answer { |
如果我们也不想显式传建一个临时 closure, 那么我们可以使用类型名而不是对象名作为 qualifier 来引用这个方法, 例如 Answer.isBetter
.
这种引用方式非 full-application, 也非 partial-application, 而是 non-application.
在目前的 Swift 语言实现下, 一个 Object
类型的任何实例成员方法 foo
, 如果其签名是 (Param0, Param1, Param2...) -> ReturnType
.
那么直接引用 Object.foo
, 也会得到一个 closure. 但需要注意, 其类型并不是 (Object, Param0, Param1, Param2...) -> ReturnType
, 而是它的 curried 形式: (Object) -> (Param0, Param1, Param2...) -> ReturnType
.
因此, 针对上面这个例子, 是无法直接引用 Answer.isBetter
的. 试图调用 answers.sort(by: Answer.isBetter)
会产生编译错误.
此时, 在条件允许的情况下, 我们可以通过定义一个泛型的自定义的运算符, 把 curried 形式的函数转化为非 curried 形式, 之后就可以传递给目标参数了. 这本身也是 functional programming 的一个应用.
1 | prefix operator ~ |
Receive: Swift UI 布局系统探究
iOS 13 时, Apple 引入了原生 Swift framework SwiftUI. 他的优点十分明显, 但学习曲线并不平坦. 我个人认为, 至少有 2 个方面需要初学者花时间熟悉:
- 数据驱动. SwiftUI 针对不同生命期的 data, 提供了一系列 property wrapper, 在描述 View 时, UI 和 data 之间会创建隐式的绑定. 理解不同的 data wrapper 和驱动机制是关键之一.
- 布局系统. 和 UIKit 中显式或依赖 constraints 自动地操纵 frame/bounds 不同, SwiftUI 通过嵌套不同的 View 对象和调用 modifiers 来完成布局的描述. 从本质上说, 这是一个黑盒, 而 Apple 的文档并不特别详尽(尽管最近的几个版本 Apple 已经在改善文档方面作出了巨大的努力).
针对后一个问题, 整个社区始终在不断的学习和研究之中. Objc.io 之前推出的一本书籍和最近的一系列 video “SwiftUI Layout Explained” 对系统性地理解 SwiftUI 的布局流程有很大帮助.
Algorithm: N-皇后问题
题目
来自 leetcode
1 | The n-queens puzzle is the problem of placing n queens on an n x n chessboard |
解答
使用深度遍历和回溯技巧即可完成所有可能布局的遍历, 这里给出递归方式的实现.
1 | func solveNQueens(_ n: Int) -> [[String]] { |
这里的核心优化是选取了一个适当的数据结构来表示可能的的棋盘布局, 来省略大量的逻辑判断.