Bolts 1.9.1

Bolts 1.9.1

测试已测试
语言语言 Obj-CObjective C
许可证 NOASSERTION
发布最后发布2020年4月

Héctor RamosGrantland ChewNikita Lutsenko维护。



Bolts 1.9.1

Bolts

Build Status Coverage Status Pod Platform Pod License Reference Status

Pod Version Carthage compatible

Bolts是一组为简化移动应用开发而设计的底层库。Bolts由Parse和Facebook为我们自己的内部使用而设计,我们决定开源这些库以使它们对他人可用。使用这些库不需要使用任何Parse服务。也不需要拥有Parse或Facebook开发者账户。

Bolts包括

  • "Tasks",它使组织复杂的异步代码更加易于管理。任务类似于JavaScript Promise,但适用于iOS和Android。
  • App Links协议的实现,帮助您链接到其他应用中的内容并处理传入的深度链接。

有关更多信息,请查看Bolts iOS API参考

Tasks

要构建一个真正响应式的iOS应用程序,你必须将长时间运行的操作从UI线程上移除,并小心避免阻塞UI线程可能正在等待的任何东西。这意味着你需要将各种操作在后台执行。为了使这更简单,我们增加了一个名为 BFTask 的类。一个任务表示异步操作的结果。通常,一个 BFTask 会从一个异步函数中返回,并提供了继续处理任务结果的能力。当任务从一个函数返回时,它已经开始了自己的工作。一个任务不受特定线程模型的影响:它代表正在执行的工作,而不是它正在执行的位置。相比于其他异步编程方法(如回调),任务具有许多优点。BFTask 不是 NSOperation 或 GCD 的替代品。实际上,它们可以很好地协同工作。但任务确实填补了那些技术与未解决的问题。

  • BFTask 会为你处理依赖关系。与使用 NSOperation 进行依赖关系管理不同,你不需要在开始一个 BFTask 之前声明所有的依赖。例如,假设你需要保存一组对象,每个对象可能需要或不需要保存子对象。使用 NSOperation,你通常必须提前为每个子保存创建操作。但你并不总是知道在开始工作之前这是否是必要的。这可能会让使用 NSOperation 的依赖关系管理变得非常痛苦。即使在最好的情况下,你也必须在实际依赖于它们的操作之前创建依赖项,这会导致代码的顺序与执行顺序不同。使用 BFTask,你可以在任务的执行过程中决定是否会有子任务,并在那种情况下返回其他任务。
  • BFTasks 会释放它们的依赖。由于 NSOperation 强烈保留其依赖关系,如果你有一个按顺序排列的操作队列,并使用依赖关系来按顺序排列它们,你会有溢出,因为每个操作都会无限期地保留。由于 BFTasks 在运行回调后立即释放它们,所以一切都会自行清理。这可以减少内存使用,并简化内存管理。
  • BFTasks 会跟踪已完成任务的状态:它会跟踪是否有返回值,任务是否已取消,或者是否发生了错误。它还有用于传播错误的便捷方法。使用 NSOperation,你必须自己构建所有这些东西。
  • BFTasks 不依赖特定的线程模型。因此,一些任务可以进行操作队列的操作工作,而其他任务可以使用GCD的块来执行工作。这些任务可以无缝地相互依赖。
  • 连续执行多个任务不会像仅使用回调一样创建嵌套的“金字塔”代码。
  • BFTasks 完全可组合,允许你执行分支、并行和复杂错误处理,而不需要大量的命名回调导致的代码混乱。
  • 你可以按照执行顺序安排基于任务的代码,而不是将逻辑分散到分散的回调函数中。

对于本文档中的示例,假设有一些异步版本的常见Parse方法,称为 saveAsync:findAsync:,它们返回一个 Task。在后面的部分,我们将展示如何自己定义这些函数。

continueWithBlock 方法

每个BFTask都有一个名为continueWithBlock:的方法,该方法接受一个延续块。延续是当任务完成时将被执行的一块代码。你可以检查任务是否成功以及获取其结果。

// Objective-C
[[self saveAsync:obj] continueWithBlock:^id(BFTask *task) {
  if (task.isCancelled) {
    // the save was cancelled.
  } else if (task.error) {
    // the save failed.
  } else {
    // the object was saved successfully.
    PFObject *object = task.result;
  }
  return nil;
}];
// Swift
self.saveAsync(obj).continueWithBlock {
  (task: BFTask!) -> BFTask in
  if task.isCancelled() {
    // the save was cancelled.
  } else if task.error != nil {
    // the save failed.
  } else {
    // the object was saved successfully.
    var object = task.result() as PFObject
  }
}

BFTasks使用Objective-C块,因此语法应该很简单。让我们通过一个例子来更仔细地查看涉及的类型。

// Objective-C
/**
 * Gets an NSString asynchronously.
 */
- (BFTask *)getStringAsync {
  // Let's suppose getNumberAsync returns a BFTask whose result is an NSNumber.
  return [[self getNumberAsync] continueWithBlock:^id(BFTask *task) {
    // This continuation block takes the NSNumber BFTask as input,
    // and provides an NSString as output.

    NSNumber *number = task.result;
    return [NSString stringWithFormat:@"%@", number];
  )];
}
// Swift
/**
 * Gets an NSString asynchronously.
 */
func getStringAsync() -> BFTask {
  //Let's suppose getNumberAsync returns a BFTask whose result is an NSNumber.
  return self.getNumberAsync().continueWithBlock {
    (task: BFTask!) -> NSString in
    // This continuation block takes the NSNumber BFTask as input,
    // and provides an NSString as output.

    let number = task.result() as NSNumber
    return NSString(format:"%@", number)
  }
}

在许多情况下,你只想在先前的任务成功后进行更多的工作,并将任何错误或取消传播以供稍后处理。为此,请使用continueWithSuccessBlock:方法而不是continueWithBlock:

// Objective-C
[[self saveAsync:obj] continueWithSuccessBlock:^id(BFTask *task) {
  // the object was saved successfully.
  return nil;
}];
// Swift
self.saveAsync(obj).continueWithSuccessBlock {
  (task: BFTask!) -> AnyObject! in
  // the object was saved successfully.
  return nil
}

链式任务组合

BFTasks有点神奇,因为它们允许你在不嵌套的情况下链式。如果你在continueWithBlock:中返回一个BFTask,那么由continueWithBlock:返回的任务不会在从新的延续块返回新任务之前被考虑已完成。这让你能够执行多个操作而不会出现与回调相关的金字塔代码。同样,你还可以从continueWithSuccessBlock:返回一个BFTask。因此,返回一个BFTask来做更多异步工作。

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Student"];
[query orderByDescending:@"gpa"];
[[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
  NSArray *students = task.result;
  PFObject *valedictorian = [students objectAtIndex:0];
  [valedictorian setObject:@YES forKey:@"valedictorian"];
  return [self saveAsync:valedictorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
  PFObject *valedictorian = task.result;
  return [self findAsync:query];
}] continueWithSuccessBlock:^id(BFTask *task) {
  NSArray *students = task.result;
  PFObject *salutatorian = [students objectAtIndex:1];
  [salutatorian setObject:@YES forKey:@"salutatorian"];
  return [self saveAsync:salutatorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
  // Everything is done!
  return nil;
}];
// Swift
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
  (task: BFTask!) -> BFTask in
  let students = task.result() as NSArray
  var valedictorian = students.objectAtIndex(0) as PFObject
  valedictorian["valedictorian"] = true
  return self.saveAsync(valedictorian)
}.continueWithSuccessBlock {
  (task: BFTask!) -> BFTask in
  var valedictorian = task.result() as PFObject
  return self.findAsync(query)
}.continueWithSuccessBlock {
  (task: BFTask!) -> BFTask in
  let students = task.result() as NSArray
  var salutatorian = students.objectAtIndex(1) as PFObject
  salutatorian["salutatorian"] = true
  return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
  (task: BFTask!) -> AnyObject! in
  // Everything is done!
  return nil
}

错误处理

通过仔细选择是否调用continueWithBlock:continueWithSuccessBlock:,你可以控制应用中错误的传播方式。使用continueWithBlock:允许你通过转换或处理错误来处理错误。你可以把失败的任务比作抛出异常。事实上,如果你在延续块内抛出异常,这将导致相应的任务因为该异常而失败。

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Student"];
[query orderByDescending:@"gpa"];
[[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
  NSArray *students = task.result;
  PFObject *valedictorian = [students objectAtIndex:0];
  [valedictorian setObject:@YES forKey:@"valedictorian"];
  // Force this callback to fail.
  return [BFTask taskWithError:[NSError errorWithDomain:@"example.com"
                                                   code:-1
                                               userInfo:nil]];
}] continueWithSuccessBlock:^id(BFTask *task) {
  // Now this continuation will be skipped.
  PFQuery *valedictorian = task.result;
  return [self findAsync:query];
}] continueWithBlock:^id(BFTask *task) {
  if (task.error) {
    // This error handler WILL be called.
    // The error will be the NSError returned above.
    // Let's handle the error by returning a new value.
    // The task will be completed with nil as its value.
    return nil;
  }
  // This will also be skipped.
  NSArray *students = task.result;
  PFObject *salutatorian = [students objectAtIndex:1];
  [salutatorian setObject:@YES forKey:@"salutatorian"];
  return [self saveAsync:salutatorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
  // Everything is done! This gets called.
  // The task's result is nil.
  return nil;
}];
// Swift
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
  (task: BFTask!) -> BFTask in
  let students = task.result() as NSArray
  var valedictorian = students.objectAtIndex(0) as PFObject
  valedictorian["valedictorian"] = true
  //Force this callback to fail.
  return BFTask(error:NSError(domain:"example.com",
                              code:-1, userInfo: nil))
}.continueWithSuccessBlock {
  (task: BFTask!) -> AnyObject! in
  //Now this continuation will be skipped.
  var valedictorian = task.result() as PFObject
  return self.findAsync(query)
}.continueWithBlock {
  (task: BFTask!) -> AnyObject! in
  if task.error != nil {
    // This error handler WILL be called.
    // The error will be the NSError returned above.
    // Let's handle the error by returning a new value.
    // The task will be completed with nil as its value.
    return nil
  }
  // This will also be skipped.
  let students = task.result() as NSArray
  var salutatorian = students.objectAtIndex(1) as PFObject
  salutatorian["salutatorian"] = true
  return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
  (task: BFTask!) -> AnyObject! in
  // Everything is done! This gets called.
  // The tasks result is nil.
  return nil
}

在最后一个错误处理者后有一个长链的成功回调通常很方便。

创建任务

当你刚开始时,你只需使用由findAsync:saveAsync:等方法返回的任务。然而,对于更高级的场景,你可能想自己创建任务。为此,你需要创建一个BFTaskCompletionSource。这个对象将允许你创建一个新的BFTask,并控制它是否被标记为完成或取消。在创建了一个BFTaskCompletionSource之后,你需要调用setResult:setError:cancel来触发其延续。

// Objective-C
- (BFTask *)successAsync {
  BFTaskCompletionSource *successful = [BFTaskCompletionSource taskCompletionSource];
  [successful setResult:@"The good result."];
  return successful.task;
}

- (BFTask *)failAsync {
  BFTaskCompletionSource *failed = [BFTaskCompletionSource taskCompletionSource];
  [failed setError:[NSError errorWithDomain:@"example.com" code:-1 userInfo:nil]];
  return failed.task;
}
// Swift
func successAsync() -> BFTask {
  var successful = BFTaskCompletionSource()
  successful.setResult("The good result.")
  return successful.task
}

func failAsync() -> BFTask {
  var failed = BFTaskCompletionSource()
  failed.setError(NSError(domain:"example.com", code:-1, userInfo:nil))
  return failed.task
}

如果你在创建任务时就知道其结果,有一些方便的方法可以使用。

// Objective-C
BFTask *successful = [BFTask taskWithResult:@"The good result."];

BFTask *failed = [BFTask taskWithError:anError];
// Swift
let successful = BFTask(result:"The good result")

let failed = BFTask(error:anError)

创建异步方法

使用这些工具,你可以轻松创建返回任务的异步函数。例如,你可以轻松地创建 fetchAsync 的基于任务版本。

// Objective-C
- (BFTask *) fetchAsync:(PFObject *)object {
  BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
  [object fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
    if (!error) {
      [task setResult:object];
    } else {
      [task setError:error];
    }
  }];
  return task.task;
}
// Swift
func fetchAsync(object: PFObject) -> BFTask {
  var task = BFTaskCompletionSource()
  object.fetchInBackgroundWithBlock {
    (object: PFObject?, error: NSError?) -> Void in
    if error == nil {
      task.setResult(object)
    } else {
      task.setError(error)
    }
  }
  return task.task
}

同样容易创建 saveAsyncfindAsyncdeleteAsync

串行任务

BFTasks 在你需要连续执行一系列任务时很有用,每个任务都等待前一个任务完成。例如,想象你想删除博客上的所有评论。

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
[query whereKey:@"post" equalTo:@123];

[[[self findAsync:query] continueWithBlock:^id(BFTask *task) {
  NSArray *results = task.result;

  // Create a trivial completed task as a base case.
  BFTask *task = [BFTask taskWithResult:nil];
  for (PFObject *result in results) {
    // For each item, extend the task with a function to delete the item.
    task = [task continueWithBlock:^id(BFTask *task) {
      // Return a task that will be marked as completed when the delete is finished.
      return [self deleteAsync:result];
    }];
  }
  return task;
}] continueWithBlock:^id(BFTask *task) {
  // Every comment was deleted.
  return nil;
}];
// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)
findAsync(query).continueWithBlock {
  (task: BFTask!) -> BFTask in
  let results = task.result() as NSArray

  // Create a trivial completed task as a base case.
  let task = BFTask(result:nil)
  for result : PFObject in results {
    // For each item, extend the task with a function to delete the item.
    task = task.continueWithBlock {
      (task: BFTask!) -> BFTask in
      return self.deleteAsync(result)
    }
  }
  return task
}.continueWithBlock {
  (task: BFTask!) -> AnyObject! in
  // Every comment was deleted.
  return nil
}

并行任务

你还可以使用 taskForCompletionOfAllTasks: 方法并行执行多个任务。你可以一次启动多个操作,并使用 taskForCompletionOfAllTasks: 创建一个新任务,当所有输入任务都完成后,该任务将被标记为完成。新任务只有在所有传入任务都成功时才会成功。并行执行操作将比串行执行更快,但可能会消耗更多的系统资源和带宽。

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
[query whereKey:@"post" equalTo:@123];

[[[self findAsync:query] continueWithBlock:^id(BFTask *results) {
  // Collect one task for each delete into an array.
  NSMutableArray *tasks = [NSMutableArray array];
  for (PFObject *result in results) {
    // Start this delete immediately and add its task to the list.
    [tasks addObject:[self deleteAsync:result]];
  }
  // Return a new task that will be marked as completed when all of the deletes are
  // finished.
  return [BFTask taskForCompletionOfAllTasks:tasks];
}] continueWithBlock:^id(BFTask *task) {
  // Every comment was deleted.
  return nil;
}];
// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)

findAsync(query).continueWithBlock {
  (task: BFTask!) -> BFTask in
  // Collect one task for each delete into an array.
  var tasks = NSMutableArray.array()
  var results = task.result() as NSArray
  for result : PFObject! in results {
    // Start this delete immediately and add its task to the list.
    tasks.addObject(self.deleteAsync(result))
  }
  // Return a new task that will be marked as completed when all of the deletes
  // are finished.
  return BFTask(forCompletionOfAllTasks:tasks)
}.continueWithBlock {
  (task: BFTask!) -> AnyObject! in
  // Every comment was deleted.
  return nil
}

任务执行器

两个方法 continueWithBlock:continueWithSuccessBlock: 都有一个带有 BFExecutor 实例的另一种形式:continueWithExecutor:withBlock:continueWithExecutor:withSuccessBlock:。这些方法允许你控制如何执行后续操作。默认执行器会将任务调度到 GCD,但你可以提供自己的执行器来在不同的线程上调度工作。例如,如果你想在工作于 UI 线程上继续工作:

// Create a BFExecutor that uses the main thread.
BFExecutor *myExecutor = [BFExecutor executorWithBlock:^void(void(^block)()) {
  dispatch_async(dispatch_get_main_queue(), block);
}];

// And use the Main Thread Executor like this. The executor applies only to the new
// continuation being passed into continueWithBlock.
[[self fetchAsync:object] continueWithExecutor:myExecutor withBlock:^id(BFTask *task) {
    myTextView.text = [object objectForKey:@"name"];
}];

对于分发到主线程等常见情况,我们提供了 BFExecutor 的默认实现。包括 defaultExecutorimmediateExecutormainThreadExecutorexecutorWithDispatchQueue:executorWithOperationQueue:。例如

// Continue on the Main Thread, using a built-in executor.
[[self fetchAsync:object] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
    myTextView.text = [object objectForKey:@"name"];
}];

任务取消

跟踪BFTaskCompletionSource以进行取消通常是一种糟糕的设计。更好的模式是在顶级创建一个“取消令牌”,并将其传递给每个您希望成为同一“可取消操作”部分的异步函数。然后,在您的后续块中,您可以检查取消令牌是否已被取消,并且通过返回一个[BFTask cancelledTask]提前退出。例如

- (void)doSomethingComplicatedAsync:(MYCancellationToken *)cancellationToken {
    [[self doSomethingAsync:cancellationToken] continueWithBlock:^{
        if (cancellationToken.isCancelled) {
            return [BFTask cancelledTask];
        }
        // Do something that takes a while.
        return result;
    }];
}

// Somewhere else.
MYCancellationToken *cancellationToken = [[MYCancellationToken alloc] init];
[obj doSomethingComplicatedAsync:cancellationToken];

// When you get bored...
[cancellationToken cancel];

注意:取消令牌实现应该是线程安全的。我们可能会在未来某个时候向Bolts添加类似的概念。

应用链接

应用链接提供了一种跨平台机制,允许开发者为他们的内容定义并发布一个深链接方案,允许其他应用直接链接到为运行它们设备的优化体验。无论您是构建接收传入链接的应用还是可能链接到其他应用内容的应用,Bolts都提供了简化实现应用链接协议的工具。

处理应用链接

最常见的情况将是让您的应用接收应用链接。内嵌链接将允许用户快速访问他们在设备上可以访问的丰富内容、最原生感觉的展示。Bolts通过提供处理传入URL的工具,使处理传入应用链接(以及一般传入深链接)变得简单。

例如,您可以使用BFURL实用类在您的AppDelegate中解析传入的URL。

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
    BFURL *parsedUrl = [BFURL URLWithInboundURL:url sourceApplication:sourceApplication];

    // Use the target URL from the App Link to locate content.
    if ([parsedUrl.targetURL.pathComponents[1] isEqualToString:@"profiles"]) {
        // Open a profile viewer.
    }

    // You can also check the query string easily.
    NSString *query = parsedUrl.targetQueryParameters[@"query"];

    // Apps that have existing deep-linking support and map their App Links to existing
    // deep-linking functionality may instead want to perform these operations on the input URL.
    // Use the target URL from the App Link to locate content.
    if ([parsedUrl.inputURL.pathComponents[1] isEqualToString:@"profiles"]) {
        // Open a profile viewer.
    }

    // You can also check the query string easily.
    NSString *query = parsedUrl.inputQueryParameters[@"query"];

    // Apps can easily check the Extras and App Link data from the App Link as well.
    NSString *fbAccessToken = parsedUrl.appLinkExtras[@"fb_access_token"];
    NSDictionary *refererData = parsedUrl.appLinkExtras[@"referer"];
}

导航到URL

跟随应用链接可以让您的应用在用户导航到链接时提供最佳用户体验(由接收应用定义)。Bolts使此过程变得简单,自动化了跟随链接所需的步骤。

  1. 解析应用链接,通过获取指定URL的HTML中的应用链接元数据来解决问题。
  2. 遍历与正在使用的设备相关的应用链接目标,检查是否在设备上存在可以处理该目标的应用。
  3. 如果存在应用程序,则使用指定的 al_applink_data 生成 URL 并导航到该 URL。
  4. 否则,使用指定的原始 URL 打开浏览器。

在最简单的情况下,只需一行代码即可导航到可能包含 App Link 的 URL

[BFAppLinkNavigation navigateToURLInBackground:url];

添加应用程序和导航数据

在大多数情况下,在导航过程中需要传递给应用程序的数据将包含在 URL 本身中,这样无论应用程序是否实际安装在设备上,用户都会被引导到正确的内容。然而,偶尔应用程序将需要传递与应用程序到应用程序导航相关的数据,或者可能希望使用可能被应用程序用于调整应用程序行为的协议(例如,显示返回到引用应用程序的链接)来扩展 App Link 协议。

如果您想利用这些特性,可以分解导航过程。首先,您必须有一个希望导航到的 App Link

[[BFAppLinkNavigation resolveAppLinkInBackground:url] continueWithSuccessBlock:^id(BFTask *task) {
    BFAppLink *link = task.result;
}];

然后,您可以构建一个带有任何您想要的额外数据的 App Link 请求并导航

BFAppLinkNavigation *navigation = [BFAppLinkNavigation navigationWithAppLink:link
                                                                      extras:@{ @"access_token": @"t0kEn" }
                                                                 appLinkData:@{ @"ref": @"12345" }];
NSError *error = nil;
[navigation navigate:&error];

解析 App Link 元数据

Bolts 允许自定义 App Link 解析,这可以用作性能优化(例如,缓存元数据)或作为允许开发者使用集中式索引获取 App Link 元数据的机制。自定义 App Link 解析器只需能够从 URL 获取并返回一个包含适用此设备 BFAppLinkTarget 列表的 BFAppLink。Bolts 默认提供了这样的功能,该功能使用隐藏的 WKWebview 进行解析。

您可以通过在 BFAppLinkNavigation 上使用任何实现 BFAppLinkResolving 协议的解析器来实现这一点

[BFAppLinkNavigation navigateToURLInBackground:url
                                      resolver:resolver];

或者,您可以将默认解析器替换为内置 API使用的解析器

[BFAppLinkNavigation setDefaultResolver:resolver];
[BFAppLinkNavigation navigateToURLInBackground:url];

App Link 返回引用视图

当应用程序通过 App Link 打开时,应显示一个允许用户“触摸以返回”的横幅。BFAppLinkReturnToRefererView 提供了此功能。它将解析传入的 App Link 并解析引用信息来显示适当的调用应用程序名称。

- (void)viewDidLoad {
  [super viewDidLoad];

  // Perform other view initialization.

  self.returnToRefererController = [[BFAppLinkReturnToRefererController alloc] init];

  // self.returnToRefererView is a BFAppLinkReturnToRefererView.
  // You may initialize the view either by loading it from a NIB or programmatically.
  self.returnToRefererController.view = self.returnToRefererView;

  // If you have a UINavigationController in the view, then the bar must be shown above it.
  [self.returnToRefererController]
}

以下代码假定视图控制器有一个已填充用于打开应用程序的 URL 的 openedAppLinkURL NSURL 属性。您可以使用此操作来显示视图

- (void)viewWillAppear {
  [super viewWillAppear];

  // Show only if you have a back AppLink.
  [self.returnToRefererController showViewForRefererURL:self.openedAppLinkURL];
}

在导航控制器视图层次结构中,横幅应该在导航栏的上方显示,并且BFAppLinkReturnToRefererController提供了一个用于辅助此操作的initForDisplayAboveNavController方法。

分析

Bolts引入了测量事件。应用链接向应用发布三个不同的测量事件通知,这些通知可以被捕获并集成到您的应用中现有的分析组件中。

  • al_nav_out — 当您的应用跳转到App Links URL时触发。
  • al_nav_in — 当您的应用打开传入的App Links URL时触发。
  • al_ref_back_out — 当您的应用使用内置的顶部导航返回栏视图返回引用应用时触发。

监听App Links测量事件

还有一些与Bolts的App Links事件集成的分析工具,但您也可以自己监听这些事件。

[[NSNotificationCenter defaultCenter] addObserverForName:BFMeasurementEventNotificationName object:nil queue:nil usingBlock:^(NSNotification *note) {
    NSDictionary *event = note.userInfo;
    NSDictionary *eventData = event[BFMeasurementEventArgsKey];
    // Integrate to your logging/analytics component.
}];

App Links事件字段

App Links测量事件从App Links Intent中发送额外的信息,格式化为扁平的字符串键值对。以下是三个事件的几个有用字段。

  • al_nav_in

    • inputURL:打开应用的URL。
    • inputURLSchemeinputURL的方案。
    • referrerURL:在al_applink_data中添加到referrer_app_link的引用应用URL。
    • refererAppName:引用应用添加到al_applink_datareferer_app_link的应用名称。
    • sourceApplication:引用应用的包。
    • targetURLal_applink_data中的target_url字段。
    • version:App Links API版本。
  • al_nav_out / al_ref_back_out

    • outputURL:用于打开其他应用(或浏览器)的URL。如果有合适的应用可以打开,这将是在al_applink_data中的自定义方案URL/intent。
    • outputURLSchemeoutputURL的方案。
    • sourceURL:托管App Links元标记的页面的URL。
    • sourceURLHostsourceURL的主机名。
    • success“1”表示在另一个应用或浏览器中打开App Link成功;“0”表示无法打开App Link。
    • type“app”表示在应用中打开,“web”表示在浏览器中打开;当成功字段为“0”时,为“fail”
    • version:App Links API版本。

安装

您可以从我们的发布页面下载最新框架文件。

Bolts 也可通过 CocoaPods 获取。要安装它,只需将以下行添加到您的 Podfile 中

pod 'Bolts'