{"id":18246615,"url":"https://github.com/douban/frdintent","last_synced_at":"2025-04-05T11:12:34.016Z","repository":{"id":56301709,"uuid":"67681320","full_name":"douban/FRDIntent","owner":"douban","description":"A framework for handle the call between view controllers in iOS","archived":false,"fork":false,"pushed_at":"2020-11-16T02:47:42.000Z","size":368,"stargazers_count":492,"open_issues_count":3,"forks_count":59,"subscribers_count":25,"default_branch":"master","last_synced_at":"2025-03-29T10:11:20.405Z","etag":null,"topics":["decoupling","intent","router"],"latest_commit_sha":null,"homepage":null,"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/douban.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}},"created_at":"2016-09-08T07:53:08.000Z","updated_at":"2025-03-28T08:42:09.000Z","dependencies_parsed_at":"2022-08-15T16:20:12.906Z","dependency_job_id":null,"html_url":"https://github.com/douban/FRDIntent","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/douban%2FFRDIntent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/douban%2FFRDIntent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/douban%2FFRDIntent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/douban%2FFRDIntent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/douban","download_url":"https://codeload.github.com/douban/FRDIntent/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247325695,"owners_count":20920714,"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":["decoupling","intent","router"],"created_at":"2024-11-05T09:26:59.593Z","updated_at":"2025-04-05T11:12:33.978Z","avatar_url":"https://github.com/douban.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FRDIntent\n\n[![Test Status](https://travis-ci.org/douban/FRDIntent.svg?branch=master)](https://travis-ci.org/douban/FRDIntent)\n[![Language](https://img.shields.io/badge/language-Swift%203-orange.svg)\n](https://developer.apple.com/swift/)\n[![IDE](https://img.shields.io/badge/XCode-8-blue.svg)]()\n[![iOS](https://img.shields.io/badge/iOS-8.0-green.svg)]()\n\n**[README in English](./README_EN.md)**\n\n**FRDIntent** 包括两部分 `FRDIntent/Intent` 和 `FRDIntent/URLRoutes`。它们分别可以用于处理 iOS 系统中，应用内和应用外的 view controller 调用。\n\n`FRDIntent/Intent` 是一个消息传递对象，用于启动 UIViewController。可以认为它是对 Android 系统中的 [Intent](https://developer.android.com/guide/components/intents-filters.html) 的模仿。\n\n`FRDIntent/URLRoutes` 是一个 URL Router。通过 FRDIntent/URLRoutes 可以用 URL 调起一个注册过的 block。\n\n可以看出，FRDIntent/URLRoutes 和社区已经存在的诸多 URL Routers 的功能和目的差别不大。在 FRDIntent 中，实现 URLRoutes 是为了让 FRDIntent/URLRoutes 和 FRDIntent/Intent 一起配合解决应用内和应用外 view controller 的调用。Intent 处理内部 view controller 跳转；URLRoutes 负责外部调用。在 FRDIntent/URLRoutes 的实现中，FRDIntent/URLRoutes 只是起了暴露外部调用入口，接收外部调用的作用。在应用内，仍然是通过 FRDIntent/Intent 启动 view controller。也就是说外部调用实际上是通过内部调用实现的。\n\n这么做其实是为了隔离了外部调用和内部调用，做这个区分会带来一些好处：\n\n- iOS 系统提供的通过 URL 调用另外一个应用功能本身就是使用在应用间的。iOS 系统中，应用之间的隔离是清晰而明确的，通过 URL 在应用之间传递信息是合适的。但是，如果在应用内部调用也使用 URL 传递信息，就会带来诸多限制。Intent 更适合内部调用的场景。通过 Intent，可以传递复杂数据对象，可以较容易地定义转场动画。这些在 URL 方案中都很难做到。\n- 区分了外部调用和内部调用。我们就可以选择是否将一个内部调用暴露给外部使用。这就避免了在 URL 的方案中，无法区分内部调用和外部调用，将本应只给内部使用的调用也暴露给了外部。\n\n\n## 安装\n\n### Install Cocoapods\n\n[CocoaPods](http://cocoapods.org) 是一个 Objective-c 和 Swift 的依赖管理工具。你可以通过以下命令安装 CocoaPods：\n\n```bash\n$ gem install cocoapods\n```\n\n### Podfile\n\n只使用 FRDInent/Intent：\n\n```ruby\ntarget 'TargetName' do\n  pod 'FRDIntent/Intent', :git =\u003e 'https://github.com/douban/FRDIntent.git'\nend\n```\n\n使用 FRDIntent/Intent 和 FRDIntent/URLRoutes：\n\n```ruby\ntarget 'TargetName' do\n  pod 'FRDIntent', :git =\u003e 'https://github.com/douban/FRDIntent.git'\nend\n```\n\n注意：`pod FRDInent` 和 `pod FRDIntent/URLRoutes` 将引入相同的代码。这是因为 FRDIntent/URLRoutes 依赖于 FRDIntent/Intent。\n\n然后，命令行运行：\n\n```bash\n$ pod install\n```\n\n### 版本\n\n版本选择：[https://github.com/douban/FRDIntent/releases](https://github.com/douban/FRDIntent/releases)。\n\n\n## Intent\n\n`FRDIntent/Intent` 是一个消息传递对象，用于启动 UIViewController。可以认为它是对 Android 系统中的 [Intent](https://developer.android.com/guide/components/intents-filters.html) 的模仿。当然，相对于 Android Intent，FRDIntent/Intent 做了极度简化。这是因为 FRDIntent/Intent 的使用场景更为简单：只处理应用内的 view controller 间跳转。\n\n直接使用 iOS 系统方法完成各 view controller 之间的跳转，各 view controller 会耦合得很紧。跳转时，一个 view controller 需要知道下一个 view controller 是如何创建的各种细节。这造成了 view controller 之间的依赖。使用 FRDIntent/Intent 传递跳转信息，可以解除 view controller 之间的耦合。\n\n如果需要对项目进行模块化，重要的一步就是解除各 view controller 之间的耦合。在这方面，FRDIntent 是一个可以考虑的方案。\n\nFRDIntent/Intent 有如下优势：\n\n- 充分解耦。调用者和被调用者完全隔离，被调用者只需要依赖协议：`FRDIntentReceivable`。一个 UIViewControlller 符合该协议即可被启动。\n- 对于“启动一个页面，并从该页面获取结果”这种较普遍的需求提供了一个通用的解决方案。具体查看方法：startControllerForResult。这是对 Android 中 startActivityForResult 的模仿和简化。\n- 支持自定义转场动画。\n- 支持传递复杂数据对象。\n\n### 使用\n\n主要通过类 `FRDControllerManager` 使用 FRDIntent/Intent。它提供了三个方法：`register` 用于注册，`startController` 和 `startControllerForResult` 用于启动页面。\n\n#### 注册\n\n通过代码注册：\n\n```Swift\n  let controllerManager = FRDControllerManager.sharedInstance\n  controllerManager.register(URL(string: \"/frodo/firstview\")!, clazz: FirstViewController.self)\n```\n\n通过 plist 文件批量注册：\n\n```Swift\n  let plistPath = Bundle.main.path(forResource: \"FRDIntentRegisters\", ofType: \"plist\")\n  let controllerManager = FRDControllerManager.sharedInstance\n  controllerManager.register(plistFile: plistPath)\n```\n\n#### 启动 view controller\n\n通过指定类名启动 view controller:\n\n```Swift\n  let intent = FRDIntent(clazz: SecondViewController.self)\n  let manager = FRDControllerManager.sharedInstance\n  manager.startController(source: self, intent: intent)\n```\n\n通过 URL 启动 view controller:\n\n```Swift\n  let intent = FRDIntent(uri: URL(string: \"/frodo/firstview\")!)\n  let manager = FRDControllerManager.sharedInstance\n  manager.startController(source: self, intent: intent)\n```\n\n#### 启动一个会返回结果的 view controller\n\n调用页面，该页面同时也是接受返回结果的页面。该 view controller 需要符合协议 `FRDIntentForResultSendable`：\n\n```Swift\n  extension ViewController: FRDIntentForResultSendable {\n\n    func onControllerResult(requestCode: Int, resultCode: FRDResultCode, data: Intent) {\n      if (requestCode == RequestText) {\n        if (resultCode == .ok) {\n          let text = data.extra[\"text\"]\n          print(\"Successful confirm get from destination : \\(text)\")\n        } else if (resultCode == .canceled) {\n          let text = data.extra[\"text\"]\n          print(\"Canceled get from destination : \\(text)\")\n        }\n      }\n    }\n\n  }\n```\n\n被调用的 view controller 需要符合协议 `FRDIntentForResultReceivable`。该协议是 `FRDIntentReceivable` 的子协议。在 `FRDIntentReceivable` 基础上，多了两个实例变量定义：\n\n```Swift\n  var delegate: FRDIntentForResultSendable?\n  var requestCode: Int?\n```\n\n通过 `startControllerForResult` 启动页面：\n\n```Swift\n  let intent = FRDIntent(clazz: ThirdViewController.self)\n  intent.putExtra(name: \"text\", data: \"Text From Source\")\n  let manager = FRDControllerManager.sharedInstance\n  manager.startControllerForResult(source: self, intent: intent, requestCode: RequestText)\n```\n\n#### 自定义转场动画\n\n在 FRDIntent 中，转场动画被抽象为协议：`FRDControllerDisplay`。自定义转场动画的实现需要符合该协议，并在启动页面时，将自定义的转场动画对象赋给 `FRDIntent` 的实例变量 `controllerDisplay` 即可完成转场动画的设置。\n\nFRDIntent/Intent 已提供了两个转场动画的实现：`FRDPushDisplay` 和 `FRDPresentationDisplay`。如果不指定转场动画，通过 `startController` 启动页面使用的是 `FRDPushDisplay`；通过 `startControllerForResult` 启动页面使用的是 `FRDPresentationDisplay`。\n\n\n## URLRoutes\n\n`FRDIntent/URLRoutes` 是一个 URL Router。通过 FRDIntent/URLRoutes 可以用 URL 调起一个注册过的 block。\n\niOS 系统为各个应用间的相互调用提供了一种基于 URL 的处理方案。即应用可以声明自己可以处理某些有特定 scheme 和 host 的 URL。其他应用就可以通过调用这些 URL 而跳转到该应用的某些页面。\n\nFRDIntent/URLRoutes 是为了使得 iOS 系统中这种基于 URL 的应用间调用的处理更为简单。所以 FRDIntent/URLRoutes 和社区已经存在的诸多 URL Routers 的功能和目的差别不大。FRDIntent 实现 URLRoutes 是为了使 FRDIntent/URLRoutes 可以和 FRDIntent/Intent 配合解决应用内和应用外 view controller 的调用。\n\n### 使用\n\n#### 向系统暴露应用可以接收的 URL\n\n在 Xcode 中选择你的项目的 Target, 点击 Info, 添加一项 URL Types。\n例如：\n\n- Identifier: com.frdintent\n- URL Schemes: frdintent\n- Role: Editor\n- Icon:\n\n#### 接管应用的 URL 处理\n\n```Swift\n  func application(app: UIApplication, openURL url: URL, options: [String : AnyObject]) -\u003e Bool {\n    return FRDURLRoutes.sharedInstance.route(url: url)\n  }\n```\n\n#### 注册\n\n通过代码注册一个 view controler。在第三方应用调起该 URL 时，会启动该 view controller。该 view controller 的进入动画为 Push 横滑进入方式。\n\n```Swift\n  let routes = FRDURLRoutes.sharedInstance\n  routes.register(url: URL(string: \"/story/:storyId\")!, clazz: SecondViewController.self)\n```\n\n通过 plist 文件批量注册，效果和上面通过代码注册一样。注册的 view controller 进入动画都为 Push 横滑进入方式。\n\n```Swift\n  let plistPath = Bundle.main.path(forResource: \"FRDURLRoutesRegisters\", ofType: \"plist\")\n  let routes = FRDURLRoutes.sharedInstance\n  routes.register(plistFile: plistPath)\n```\n\n注册一个 block handler。下面例子中的 block handler 中，用注册时的 URL 构造了一个 Intent，并将该 Intent 送出。FRDControllerManager 会处理这个 Intent。看是否有合适的 view controller 可以被启动。\n\n如果，需要定制 view controller 的转场动画，可以使用该方法注册 URL。\n\n```Swift\n  let router = FRDURLRoutes.sharedInstance\n  router.register(url: URL(string: \"/user/:userId\")!) { (params: [String: Any]) in\n    let intent = Intent(url: params[URLRoutes.URLRoutesURL] as! URL)\n    if let topViewController = UIApplication.topViewController() {\n      FRDControllerManager.sharedInstance.startController(source: topViewController, intent: intent)\n    }\n  }\n```\n\n### 获取 URL 参数\n\nFRDIntent/URLRoutes 支持简单的 URL 参数模式适配。上例中，我们以模式匹配的形式注册了 URL `\"/story/:storyId\"`。如有诸如 `frdintent://frdintent.com/story/123` 这样的外部调用，FRDIntent/URLRoutes 会将键 `storyId` 和值 `123` 存入 block handler 的参数 params 中。这样在 block handler 中就能通过键 `storyId` 获取值 `123`。\n\n\n## 注意点\n\n#### Prefix\n\nSwift 由于有可见性声明，并无需前缀来避免命名冲突。所以，前缀在 Swift 项目中并不需要。但我们仍然为公开类都添加了`FRD`前缀。这是由于该库仍然主要供 Objective-C 项目使用，为了避免 Objective-C 代码的命名冲突，还是加上了前缀。\n\n#### 参数 source 的类型\n\nFRDControllerManager 的方法 `startControllerForResult(source: UIViewController, intent: FRDIntent, requestCode: Int)` 没有严格限制 `source` 参数类型。`source` 精确的类型应该分别是形如 `UIViewController\u003cFRDIntentForResultSendable\u003e` 所表达的：“这是一个类，并且符合一个协议”。这在 Swift 3 中，仍然需要别扭地使用泛型声明来实现。但这里使用泛型声明并不精确，同时更麻烦的是泛型方法无法暴露给 Objective-C 使用。因此，FRDIntent 做了折衷，`source` 类型只是 UIViewController。使用者需要自己保证它也是符合 `FRDIntentForResultSendable` 协议的。\n\n\n## FRDIntentDemo\n\nFRDIntentDemo 对 FRDIntent 各种使用方法都做了演示。FRDIntentDemo 使用 Objective-C 实现，这是为了演示 FRDIntent 虽然使用 Swift 完成，但是对 Objective-C 有良好的兼容。\n\n对于外部调用的演示，可以在模拟器的 Safari 的地址栏中输入 `frdintent://frdintent.com/user/123`。正常情况下，访问该 URL 将会启动 FRDIntentDemo，并进入 FirstViewController。\n\n\n## 单元测试\n\nFRDIntentTests 文件夹包含了 FRDIntent 单元测试代码。单元测试不仅是对代码正确性的验证，也是查看如何使用 FRDIntent 的良好示例。\n\n\n## License\n\nThe MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdouban%2Ffrdintent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdouban%2Ffrdintent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdouban%2Ffrdintent/lists"}