在设计
Router组件的时候,其中必不可少的一步是路由注册,将pattern和handler函数进行绑定。
使用 @_silgen_name 来进行注册
1 | @_silgen_name("/user") |
实现原理
什么是 @silgen_name
根据 Swift 的官方手册,@silgen_name 有两个作用:
- To specify the symbol name of a Swift function so that it can be called from Swift-aware C. Such functions have bodies.
- To provide a Swift declaration which really represents a C declaration. Such functions do not have bodies.
swift/StandardLibraryProgrammersManual.md
所以可以利用第2个特性给 handler 函数指定一个导出函数符号 Exported Symbols,然后通过将 pattern 作为导出符号即可绑定对应的 handler 函数。
同时为了避免与其它导出符号冲突并且提高查找的效率,可以给导出符号加一个约定的前缀,如 ditto:。
注册
运行时利用 _dyld_register_func_for_add_image() 来监听所有的 image 加载,然后遍历带特定前缀的导出函数符号。
1 | _dyld_register_func_for_add_image { image, slide in |
然后通过 unsafeBitCast(_:to:) 函数将函数符号转为 handler 函数进行注册。
1 | let routes: [(String, Route<Coordinator>.Handler)] = symbols |
进阶
Ditto 在设计之初便支持范型,并且可以同时存在多个 Router(虽然没太必要)。
范型
_dyld_register_func_for_add_image() 是一个 C 函数,支持传入一个回调函数指针作为参数,定义如下:
1 | void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)); |
回调函数并不同于一般的 closure,它不能捕获上下文信息:A C function pointer cannot be formed from a closure that captures context。所以对于支持范型的 Router 来说,就不能直接的调用这个函数。
所以我们需要用过一些其他手段先把遍历到的函数符号先存起来,再重新读取出来即可。
1 | class SIL { |
模块化
假如同时存在多个 Router 的话,那么只靠 pattern 无法区分要注册到哪一个。同时由于 _dyld_register_func_for_add_image() 函数无法捕获上下文,也无法传递指定的 silgen_name 前缀来进行分开遍历。
一个可行但并不优雅的方法依然是在 silgen_name 的命名中做文章,如约定特殊的前缀来进行区分。
如果不同的 Router 是在不同的模块中(module),那么可以通过遍历指定的 image (通过 _dyld_get_image_name() 方法)来注册当前模块的符号。
swift/StandardLibraryProgrammersManual.md
Should anyone be using @_silgen_name outside of the standard library developers?