{"id":21668442,"url":"https://github.com/zgjff/fjrouter","last_synced_at":"2025-03-20T07:18:37.587Z","repository":{"id":263974951,"uuid":"891936781","full_name":"zgjff/FJRouter","owner":"zgjff","description":"正则、参数解析匹配、路由(重定向、回调、子路由)、资源中心、事件总线","archived":false,"fork":false,"pushed_at":"2025-03-18T12:40:17.000Z","size":225,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-20T07:18:31.958Z","etag":null,"topics":["event","eventbus","pod","regular","resource","router","spm","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zgjff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-21T08:18:21.000Z","updated_at":"2025-03-18T12:40:21.000Z","dependencies_parsed_at":"2024-12-22T02:26:30.968Z","dependency_job_id":"c135d519-1ada-4fa3-91d4-939e28cd0b68","html_url":"https://github.com/zgjff/FJRouter","commit_stats":null,"previous_names":["zgjff/fjrouter"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zgjff%2FFJRouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zgjff%2FFJRouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zgjff%2FFJRouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zgjff%2FFJRouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zgjff","download_url":"https://codeload.github.com/zgjff/FJRouter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244566948,"owners_count":20473451,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["event","eventbus","pod","regular","resource","router","spm","swift"],"created_at":"2024-11-25T12:15:52.531Z","updated_at":"2025-03-20T07:18:37.580Z","avatar_url":"https://github.com/zgjff.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FJRouter\n[![Swift](https://img.shields.io/badge/Swift-6.0-orange?style=flat-square)](https://img.shields.io/badge/Swift-6.0-Orange?style=flat-square)\n![](https://img.shields.io/cocoapods/p/FJRouter.svg?style=flat)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FJRouter.svg?style=flat-square)](https://img.shields.io/cocoapods/v/FJRouter.svg)\n[![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)\n\n## 简介\n\u003e 理解中的路由应该包含路由页面跳转管理、通过url获取对应位置的资源\n\n框架包含三大模块:\n\n- [路由页面跳转管理](#路由页面跳转管理)\n\n- [资源管理中心](#资源管理中心)\n\n- [事件总线](#事件总线)\n\n## 基础设施: `url`的参数正则解析\n通过提前注册url pattern规则`/path/:{路径参数}`, 通过正则表达式解析并匹配其中的所有参数。eg:\n\n- 单个参数: 注册路径`/users/:userId`, 解析出此url路径的参数数组为`[userId]`, 可以匹配`/users/...`, 如`/users/123`, 可以匹配成功并解析出`userId`为`123`;\n\n- 多个参数: 注册路径`/city/school/:sname/class/:cid`, 解析出此url路径的参数数组为`[sname, cid]`, 可以匹配`/city/school/xxx/class/xxx`, 如`/city/school/清华大学/class/123`, 可以匹配成功并解析出参数为`[\"sname\": \"清华大学\", \"cid\": \"123\"]`;\n\n- 无参数: 注册路径`/settings/detail`, 可以匹配`/settings/detail`,以及携带查询参数的`url`, 如`/settings/detail?q=a`、`/settings/detail?p=1\u0026q=2`...\n\n## 路由页面跳转管理\n\n### 页面跳转`FJRouterJumpable`协议\n1: 路由页面跳转是定义为`FJRouterJumpable`的协议, 可以通过`FJRouter.jump()`获取框架内跳转管理中心对象.\n\n2: 支持通过路由路径和已经注册的路由名称进行对应操作\n\u003e 当然在这里, 建议通过路由名称相关的api进行操作, 如`go(.name(...))`, `viewController(.name(...))`方法; why:\n\n\u003e 当路由路径比较复杂,且含有参数的时候, 如果通过硬编码的方法直接手写路径, 可能会造成拼写错误,参数位置错误等错误\n\n\u003e 在实际app中, 路由的`URL`格式可能会随着时间而改变, 但是一般路由名称不会去更改\n\n```swift \n通过路由路径获取对应的控制器: \nlet vc = try await FJRouter.jump().viewController(.loc(\"/\"), extra: nil)\n\n通过路由名称获取对应的控制器:\nlet vc2 = try await FJRouter.jump().viewController(.name(\"root\", params: [\"id\": \"123\"]), extra: nil)\n\n通过路由路径导航至对应控制器:\ntry await FJRouter.jump().go(.loc(\"/\"), extra: nil, from: self, ignoreError: true)\n\n通过路由名称导航至对应控制器:\ntry await FJRouter.jump().go(.name(\"root\"), extra: nil, from: self, ignoreError: true)\n```\n\n3: 支持路由回调\n\u003e 路由回调是使用`Combine`框架实现的\n\n\u003e 不需要提前注册回调方法, 只需要在收到`Combine`事件流中区分对应的事件\n\n```swift\nlet callback = try await FJRouter.jump().go(.name(\"root\"), extra: nil, from: self, ignoreError: true)\ncallback.sink(receiveCompletion: { cop in\n    print(\"cop----全部\", cop)\n}, receiveValue: { item in\n    print(\"value----全部\", item)\n}).store(in: \u0026cancels)\n\n\n触发:需要viewController方调用\ntry? dispatchFJRouterCallBack(name: \"haha\", value: ())\ndismiss(animated: true, completion: { [weak self] in\n    try? self?.dispatchFJRouterCallBack(name: \"completion\", value: 123)\n})\n```\n\n### 路由模型model:`FJRoute`\n\u003e 每一个路由都通过`FJRoute`对象进行配置.\n\n#### 路由匹配路径`path`\n1: 如果是起始父路由, 其`path`必须以`/`为前缀\n\n2: 支持路径参数, 路由参数将被解析并储存在`JJRouterState`中, 用于[builder](#构建路由方式-builder)和[redirect](#路由拦截器-redirect)\n\n#### 路由的名称: `name`\n如果赋值, 必须提供唯一的字符串名称, 且不能为空\n\n#### 构建路由方式: `builder`\n1: 此参数是`block`形式的类型别名\n```swift\npublic typealias Builder = (@MainActor @Sendable (_ info: BuilderInfo) -\u003e UIViewController?)\n```\n\n2: 此参数可以为`nil`, 但是为`nil`时, 重定向参数`redirect`不能为`nil`\n\n3: `builder`可以根据路由信息`BuilderInfo`返回对应的控制器, 也可以在不符合条件的情况下, 返回`nil`\n\n#### 显示路由控制器的方式: `animator`\n1: 此参数是`block`形式的类型别名\n```swift \npublic typealias Animator = (@MainActor @Sendable (_ info: AnimatorInfo) -\u003e any FJRouteAnimator)\n```\n2: 用于在使用`go(...)`方法进行跳转时, 页面的展现方式。\n\n3: 可以使用框架已内置的动画方式对象, 或者可以使用自己实现的协议对象, 具体参考[FJRouteAnimator](#路由显示动画协议-fjrouteanimator)。\n\n4: 没有`push`和`present`的概念; 所有的动画细节均隐藏在协议对象里:\n\n\u003e `FJRoute.SystemPushAnimator`对应`push`\n\n\u003e `FJRoute.SystemPresentAnimator`对应`present`\n\n#### 路由拦截器: `redirect`\n\n1: 有些路由地址需要拦截器，例如对于没有登录的用户，有些页面就无法访问.eg: \n```swift\nlet loginRoute = try! FJRoute(path: \"/login\", name: \"login\", builder: { info in\n    return UIViewController()\n}, redirect: FJRouteCommonRedirector(redirect: { state in\n    let hasLogin = xxx\n    if hasLogin { // true, 即代表已经登录, 此时允许可以跳转至login路由\n        return .original\n     }\n    // hasLogin: false, 即代表未登录, 此时页面在未登录相关的页面, 如登录/注册/发送验证码...等页面, 此时不允许跳转至login路由, 防止多重的跳转至登录\n    return .interception\n}))\n```\n\n2: 此参数可以为`nil`, 但是为`nil`时, 构建控制器参数`builder`不能为`nil`\n\n#### 关联的子路由: `routes`\n\n1: 所谓子路由就是: 一个大的路由页面下面的多个相关联的路由。如设置页面下的设置项a,b,c,d...\n\n2: 子路由还一个好处是, 可以减少拼写相同的path路径。如\n```swift\n设置项：FJRoute(path: \"/user/settings\", name: \"user_settings\", xxx)\n设置项a：FJRoute(path: \"/user/settings/a\", name: \"user_settings_a\", xxx)\n设置项b：FJRoute(path: \"/user/settings/b\", name: \"user_settings_a\", xxx)\n设置项c：FJRoute(path: \"/user/settings/c\", name: \"user_settings_c\", xxx)\n...\n\n上述所有路由可以总结为一个设置项路由:\ntry FJRoute(path: \"/user/settings\", name: \"user_settings\", xxx, routes: [\n    FJRoute(path: \"a\", name: \"user_settings_a\", xxx),\n    FJRoute(path: \"b\", name: \"user_settings_b\", xxx),\n    FJRoute(path: \"c\", name: \"user_settings_c\", xxx),\n    ...\n])\n```\n\n3: 子路由也支持此当前路由的参数\n\n4: 强烈建议子路由的`path`不要以`/`为开头\n\n```swift\nlet route = try FJRoute(path: \"/play/:id\", builder: ({ _  in ViewControllerPlay() }), routes: [\n    FJRoute(path: \"feature1\", builder: ({ _  in ViewControllerPlay1() })),\n    FJRoute(path: \"feature2\", name: \"bfeature2\", builder: ({ _ in ViewControllerPlay2() })),\n    FJRoute(path: \"feature3/:name\", builder: ({ _  in ViewControllerPlay3() })),\n    FJRoute(path: \"feature3\", name: \"bfeature3\", builder: ({ _  in ViewControllerPlay4() })),\n    FJRoute(path: \"feature3\", name: \"bfeature3-1\", builder: ({ _  in ViewControllerPlay5() })),\n    FJRoute(path: \"feature4/:name\", name: \"feature4\", builder: ({ _  in ViewControllerPlay6() })),\n])\n```\n\n### 路由显示动画协议: `FJRouteAnimator`\n\n1: 此协议只有一个方法\n\n```swift\n/// 开始路由转场动画\n/// - Parameters:\n///   - fromVC: 要跳转到的源控制器\n///   - toVC: 匹配到的路由指向的控制器\n///   - matchState: 匹配到的路由信息\n@MainActor func startAnimatedTransitioning(from fromVC: UIViewController?, to toVC: UIViewController, state matchState: FJRouterState)\n```\n\n2: 内置动画显示方式:\n\u003e 以`FJRoute.xxxxAnimator`为开头的实例对象\n\n- 设置app window的rootViewController: `FJRoute.AppRootControllerAnimator`\n\n- 系统present动画进行显示: `FJRoute.SystemPresentAnimator`\n\n- 系统push进行显示: `FJRoute.SystemPushAnimator`\n\n- 根据情况自动选择动画方式: `FJRoute.AutomaticAnimator`\n\n- 自定义`custom`的`present`转场动画: `FJRoute.CustomPresentationAnimator`\n\n- 自定义转场动画进行push: `FJRoute.CustomPushAnimator`\n\n- 自定义`fullScreen`的`present`转场动画: `FJRoute.FullScreenPresentAnimator`\n\n- 系统push/pop动画风格的present/dismiss转场动画, 支持侧滑dismiss: `FJRoute.PresentSameAsPushAnimator`\n\n- 刷新与匹配控制器相同类型的上一个控制器动画: `FJRoute.RefreshSamePreviousAnimator`\n  ```swift\n    try await FJRouter.shared.registerRoute(FJRoute(path: \"/four\", name: \"four\", builder: { sourceController, state in\n        FourViewController()\n    }, animator: { info in\n        if let pvc = info.fromVC, pvc is FourViewController { // 或者其它判断条件\n            return FJRoute.RefreshSamePreviousAnimator { @Sendable previousVC, state in\n                previousVC.view.backgroundColor = .random()\n                previousVC.updatexxxx()\n            }\n        }\n        return FJRoute.SystemPushAnimator()\n    }))\n  ```\n\n### 路由重定向协议: `FJRouteRedirector`\n\n1: 此协议只有一个方法: 根据匹配状态进行判断返回对应路由的url路径\n\n```swift\n/// 重定向行为: interception: 不可以跳转, 即路由守卫/original: 不需要重定向/new(xxx)需要重定向到新路由路径: 如果返回的是`nil`, 也不需要重定向\nfunc redirectRouteNext(state: FJRouterState) async -\u003e FJRouteRedirectorNext\n```\n\n2: app已经内置了一个通用的重定向: `FJRouteCommonRedirector`\n\n3: 路由循环重定向, 框架内部在进行路由匹配的时候, 如果检测到巡航重定向, 则会抛出错误。如:\n```\na-\u003eb-\u003ec-\u003ed-\u003ea\n```\n\n4: 最大重定向次数: 框架内部在进行单个路由匹配的时候, 会记录重定向次数, 如果次数大于设定的值, 会抛出错误。默认最大重定向次数为5, 可以通过如下代码进行设置修改:\n```swift\nawait FJRouter.jump().setRedirectLimit(50)\n```\n\n\n### 注册、跳转事例代码:\n```swift\n注册\nstatic func register() async throws {\n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/\", name: \"root\", builder: { @MainActor @Sendable _ in ViewController()}, animator: { info in\n            FJRoute.AutomaticAnimator(navigationController: UINavigationController())\n        }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/first\", name: \"first\", builder: { _ in FViewController() }, animator: { _ in FJRoute.SystemPushAnimator() }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/second\", name: \"second\", builder: { _ in SViewController() }, animator: { _ in\n        FJRoute.CustomPresentationAnimator(navigationController: UINavigationController())\n    }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/third\", name: \"third\", builder: { _ in TViewController() }, animator: { _ in FJRoute.SystemPresentAnimator(fullScreen: true, navigationController: UINavigationController()) }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/four\", name: \"four\", builder: { _ in FourViewController() }, animator: { info in\n        if let pvc = info.fromVC, pvc is FourViewController {\n            return FJRoute.RefreshSamePreviousAnimator { @Sendable previousVC, state in\n                previousVC.view.backgroundColor = .random()\n            }\n        }\n        return FJRoute.SystemPushAnimator()\n    }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/five\", name: \"five\", builder: { _ in FiveViewController() }, animator: { _ in FJRoute.  CustomPresentationAnimator(navigationController: UINavigationController()) { @Sendable ctx in\n        ctx.usingBottomPresentation()\n    }}))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/six\", name: \"six\", builder: { _ in SixViewController() }, animator: { info in\n        return FJRoute.PresentSameAsPushAnimator(navigationController: UINavigationController())\n    }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/seven\", name: \"seven\", builder: { _ in SevenViewController() }, animator: { info in\n        return FJRoute.PresentSameAsPushAnimator(navigationController: UINavigationController())\n    }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/eight\", name: \"eight\", builder: { _ in EightViewController() }, animator: { info in\n        return FJRoute.AutomaticAnimator()\n    }))\n        \n    try await FJRouter.jump().registerRoute(FJRoute(path: \"/nine\", name: \"nine\", builder: { _ in NineViewController() }, animator: { info in\n        return FJRoute.SystemPushAnimator()\n    })) \n}\n```\n\n```swift \n跳转\nFJRouter.jump().go(.name(\"first\"))\n\nlet callback = await FJRouter.jump().go(.name(\"second\"), extra: nil, from: self, ignoreError: true)\ncallback.sink(receiveCompletion: { cop in\n    print(\"cop----全部\", cop)\n}, receiveValue: { item in\n    print(\"value----全部\", item)\n}).store(in: \u0026cancels)\n\nFJRouter.jump().go(.loc(\"/six\"))\n```\n\n## 资源管理中心\n\n### 资源管理`FJRouterResourceable`协议:\n\n1: 资源管理是定义为`FJRouterResourceable`的协议, 可以通过`FJRouter.resource()`获取框架资源管理中心对象.\n\n2: 建议使用try FJRouterResource(path: \"/xxx\", name: \"xxx\", value: xxx), get(name: xxx)方法进行相关操作。\n\n\u003e 当资源路径比较复杂,且含有参数的时候, 如果通过硬编码的方法直接手写路径, 可能会造成拼写错误,参数位置错误等错误\n\n\u003e 在实际app中, 资源的`URL`格式可能会随着时间而改变, 但是一般资源名称不会去更改\n\n### 存放资源:\n\n```swift \nfunc put(_ resource: FJRouterResource) async throws\n```\n\n1: 资源可以是int, string, enum, uiview, uiviewcontroller, protocol...\n\n2: 资源必须是`Sendable`修饰的对象\n\n3: 存放资源的时候可以携带参数\n\n4: 适用于全局只会存放一次的资源: 如单例中或者`application:didFinishLaunchingWithOptions`中, 或者存放的资源内部具体的值是个固定值, 不会随着时间/操作更改\n\n5: 如果每次存放资源可能会更改, 建议使用`put(_ resource: FJRouterResource, uniquingPathWith: xxx)`方法\n\n6: 事例代码:\n\n```swift \nlet r1 = try FJRouterResource(path: \"/intvalue1\", name: \"intvalue1\", value: { _ in 1 })\ntry await FJRouter.resource().put(r1)\n\nlet r3 = try FJRouterResource(path: \"/intOptionalvalue2\", name: \"intOptionalvalue2\") { @Sendable info -\u003e Int? in\n    return nil\n}\ntry await FJRouter.resource().put(r3)\n\nlet r5 = try FJRouterResource(path: \"/intOptionalvalue3/:optional\", name: \"intOptionalvalue3\", value: { @Sendable info -\u003e Int? in\n    let isOptional = info.pathParameters[\"optional\"] == \"1\"\n    return isOptional ? nil : 1\n})\ntry await FJRouter.resource().put(r5)\n\n存放协议\nlet r6 = try FJRouterResource(path: \"/protocolATest/:isA\", name: \"protocolATest\", value: { @Sendable info -\u003e ATestable in\n    let isA = info.pathParameters[\"isA\"] == \"1\"\n    return isA ? AModel() : BModel()\n})\ntry await FJRouter.resource().put(r6)\n```\n\n### 根据策略存放资源\n\n```swift \nfunc put(_ resource: FJRouterResource, uniquingPathWith combine: @Sendable (_ current: @escaping FJRouterResource.Value, _ new: @escaping FJRouterResource.Value) -\u003e FJRouterResource.Value) async\n```\n\n1: 如果已经存放过相同`path`的资源, 不会抛出`FJRouter.PutResourceError.exist`错误, 会按照`combine`策略进行合并\n\n2: 适用于可能多处/多处存放: 如某个viewController, 出现的时候才去存储资源, 但是因为viewController可能会多次进入, 而且每次存放的资源的具体值均不相同, 使用此方法可以有效的存储, 不会抛出`FJRouter.PutResourceError.exist`错误\n\n3: 资源的名称会优先使用新的资源name, 如果新的资源name为nil, 才会使用旧资源name\n\n4: 事例代码\n```swift \n// 使用旧值\nawait FJRouter.resource().put(r) { (currnet, _) in currnet }\n// 使用新值\nawait FJRouter.resource().put(r) { (_, new) in new }\n```\n\n### 获取资源\n\n1: 可以通过路径和资源名称获取资源\n\n```swift \nfunc get\u003cValue\u003e(_ uri: FJRouter.URI, inMainActor mainActor: Bool) async throws(FJRouter.GetResourceError) -\u003e Value where Value: Sendable\n```\n\n2: 事例代码\n```swift\nlet intvalue1: Int = try await FJRouter.resource().get(.loc(\"/intvalue1\"), inMainActor: false)\nlet intvalue3: Int? = try await FJRouter.resource().get(.loc(\"/intvalue1\"), inMainActor: true)\nlet intOptionalvalue3: Int? = try await FJRouter.resource().get(.loc(\"/intOptionalvalue2\"), inMainActor: false)\nlet stringvalue1: String = try await FJRouter.resource().get(.name(\"intvalue1\"), inMainActor: false)\nlet aTestable1: ATestable = try await FJRouter.resource().get(.loc(\"/protocolATest/1\"), inMainActor: false)\nlet aTestable2: ATestable = try await FJRouter.resource().get(.name(\"/protocolATest/0\"), inMainActor: true)\nlet aTestable3: BModel = try await FJRouter.resource().get(.name(\"/protocolATest/0\"), inMainActor: false)\n```\n\n### 更新资源\n1: 可以通过路径和资源名称更新资源\n```swift \nfunc update(byPath path: String, value: @escaping FJRouterResource.Value) async throws\n\nfunc update(byName name: String, value: @escaping FJRouterResource.Value) async throws\n```\n\n2: 如果没有存放过相同path的资源, 会抛出`FJRouter.GetResourceError.notFind`错误\n\n3: 事例代码\n```swift\ntry await impl.update(byName: \"sintvalue1\", value: { _ in 66 })\n```\n\n### 删除资源\n\n```swift \nfunc delete(byPath path: String) async throws\n\nfunc delete(byName name: String) async throws\n```\n\n1: 必须是已经存放过的资源, 删除不存在的资源会抛出`FJRouter.GetResourceError.notFind`错误\n\n2: 可以根据名称或者路由删除\n\n3: 事例代码:\n\n```swift\ntry await FJRouter.resource().delete(byPath: \"/intvalue1\")\ntry await FJRouter.resource().delete(byName: \"intOptionalvalue1\")\n```\n\n## 事件总线\n\n### 事件总线`FJRouterEventable`协议\n\n1: 事件总线协议定义为`FJRouterEventable`的协议, 可以通过`FJRouter.event()`获取事件总线管理对象.\n\n2: 建议使用onReceive(path: \"xxx\", name: \"xxx\"), emit(name: xxx)方法进行相关操作。\n\n\u003e 当事件路径比较复杂,且含有参数的时候, 如果通过硬编码的方法直接手写路径, 可能会造成拼写错误,参数位置错误等错误\n\n\u003e 在实际app中, 事件的`URL`格式可能会随着时间而改变, 但是一般事件名称不会去更改\n\n### 监听事件\n\n1: 监听动作是通过系统`Combine`框架进行响应, 不持有监听者\n\n2: 可以一对多的进行监听, 即可以在多处监听\n\n3: 事例代码\n```swift\n无参\nlet seekSuccess = try await FJRouter.event().onReceive(path: \"/seek/success\", name: \"onSeekSuccess\")\nseekSuccess.receive(on: OperationQueue.main)\n.sink(receiveValue: { info in\n    print(\"onSeekSuccess=\u003e\", info)\n}).store(in: \u0026self.cancels)\n\n有参\nlet seekProgress = try await FJRouter.event().onReceive(path: \"/seek/:progress\", name: \"onSeekProgress\")\nseekProgress.receive(on: OperationQueue.main)\n.sink(receiveValue: { info in\n    print(\"onSeekProgress=\u003e\", info)\n}).store(in: \u0026self.cancels)\n```\n\n### 触发事件\n\n1: 可以通过事件路径和名称进行触发\n\n2: 事例代码\n```swift\n通过事件url路径触发事件\n//无参\ntry await FJRouter.event().emit(\"/seek/success\", extra: 5)\n//有参: 1就是监听\"/seek/:progress\"中的progress字段\ntry await FJRouter.event().emit(\"/seek/1\", extra: nil)\n\n通过事件名称触发事件\n//无参\ntry await FJRouter.event().emit(.name(\"onSeekProgress\"), extra: nil)\n// 有参\ntry await FJRouter.event().emit(.name(\"onSeekProgress\", params: [\"progress\": \"1\"]), extra: nil)\n```\n\n## 安装\n\u003e 从2.0.2分支开始, 要求swfitVersion\u003e=6, 即必须使用xcode 16.0版本以上\n\n### Swift Package Manager\n\u003e 使用 **Swift PM** 的最简单的方式是找到 Project Setting -\u003e Swift Packages \n\n\u003e 搜索 `https://github.com/zgjff/FJRouter` 并添加\n\n### CocoaPods\n在 Podfile 文件中添加 FJRouter:\n```rb\npod 'FJRouter'\n```\n然后运行 `pod install`。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzgjff%2Ffjrouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzgjff%2Ffjrouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzgjff%2Ffjrouter/lists"}