Swift 通过 Sourcery 管理路由

74 阅读2分钟

背景

  • 路由定义:项目中使用路由跳转时往往需要定义很多路由路径,如果直接使用硬编码,不利于代码维护,调用也不方便,如果使用枚举去定义每一个路由,随着路由路径加深,枚举数量增多,维护起来也不方便。
  • 路由参数:如果无法在定义路由时明确路由所需的参数,那么实际在使用时只能传入Any,十分不方便。

Sourcery

Sourcery 是一个 Swift 代码生成的开源命令行工具,它 (通过 SourceKitten) 使用 Apple 的 SourceKit 框架,来分析你的源码中的各种声明和标注,然后套用你预先定义的模板 (进行代码生成。

我们可以定义一个枚举,通过sourcery来自动生成所需要的路由路径代码。同时为每个路由的路径节点明确参数。

路由枚举

enum SocialRouteModule: SourceryRouteGenerator {
  case message(Message)
	case xxx
  ...
}

extension SocialRouteModule {
  enum Message {
    /// 会话列表
    case conversation
    /// 消息首页
    case home
    /// 好友页面
    case friend(Friend)
    /// 单聊页面
    case chat(String)
    /// 站内信
    case appMessage
    
    enum Friend {
      /// 搜索好友页面
      case search
      /// 好友设置页面
      case setting(FriendInfo)
      /// 发出好友请求
      case request(Request, SearchUser)
      /// 修改好友备注
      case remark(String)
      /// 通讯录
      case contact
      
      enum Request {
        /// 好友请求列表
        case list
      }
    }
  }
}

我们使用这样若干个枚举来描述所需要管理的路由:

  • 遵循协议 SourceryRouteGenerator 的被称为base路由,只有base路由枚举管理的枚举才会自动生成代码。
  • 每个case代表路由路径上的一个节点
  • 如果此节点不是叶子结点,那么它需要一个与其同名的枚举(首字母大写),并设定为其关联值,此枚举中的case作为此节点的子节点。结构类似一个多叉树。
  • 如果此节点路由需要参数,为其添加一个关联值,作为路由参数,此参数类型应当遵循Codable。(两个关联值的顺序没有要求)

路由协议

public protocol Route {
  var scheme: String { get }
  var domain: String { get }
  var path: String { get }
  var route: String { get }
  var params: Codable? { get }
  
  init()
  init(path: String)
}

协议定义了路由的相关参数。

自动生成的代码

extension _LFCRoute {
  var account: Account { Account(path: path.appending("account/")) }
  
  struct Account: Route {
    var path: String
    
    init(path: String) {
      self.path = path
    }
  }
}

extension _LFCRoute.Account {
  var login: Login { Login(path: path.appending("login/")) }
  
  struct Login: Route {
    var path: String
    
    var _params: Optional<AccountInfo> = nil
    var params: Codable? { _params }
    
    func params(_ data: AccountInfo) -> Self {
      var variableSelf = self
      variableSelf._params = data
      return variableSelf
    }
    
    init(path: String) {
      self.path = path
    }
  }
}

...

模版会自动生成类似的代码,根据枚举的结构,生成若干对应的 struct,在使用路由时,可以通过链式语法一层一层调用所需要的路由,同时传入所需要的参数。

路由的使用

print(LFC.account.bind.google.params(.init(value: "er", bindId: "aa")).route)
print(LFC.profile.edit.name.route)
print(LFC.developer.fileExplorer.route)
print(LFC.friend.request.detail.params(.init(value: "asd", id: 999)).route)
print(LFC.account.bind.params(.init(value: "bindValue", bindId: "lfc_sdz")).route)

//输出:
/*
lfc://happy.account/bind/google?value=er&bindId=aa
lfc://happy.profile/edit/name
lfc://happy.developer/fileExplorer
lfc://happy.friend/request/detail?id=999&value=asd
lfc://happy.account/bind?value=bindValue&bindId=lfc_sdz
*/

相关代码:

github.com/ssly1997/So…

你可以在这里找到相关代码和sourcery模版