A URL Router for iOS/macOS, written in Swift
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
Khala
Sources add: KhalaInfo Jan 17, 2019
.gitignore init Nov 19, 2018
.travis.yml for ci Dec 19, 2018
Khala.podspec add: KhalaInfo Jan 17, 2019
LICENSE init Oct 19, 2018
README.md Document improvement Dec 21, 2018
_Pods.xcodeproj
document.sh 新增文档. Dec 18, 2018
readme_en.md Document improvement Dec 21, 2018

README.md

Khala

CI Status Version License Platform

Swift 路由和模块通信解耦工具和规范。 可以让模块间无耦合的调用服务、页面跳转。

English Introduction

特性

  • 支持 cocopods 组件化开发.
  • 无需注册URL,采用runtime来实现target-action形式调用.
  • 内置URL重定向模块.
  • 内置日志模块.
  • 支持模块自定义.
  • 内置断言,可切换语言.
  • 优先支持swift.

要求

  • iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+

  • Swift 4.x

安装

pod 'Khala'

定义

有部分内容无法准确定义,在此个人擅自定义以下名词.

  1. 路由类: 负责接收路由事件的NSOBject类.
  2. 路由函数: 路由类中 负责接收路由事件的函数.

快速使用

  1. URL

    在Khala中,最原始的URL结构为:

    scheme://[route class]/[route function]?key1=value1&key2=value2

    但是你可以编写重定向规则来实现复杂的URL结构,与权限控制.

  2. 首先我们定义2个独立的路由类文件, 并且将其分别封装至2个pod库中.

    该部分内容可以下载示例工程体验.

    • AModule.swift

      import UIKit
      import Khala
      
      @objc(AModule) @objcMembers
      class AModule: NSObject {
         
        func doSomething(_ info: [String: Any]) -> String {
          return description
        }
        
        func server(_ info: [String: Any]) -> Int {
          guard let value = info["value"] as? String, let res = Int(value) else {
              return 0 
          }
          return res
        }
        
        func forClosure(_ closure: KhalaClosure) {
          closure(["value": #function])
        }
        
        func forClosures(_ success: KhalaClosure, failure: KhalaClosure) {
          success(["success": #function])
          failure(["failure": #function])
        }
      
      }
    • BModule.swift

      import UIKit
      import Khala
      
      @objc(BModule) @objcMembers
      class BModule: NSObject {
        
        func doSomething(_ info: [String: Any]) -> String {
          return description
        }
        
      }
  3. 通过URL执行路由函数: Khala

    • 普通调用:

      // 2. 不保持参数类型,(url中参数类型皆为String)
      let value = Khala(str: "kl://AModule/server2?value=64")?.call() as? Int
      print(value ?? "nil")
      /// Print: 64
    • 异步调用:

      /// 单个block调用
      Khala(str: "kf://AModule/forClosure")?.call(block: { (item) in
       	print("forClosure:", item)
      })
      /// Print: forClosure: ["value": "forClosure"]
      // 
      
      
      /// 多个block调用
      Khala(str: "kf://AModule/forClosures")?.call(blocks: { (item) in
       	print("forClosures block3:", item)
      },{ (item) in
       	print("forClosure block4:", item)
      })
      //Print: forClosures block3: ["success": "forClosures(_:failure:)"]
      //Print: forClosure block4: ["failure": "forClosures(_:failure:)"]
      
      /// or
      Khala(str: "kf://AModule/forClosures")?.call(blocks: [{ (item) in
       	print("forClosures block1:", item)
      },{ (item) in
      	print("forClosure block2:", item)
      }])
      
      //Print: forClosures block1: ["success": "forClosures(_:failure:)"]
      //Print: forClosure block2: ["failure": "forClosures(_:failure:)"]
    • UIKit/AppKit 扩展调用:

      let vc = Khala(str: "kl://BModule/vc?style=0")?.viewController
  4. **路由通知 **KhalaNotify

    可以使用该类型来执行多个已缓存路由类中的同名函数.

    // 缓存 AModule 与 BModule 路由类.
    Khala(str: "kl://AModule")?.regist()
    Khala(str: "kl://BModule")?.regist()
        
    // 执行通知
    let value = KhalaNotify(str: "kl://doSomething?value=888")?.call()
    print(value ?? "")
    
    // Print: [<BModule: 0x60000242f230>, <AModule: 0x600002419d10>]

    通知只能发送至已被缓存的路由类中. 缓存路径: PseudoClass.cache

  5. 路由注册

    Khala中我提供了以下接口来抽象 PseudoClass.cache:

    /// 注册路由类, 等同于Khala(str: "kl://AModule/doSomething")
    func register() -> Bool
    // 取消注册路由类, 等同于 PseudoClass.cache["AModule"] = nil
    func unregister() -> Bool
    // 取消全部注册路由类, 等同于 PseudoClass.cache.removeAll()
    func unregisterAll() -> Bool
    // 批量注册遵守KhalaProtocol协议的路由类:
    Khala.registWithKhalaProtocol()

    作者个人建议, 请尽量避免使用.

    KhalaProtocol协议

  6. URL重定向: KhalaRewrite

    若开发者需要自定义路由解析规则或重定向路由函数,这部分则尤为重要.

    1. 构造规则:

      let filter = RewriteFilter {
       if $0.url.host == "AModule" {
      	var urlComponents = URLComponents(url: $0.url, resolvingAgainstBaseURL: true)!
      	urlComponents.host = "BModule"
      	$0.url = urlComponents.url!
       }
      	return $0
      }
    2. 添加至全局规则池

      Khala.rewrite.filters.append(filter)
    3. 请求调用

      let value = Khala(str: "kl://AModule/doSomething")?.call()
      print(value ?? "nil")
      /// Print: <BModule: 0x6000026e2800>
  7. 日志模块: KhalaHistory

    每一份url请求都将记录至日志文件中, 可以在适当的时候提供开发者便利.

    1. 开启日志(默认关闭)

      Khala.isEnabledLog = true
      // or 
      Khala.history.isEnabled = true
    2. 文件路径: /Documents/khala/logs/

    3. 文件内容: 日期 + 时间 + URL + 参数

    2018-12-01 02:06:54  kl://SwiftClass/double?  {"test":"666"}
    2018-12-01 02:06:54  kl://SwiftClass/double  {"test":"666"}
  8. 扩展机制: KhalaStore

    khala 库中提供了一个空置的类[KhalaStore]用于盛放路由函数对应的本地函数.来简化本地调用复杂度的问题.

    extension KhalaStore { 
     class func aModule_server(value: Int) -> Int {
        return Khala(str: "kf://AModule/server", params: ["value": value])!.call() as! Int
      }
    }
      
    @objc(AModule) @objcMembers
    class AModule: NSObject {
     func server(_ info: [String: Any]) -> Int {
        return info["value"] as? Int ?? 0
      }
    }
    
    let value = KhalaStore.aModule_server(value: 46)

    ps: KhalaStore 扩展文件建议统一放置.

  9. 断言机制

    为方便开发者使用,添加了部分场景下断言机制,示例:

    khala.iOS Fatal error: [Khala] 未在[AModule]中匹配到函数[server], 请查看函数列表:
    0: init
    1: doSomething:
    2: vc

    关闭断言(默认开启):

    Khala.isEnabledAssert = false
  10. 缓存机制: PseudoClass.cache

  • 当路由第一次调用/注册路由类时,该路由类将被缓存至 PseudoClass.cache 中, 以提高二次查找性能.
  • 当路由类实例化时,该路由类中的函数列表将被缓存至 PseudoClass().methodLists中, 以提高查找性能.

注意事项

  1. 路由类

    限制:

    1. 路由类必须继承自 NSObject

    2. 需要添加@objc(class_name) 要防止编译器移除该类.

      编译器会在编译时检查swift文件中未被调用的类,并移除.(>= swift 3.0)

    示例:

    // 推荐
    @objc(AModule) @objcMembers
    class AModule: NSObject {
     func server1(_ info: [String: Any]) -> Int { ... }
     func server2(_ info: [String: Any]) -> Int { ... }
    }
    
    // 也行
    @objc(BModule)
    class AModule: NSObject {
     @objc func server1(_ info: [String: Any]) -> Int { ... }
     @objc func server2(_ info: [String: Any]) -> Int { ... }
    }
  2. 路由函数

    限制:

    1. 不支持函数重载.例如:

      @objc(AModule) @objcMembers
      class AModule: NSObject {
       func server(_ info: [String: Any]) -> Int { ... }
       func server(_ info: [String: Any], closure: KhalaClosure) -> Int { ... }
      }

      缘由: khala 缓存了路由类中的函数列表, 键名为第一个:前的字符串.

    2. 推荐第一个参数采用匿名参数,方便阅读.

    3. 参数格式只支持

      • 单个: [AnyHashable: Any], 无顺序要求:

      • 多个: KhalaClosure, 有顺序要求:

        typealias KhalaClosure = @convention(block) (_ useInfo: [String: Any]) -> Void

      示例:

      @objc(AModule) @objcMembers
      class AModule: NSObject {
       func server1() -> Int { ... }
       func server2(info: [String: Any]) -> Int { ... }
       func server3(_ info: [String: Any]) -> Int { ... }
       func server4(_ info: [String: Any], closure: KhalaClosure) -> Int { ... }
       func server5(_ info: [String: Any], success: KhalaClosure,failure: KhalaClosure, 
      complete: KhalaClosure) -> Int { ... }
        func server6(_ success: KhalaClosure,failure: KhalaClosure, info: [String: Any],
      complete: KhalaClosure) -> Int { ... }
      }

      缘由:

      block 为结构体类型,无法抽象出基类或者协议.

      [String: Any] 会适当的插入 [KhalaClosure]中组成参数列表.

      ps: 调用方 KhalaClosure 数目需要比路由函数多或者持平.否则会触发断言.

  3. Khala 初始化函数

    • params中的参数会保持传入的类型,例如传递 UIImage等对象.
    public class Khala: NSObject {
    	public init(url: URL, params: [AnyHashable: Any] = [:]) { ... }
    	public init?(str: String, params: [AnyHashable: Any] = [:]) { ... } 
    }

进阶用法

  1. 自定义 重定向模块

    1. 继承 KhalaRewrite 协议.

    2. 替换重定向模块

      Khala.rewrite = CustomRewrite()
      
  2. 自定义 日志模块

    1. 继承 KhalaHistory 协议.

    2. 替换日志模块

      Khala.history = CustomHistory()

任务列表

  • 完善 Objective-C 调用
  • 完善demo示例.
  • 日志模块采用mmap读写(解决crash部分日志未写入文件).
  • 英文注释与文档.

文档

参考与致谢

  • CTMediator: 由 Casa 创建的 Target-Action 形式解耦路由.
  • Routable: khala 的前身, 正式投入生产环境迭代2年.
  • 星际争霸: khala 名称源自星际争霸背景设定中达拉姆星灵的主要宗教,它基于一种由信徒之间的灵能链接而形成的哲学。

作者

linhay, is.linhay@outlook.com

License

Khala is available under the MIT license. See the LICENSE file for more info.