Bolts is a collection of low-level libraries designed to make developing mobile apps easier.

Related tags

EventBus Bolts-ObjC
Overview

Bolts

Build Status Coverage Status Pod Platform Pod License Reference Status

Pod Version Carthage compatible

Bolts is a collection of low-level libraries designed to make developing mobile apps easier. Bolts was designed by Parse and Facebook for our own internal use, and we have decided to open source these libraries to make them available to others. Using these libraries does not require using any Parse services. Nor do they require having a Parse or Facebook developer account.

Bolts includes:

  • "Tasks", which make organization of complex asynchronous code more manageable. A task is kind of like a JavaScript Promise, but available for iOS and Android.
  • An implementation of the App Links protocol, helping you link to content in other apps and handle incoming deep-links.

For more information, see the Bolts iOS API Reference.

Tasks

To build a truly responsive iOS application, you must keep long-running operations off of the UI thread, and be careful to avoid blocking anything the UI thread might be waiting on. This means you will need to execute various operations in the background. To make this easier, we've added a class called BFTask. A task represents the result of an asynchronous operation. Typically, a BFTask is returned from an asynchronous function and gives the ability to continue processing the result of the task. When a task is returned from a function, it's already begun doing its job. A task is not tied to a particular threading model: it represents the work being done, not where it is executing. Tasks have many advantages over other methods of asynchronous programming, such as callbacks. BFTask is not a replacement for NSOperation or GCD. In fact, they play well together. But tasks do fill in some gaps that those technologies don't address.

  • BFTask takes care of managing dependencies for you. Unlike using NSOperation for dependency management, you don't have to declare all dependencies before starting a BFTask. For example, imagine you need to save a set of objects and each one may or may not require saving child objects. With an NSOperation, you would normally have to create operations for each of the child saves ahead of time. But you don't always know before you start the work whether that's going to be necessary. That can make managing dependencies with NSOperation very painful. Even in the best case, you have to create your dependencies before the operations that depend on them, which results in code that appears in a different order than it executes. With BFTask, you can decide during your operation's work whether there will be subtasks and return the other task in just those cases.
  • BFTasks release their dependencies. NSOperation strongly retains its dependencies, so if you have a queue of ordered operations and sequence them using dependencies, you have a leak, because every operation gets retained forever. BFTasks release their callbacks as soon as they are run, so everything cleans up after itself. This can reduce memory use, and simplify memory management.
  • BFTasks keep track of the state of finished tasks: It tracks whether there was a returned value, the task was cancelled, or if an error occurred. It also has convenience methods for propagating errors. With NSOperation, you have to build all of this stuff yourself.
  • BFTasks don't depend on any particular threading model. So it's easy to have some tasks perform their work with an operation queue, while others perform work using blocks with GCD. These tasks can depend on each other seamlessly.
  • Performing several tasks in a row will not create nested "pyramid" code as you would get when using only callbacks.
  • BFTasks are fully composable, allowing you to perform branching, parallelism, and complex error handling, without the spaghetti code of having many named callbacks.
  • You can arrange task-based code in the order that it executes, rather than having to split your logic across scattered callback functions.

For the examples in this doc, assume there are async versions of some common Parse methods, called saveAsync: and findAsync: which return a Task. In a later section, we'll show how to define these functions yourself.

The continueWithBlock Method

Every BFTask has a method named continueWithBlock: which takes a continuation block. A continuation is a block that will be executed when the task is complete. You can then inspect the task to check if it was successful and to get its result.

// 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 use Objective-C blocks, so the syntax should be pretty straightforward. Let's look closer at the types involved with an example.

// 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)
  }
}

In many cases, you only want to do more work if the previous task was successful, and propagate any errors or cancellations to be dealt with later. To do this, use the continueWithSuccessBlock: method instead of 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
}

Chaining Tasks Together

BFTasks are a little bit magical, in that they let you chain them without nesting. If you return a BFTask from continueWithBlock:, then the task returned by continueWithBlock: will not be considered finished until the new task returned from the new continuation block. This lets you perform multiple actions without incurring the pyramid code you would get with callbacks. Likewise, you can return a BFTask from continueWithSuccessBlock:. So, return a BFTask to do more asynchronous work.

// 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
}

Error Handling

By carefully choosing whether to call continueWithBlock: or continueWithSuccessBlock:, you can control how errors are propagated in your application. Using continueWithBlock: lets you handle errors by transforming them or dealing with them. You can think of failed tasks kind of like throwing an exception. In fact, if you throw an exception inside a continuation, the resulting task will be faulted with that exception.

// 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
}

It's often convenient to have a long chain of success callbacks with only one error handler at the end.

Creating Tasks

When you're getting started, you can just use the tasks returned from methods like findAsync: or saveAsync:. However, for more advanced scenarios, you may want to make your own tasks. To do that, you create a BFTaskCompletionSource. This object will let you create a new BFTask, and control whether it gets marked as finished or cancelled. After you create a BFTaskCompletionSource, you'll need to call setResult:, setError:, or cancel to trigger its continuations.

// 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
}

If you know the result of a task at the time it is created, there are some convenience methods you can use.

// 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)

Creating Async Methods

With these tools, it's easy to make your own asynchronous functions that return tasks. For example, you can make a task-based version of fetchAsync: easily.

// 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
}

It's similarly easy to create saveAsync:, findAsync: or deleteAsync:.

Tasks in Series

BFTasks are convenient when you want to do a series of tasks in a row, each one waiting for the previous to finish. For example, imagine you want to delete all of the comments on your blog.

// 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
}

Tasks in Parallel

You can also perform several tasks in parallel, using the taskForCompletionOfAllTasks: method. You can start multiple operations at once, and use taskForCompletionOfAllTasks: to create a new task that will be marked as completed when all of its input tasks are completed. The new task will be successful only if all of the passed-in tasks succeed. Performing operations in parallel will be faster than doing them serially, but may consume more system resources and bandwidth.

// 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
}

Task Executors

Both continueWithBlock: and continueWithSuccessBlock: methods have another form that takes an instance of BFExecutor. These are continueWithExecutor:withBlock: and continueWithExecutor:withSuccessBlock:. These methods allow you to control how the continuation is executed. The default executor will dispatch to GCD, but you can provide your own executor to schedule work onto a different thread. For example, if you want to continue with work on the UI thread:

// 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"];
}];

For common cases, such as dispatching on the main thread, we have provided default implementations of BFExecutor. These include defaultExecutor, immediateExecutor, mainThreadExecutor, executorWithDispatchQueue:, and executorWithOperationQueue:. For example:

// 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"];
}];

Task Cancellation

It's generally bad design to keep track of the BFTaskCompletionSource for cancellation. A better model is to create a "cancellation token" at the top level, and pass that to each async function that you want to be part of the same "cancelable operation". Then, in your continuation blocks, you can check whether the cancellation token has been cancelled and bail out early by returning a [BFTask cancelledTask]. For example:

- (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];

Note: The cancellation token implementation should be thread-safe. We are likely to add some concept like this to Bolts at some point in the future.

App Links

App Links provide a cross-platform mechanism that allows a developer to define and publish a deep-linking scheme for their content, allowing other apps to link directly to an experience optimized for the device they are running on. Whether you are building an app that receives incoming links or one that may link out to other apps' content, Bolts provides tools to simplify implementation of the App Links protocol.

Handling an App Link

The most common case will be making your app receive App Links. In-linking will allow your users to quickly access the richest, most native-feeling presentation of linked content on their devices. Bolts makes it easy to handle an inbound App Link (as well as general inbound deep-links) by providing utilities for processing an incoming URL.

For example, you can use the BFURL utility class to parse an incoming URL in your AppDelegate:

- (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"];
}

Navigating to a URL

Following an App Link allows your app to provide the best user experience (as defined by the receiving app) when a user navigates to a link. Bolts makes this process simple, automating the steps required to follow a link:

  1. Resolve the App Link by getting the App Link metadata from the HTML at the URL specified.
  2. Step through App Link targets relevant to the device being used, checking whether the app that can handle the target is present on the device.
  3. If an app is present, build a URL with the appropriate al_applink_data specified and navigate to that URL.
  4. Otherwise, open the browser with the original URL specified.

In the simplest case, it takes just one line of code to navigate to a URL that may have an App Link:

[BFAppLinkNavigation navigateToURLInBackground:url];

Adding App and Navigation Data

Under most circumstances, the data that will need to be passed along to an app during a navigation will be contained in the URL itself, so that whether or not the app is actually installed on the device, users are taken to the correct content. Occasionally, however, apps will want to pass along data that is relevant for app-to-app navigation, or will want to augment the App Link protocol with information that might be used by the app to adjust how the app should behave (e.g. showing a link back to the referring app).

If you want to take advantage of these features, you can break apart the navigation process. First, you must have an App Link to which you wish to navigate:

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

Then, you can build an App Link request with any additional data you would like and navigate:

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

Resolving App Link Metadata

Bolts allows for custom App Link resolution, which may be used as a performance optimization (e.g. caching the metadata) or as a mechanism to allow developers to use a centralized index for obtaining App Link metadata. A custom App Link resolver just needs to be able to take a URL and return a BFAppLink containing the ordered list of BFAppLinkTargets that are applicable for this device. Bolts provides one of these out of the box that performs this resolution on the device using a hidden UIWebView WKWebview.

You can use any resolver that implements the BFAppLinkResolving protocol by using one of the overloads on BFAppLinkNavigation:

[BFAppLinkNavigation navigateToURLInBackground:url
                                      resolver:resolver];

Alternatively, a you can swap out the default resolver to be used by the built-in APIs:

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

App Link Return-to-Referer View

When an application is opened via an App Link, a banner allowing the user to "Touch to return to " should be displayed. The BFAppLinkReturnToRefererView provides this functionality. It will take an incoming App Link and parse the referer information to display the appropriate calling app name.

- (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]
}

The following code assumes that the view controller has an openedAppLinkURL NSURL property that has already been populated with the URL used to open the app. You can then do something like this to show the view:

- (void)viewWillAppear {
  [super viewWillAppear];

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

In a navigation-controller view hierarchy, the banner should be displayed above the navigation bar, and BFAppLinkReturnToRefererController provides an initForDisplayAboveNavController method to assist with this.

Analytics

Bolts introduces Measurement Event. App Links posts three different Measurement Event notifications to the application, which can be caught and integrated with existing analytics components in your application.

  • al_nav_out — Raised when your app switches out to an App Links URL.
  • al_nav_in — Raised when your app opens an incoming App Links URL.
  • al_ref_back_out — Raised when your app returns back the referrer app using the built-in top navigation back bar view.

Listen for App Links Measurement Events

There are other analytics tools that are integrated with Bolts' App Links events, but you can also listen for these events yourself:

[[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 Event Fields

App Links Measurement Events sends additional information from App Links Intents in flattened string key value pairs. Here are some of the useful fields for the three events.

  • al_nav_in

    • inputURL: the URL that opens the app.
    • inputURLScheme: the scheme of inputURL.
    • refererURL: the URL that the referrer app added into al_applink_data: referer_app_link.
    • refererAppName: the app name that the referrer app added to al_applink_data: referer_app_link.
    • sourceApplication: the bundle of referrer application.
    • targetURL: the target_url field in al_applink_data.
    • version: App Links API version.
  • al_nav_out / al_ref_back_out

    • outputURL: the URL used to open the other app (or browser). If there is an eligible app to open, this will be the custom scheme url/intent in al_applink_data.
    • outputURLScheme: the scheme of outputURL.
    • sourceURL: the URL of the page hosting App Links meta tags.
    • sourceURLHost: the hostname of sourceURL.
    • success: “1” to indicate success in opening the App Link in another app or browser; “0” to indicate failure to open the App Link.
    • type: “app” for open in app, “web” for open in browser; “fail” when the success field is “0”.
    • version: App Links API version.

Installation

You can download the latest framework files from our Releases page.

Bolts is also available through CocoaPods. To install it simply add the following line to your Podfile:

pod 'Bolts'
Comments
  • Dynamic Library for iOS Carthage Support

    Dynamic Library for iOS Carthage Support

    Trying to address issue #152, this build I believe will support Carthage. The issue is that Bolts is currently a static library, whereas Carthage only supports dynamic libraries (hence why the Mac build works). I know there can be backwards compatibility issues so I've added a new target that builds dynamically. I'd really like this to get moved into production so that Facebook could support Carthage out of the box. Let me know if anything needs to be changed.

    CLA Signed 
    opened by lucasderraugh 25
  • Add Task Cancellation

    Add Task Cancellation

    opened by josephearl 21
  • Carthage compatibility

    Carthage compatibility

    This issue is for tracking compatibility with Carthage. OS X version is already compatible, but we need to make Bolts for iOS/tvOS also compile and work great with Carthage.

    enhancement help wanted 
    opened by nlutsenko 19
  • Xcode 9 Warnings

    Xcode 9 Warnings

    Seeing warnings in Xcode 9 of type "This block declaration is not a prototype". Can be fixed by Inserting 'void' inside ().

    In files BFCancellationToken.h, BFExecutor.h, BFTask.h thanks.

    opened by EverGod 17
  • Use a semaphore instead of NSCondition for -waitUntilFinished.

    Use a semaphore instead of NSCondition for -waitUntilFinished.

    NSCondition suffers from the possibility of spurious wakeups and requires a predicate to guard against this; -waitUntilFinished did not do this. Changed implementation to used a semaphore (from libdispatch).

    This fixes #134.

    CLA Signed needs investigation 
    opened by toddreed 17
  • Issue when chaining BFTasks created from Swift

    Issue when chaining BFTasks created from Swift

    The test [result isKindOfClass:[BFTask class]] is failing sometimes when using tasks created from Swift.

    The result of the test is undefined (know bug by apple)

    opened by flovilmart 17
  • NSInternalInconsistencyException for seemingly no reason

    NSInternalInconsistencyException for seemingly no reason

    I am using Bolts via Parse, and am seeing an untrappable bolts error, with Bolts trying to set an error, with no error except the faulted = YES .. with no hint, no clue, no ANYTHING except a stopped app.

    The ONLY thing changed between a working app and this was UI settings of another view, a child of the offending view.

    Unsettling that Bolts is giving nothing to act on yet is stopping my app from running. For now i am going to toss in a check for a nil error and ignore, but this is frustrating. ;)

    The stoppage occurs in:

    (void)setError:(NSError *)error { if (![self trySetError:error]) { [NSException raise:NSInternalInconsistencyException format:@"Cannot set the error on a completed task."]; } }

    I have no clue how to better describe this, and will try. The last time this happened, i simply stepped back an entire coding day and rebuilt, testing each save and got past the point of the last occurrence, which shows me it is not grounded in that which i am doing but in something very fuzzy.

    Same thing happened today, but i will only lose about 2 hours of work to rebuild.

    opened by wdcurry 17
  • “Include of non-modular header inside framework module” error in project with framework sub-dependency

    “Include of non-modular header inside framework module” error in project with framework sub-dependency

    The following error shows up on several Bolts library files when including a framework in a project which it itself requires Bolts. Compilation error: “Include of non-modular header inside framework module”

    This seems to be caused by the fact that header files are included directly in .h files instead of the correct Objective-C approach of specifying @class and @protocol in the header and doing the actual include only in the .m file (see thread here for exact problem: http://stackoverflow.com/questions/28552500/xcode6-receiving-error-include-of-non-modular-header-inside-framework-module)

    It looks like the Bolts code should be updated on these files with the correct Objective-C pattern in order to allow upper-level projects to correctly include frameworks that use Bolts as a sub-dependency.

    discussion 
    opened by marchy 15
  • Create separate API using Swift generics

    Create separate API using Swift generics

    Now that Swift has been released, the API design between Android and iOS should be brought together with the support of generics in Swift.

    Swift apps should be able to reference a more modern API that takes advantage of generics for task results and continuations.

    For legacy purposes it likely makes sense to keep the Objective-C implementation separate, since Obj-C does not support generics. However Swift codebases should not be held back from leveraging the advantages of generics due to the legacy nature of the Bolts Obj-C library.

    enhancement 
    opened by marchy 15
  • UIWebview -> WKWebView ( Tested )

    UIWebview -> WKWebView ( Tested )

    This pull request replaces use of deprecated UIWebView with WKWebView allowing Bolts-ObjC to be used without Apple's warning emails about deprecation of UIWebView.

    This code have passed for all tests.

    CLA Signed 
    opened by roremeol 13
  • Apple Review

    Apple Review

    After uploading the App to appstoreconnect,we receive this message:

    ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs . See https://developer.apple.com/documentation/uikit/uiwebview for more information.
    

    I am integrating Bolts by CocoaPods,I wonder how i can delete the files that using UIWebView related API.

    opened by seanLee 10
  • Library not loaded: @rpath/Bolts.framework/Versions/A/Bolts

    Library not loaded: @rpath/Bolts.framework/Versions/A/Bolts

    Hello, I am using the Parse SDK: https://github.com/parse-community/Parse-SDK-iOS-OSX I installed the Parse SDK through CocoaPods and when I try to run the project on Mac catalyst, I can build the project but I have this error at the beginning of the run:

    dyld[88120]: Library not loaded: @rpath/Bolts.framework/Versions/A/Bolts
      Referenced from: /Users/me/Library/Developer/Xcode/DerivedData/myproject-aonhubtyosxwqhaqhrzbjchzjkbq/Build/Products/Debug-maccatalyst/myproject.app/Contents/MacOS/myproject
      Reason: tried: '/Users/me/Library/Developer/Xcode/DerivedData/myproject-aonhubtyosxwqhaqhrzbjchzjkbq/Build/Products/Debug-maccatalyst/Bolts.framework/Versions/A/Bolts' (no such file),
    

    And there's around 20 more lines where it's showing the different paths that the system tried.

    Any idea on how to fix it?

    opened by mistralaix 0
  • Is Bolts relevant in 2022 for handling simple deeplinks opening from Facebook?

    Is Bolts relevant in 2022 for handling simple deeplinks opening from Facebook?

    Is Bolts-ObjC deprecated? It seems the last update was in 2020.

    In README, there's a deprecated AppDelegate method mentioned for handling App Links:

    - (BOOL)application:(UIApplication *)application
                openURL:(NSURL *)url
      sourceApplication:(NSString *)sourceApplication
             annotation:(id)annotation {
    

    Do we need to use this method? According to Apple documentation, it was deprecated, and Apple encourages us to use the application:openURL:options: method instead of this one. And do we need to use Bolts to parse and handle App Links that can come from Facebook? Or are there simple ways? My main point - is Bolts still relevant in 2022 for handling simple deeplinks opening from Facebook?

    opened by n0an 0
  • Failed to install apps when using Carthage and Xcode12.5

    Failed to install apps when using Carthage and Xcode12.5

    When we build applications depending on Bolts-ObjC using Carthage and following commands, the built application failed to install on iOS 14.6.

    carthage build --platform iOS --use-xcframeworks Bolts-ObjC
    

    The reason for the installation failure is the same as in the following link.

    code signature version is no longer supported
    

    https://developer.apple.com/forums/thread/679182

    I don't understand if this is a Carthage issue or a Bolts issue. However, I found a workaround that removes the Scheme for StaticLib in Bolts.

    Is there any other solution? Thanks.

    Versions

    Bolts-Objc: 1.9.1 Carthage: 0.38.0

    opened by soranoba 1
  • SPM Support

    SPM Support

    Adds SPM support.

    This is my first work with SPM; it seems like it has great potential, but is currently limited. I couldn't find a practical way to have iOS-specific files not compile for macOS with the manager, so there's some sad precompiler work in the iOS-specific files.

    CLA Signed 
    opened by drdaz 2
  • Crash BFTask.m - Line 54 Crashed: com.parse.asynctaskqueue.sync SIGABRT ABORT 0x00000001b70da95c

    Crash BFTask.m - Line 54 Crashed: com.parse.asynctaskqueue.sync SIGABRT ABORT 0x00000001b70da95c

    Version: 1.9.1

    Crash on BFTask Init method on line 54

     _callbacks = [NSMutableArray array];
    

    Maybe this should be

     _callbacks = [[NSMutableArray alloc] init];
    

    Crash info:

     Heap corruption detected, free list is damaged at 0x2814a8cc0 *** Incorrect guard value: 8081049728
    

    strack trace

    Crashed: com.parse.asynctaskqueue.sync
    0  libsystem_kernel.dylib         0x1b70da95c __pthread_kill + 8
    1  libsystem_pthread.dylib        0x1d237e9e8 pthread_kill + 212
    2  libsystem_c.dylib              0x1952cb934 abort + 100
    3  libsystem_malloc.dylib         0x19af9d030 _malloc_put + 554
    4  libsystem_malloc.dylib         0x19af9d2a4 malloc_zone_error + 100
    5  libsystem_malloc.dylib         0x19af9d2e0 nanov2_allocate_from_block$VARIANT$mp.cold.1 + 36
    6  libsystem_malloc.dylib         0x19af82428 nanov2_find_block_and_allocate$VARIANT$mp + 510
    7  libsystem_malloc.dylib         0x19af816a0 nanov2_allocate$VARIANT$mp + 124
    8  libsystem_malloc.dylib         0x19af81b90 nanov2_calloc$VARIANT$mp + 148
    9  libsystem_malloc.dylib         0x19af92aa4 _malloc_zone_calloc + 80
    10 libobjc.A.dylib                0x19ff024bc class_createInstance + 52
    11 CoreFoundation                 0x18c65d84c __CFAllocateObject + 20
    12 CoreFoundation                 0x18c573710 __NSArrayM_new + 56
    13 CoreFoundation                 0x18c53ea2c +[NSArray array] + 32
    14 Bolts                          0x106888e20 -[BFTask init] + 54 (BFTask.m:54)
    15 Bolts                          0x1068877f8 -[BFTaskCompletionSource init] + 37 (BFTaskCompletionSource.m:37)
    16 Bolts                          0x1068877a4 +[BFTaskCompletionSource taskCompletionSource] + 30 (BFTaskCompletionSource.m:30)
    17 Bolts                          0x10688a99c -[BFTask continueWithExecutor:block:cancellationToken:] + 322 (BFTask.m:322)
    18 Bolts                          0x10688a910 -[BFTask continueWithExecutor:withBlock:] + 316 (BFTask.m:316)
    19 Parse                          0x105da50d0 -[BFTask(Private) continueAsyncWithBlock:] + 33 (BFTask+Private.m:33)
    20 Parse                          0x105d42570 __28-[PFAsyncTaskQueue enqueue:]_block_invoke + 50 (PFAsyncTaskQueue.m:50)
    21 libdispatch.dylib              0x18c289298 _dispatch_call_block_and_release + 24
    22 libdispatch.dylib              0x18c28a280 _dispatch_client_callout + 16
    23 libdispatch.dylib              0x18c232fa8 _dispatch_lane_serial_drain$VARIANT$mp + 612
    24 libdispatch.dylib              0x18c233a84 _dispatch_lane_invoke$VARIANT$mp + 424
    25 libdispatch.dylib              0x18c23d518 _dispatch_workloop_worker_thread + 712
    26 libsystem_pthread.dylib        0x1d237f5a4 _pthread_wqthread + 272
    27 libsystem_pthread.dylib        0x1d2382874 start_wqthread + 8
    
    opened by jesusmateos1234 3
Releases(1.9.1)
Owner
Bolts is a collection of low-level libraries designed to make developing mobile apps easier.
null
A dead-simple abstraction over the iOS BackgroundTask API to make background tasks easy to isolate, maintain and schedule

A dead-simple abstraction over the iOS BackgroundTask API to make background tasks easy to isolate, maintain and schedule. Designed to be as lightweight and flexible as possible while tightly integrating with the system APIs. And It's built with Swift Concurrency in mind.

Sam Spencer 31 Dec 11, 2022
Collection of Cosmos series chain

protobuf & gRPC for Cosmos based chains convert .proto files for other languages(java, swift, etc) cosmos default iris native & mode akash ... How to

Cosmostation 2 Sep 13, 2022
Bolts is a collection of low-level libraries designed to make developing mobile apps easier.

Bolts in Swift Bolts is a collection of low-level libraries designed to make developing mobile apps easier. Bolts was designed by Parse and Facebook f

null 1.3k Nov 11, 2022
Blueprints is a collection of flow layouts that is meant to make your life easier when working with collection view flow layouts.

Blueprints is a collection of flow layouts that is meant to make your life easier when working with collection view flow layouts. It comes

Christoffer Winterkvist 982 Dec 7, 2022
This app is a sample app that recognizes specific voice commands such as "make it red", "make it blue", "make it green", and "make it black" and change the background color of the view in the frame.

VoiceOperationSample This app is a sample app that recognizes specific voice commands such as "make it red", "make it blue", "make it green", and "mak

Takuya Aso 3 Dec 3, 2021
SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way.

What is SugarRecord? SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way. Than

Modo 2.1k Dec 29, 2022
A mobile application project designed for everybody which provides the easiest way to make searchs for public services

A mobile application project designed for everybody which provides the easiest way to make searchs for public services

null 0 Nov 23, 2021
MbientLab 2 Feb 5, 2022
RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.

Written in Swift 5 RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with

Dalton Hinterscher 9 Aug 7, 2020
Blueprints - A framework that is meant to make your life easier when working with collection view flow layouts.

Description Blueprints is a collection of flow layouts that is meant to make your life easier when working with collection view flow layouts. It comes

Christoffer Winterkvist 982 Dec 7, 2022
A Collection of useful Swift property wrappers to make coding easier

Swift Property Wrappers A Collection of useful Swift property wrappers to make c

Gordan Glavaš 2 Jan 28, 2022
SwiftUI Backports - Introducing a collection of SwiftUI backports to make your iOS development easier

SwiftUI Backports Introducing a collection of SwiftUI backports to make your iOS development easier. Many backports support iOS 13+ but where UIKIt fe

Shaps 530 Dec 28, 2022
Gauntlet is a collection of testing utility methods that aims to make writing great tests easier.

Gauntlet What is Gauntlet? Gauntlet is a collection of testing utility methods that aims to make writing great tests easier and with more helpful and

null 11 Dec 17, 2022
Switchboard - easy and super light weight A/B testing for your mobile iPhone or android app. This mobile A/B testing framework allows you with minimal servers to run large amounts of mobile users.

Switchboard - easy A/B testing for your mobile app What it does Switchboard is a simple way to remote control your mobile application even after you'v

Keepsafe 287 Nov 19, 2022
Movies is a collection of a few UI/UX ideas that came up whilst developing an iOS app

Movies Introduction: Movies is a collection of a few UI/UX ideas that came up whilst developing an iOS app called Wattmo You'll find tableviews, detai

Kevin Mindeguia 861 Nov 19, 2022
ChainPageCollectionView A custom View with two level chained collection views and fancy transition animation

ChainPageCollectionView A custom View with two level chained collection views and fancy transition animation. Demo Requirements iOS 9.0+ Xcode 8 Insta

Yansong Li 775 Dec 7, 2022
Sinatra-like DSL for developing web apps in Swift

Swiftra Swiftra is a library that provides DSLs like Sinatra. System Requirements DEVELOPMENT-SNAPSHOT-2016-02-08-a Example See swiftra-example. impor

Shun Takebayashi 262 Jun 29, 2022
CS193p---Assignments - Assignment Solutions for Stanford CS193p - Developing Apps for iOS

Assignment Solutions for Stanford CS193p - Developing Apps for iOS Note: This is ongoing work Task done Programming Assignment 1 x Programming Assignm

null 0 Jan 12, 2022
A type-safe, high-level networking solution for Swift apps

What Type-safe network calls made easy Netswift offers an easy way to perform network calls in a structured and type-safe way. Why Networking in Swift

Dorian Grolaux 23 Apr 27, 2022
iOS tweak to display toasts for Low Power alerts and charging

Electrode iOS tweak to display toasts for Low Power alerts and charging. Localization Want to help translate Electrode to your language? Sumbit a pull

null 8 Sep 7, 2022