Code Monkey home page Code Monkey logo

bolts-objc's Introduction

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'

bolts-objc's People

Contributors

bklimt avatar bolinfest avatar chuganzy avatar clang13 avatar coeur avatar dhirenp avatar flovilmart avatar grantland avatar hramos avatar ide avatar jrg-developer avatar jvenegas avatar keith avatar lucasderraugh avatar mingflifb avatar mrplants avatar nlutsenko avatar noobs2ninjas avatar peymano avatar richardgroves avatar richardjrossiii avatar rogernolan avatar roremeol avatar ruiaaperes avatar saniul avatar toddkrabach avatar travisjeffery avatar valeriyvan avatar wiruzx avatar wzs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bolts-objc's Issues

Not compatible with UISearchDisplayController

When the returnToRefererView is shown in a setup with a UINavigationController containing a UITableViewController with a searchBar on top, there are a couple of issues:

  • the searchBar can't be scrolled in completely. It's only shown by 10 pt or so, the rest is covered by the navigation bar. It looks like the contentInsets are wrong.
  • when I actually manage to tap that small part of the searchBar to activate UISearchDisplayController, the searchBar is almost completely covered by the returnToRefererView.
  • When search mode is ended, the returnToRefererView is not shown anymore. It is displayed again when search is activated again.
  • when returnToRefererView is closed while search is active, the searchBar moves down not up and reveals an empty gap where the returnToRefererView was before.

To me it looks like it's not a good idea to mess with the navigationBar position!

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.

Cancellation trumps error in -[BFTask taskForCompletionOfAllTasks:]

I have a scenario where I have many, many network requests to make. I would like to provide a BFTask for the completion of all of them.

I want to be able to cancel them if necessary. Also, if any of them individually fail, I would like to abort the ones not yet dispatched. They are all scheduled on a BFExecutor built from an NSOperationQueue.

Each task begins with a check on a shared cancellationToken to see if they should proceed. If the request fails, it returns its error and sets the cancellationToken to true. The remaining tasks in the queue then check the cancellationToken and dutifully return -[BFTask cancelledTask] when they get their turn.

The problem is that the continuation block for -[BFTask taskForCompletionOfAllTasks:] gives cancellations precedent over errors. So, the task for the completion of all of them returns a cancelled task instead of an error. There's an important distinction there. I want to message errors in my user experience, but not cancellations.

I suspect it's intentional, but I don't know why. Is my use of the same cancellation token for canceling and errors discouraged? Or is this something that you could possibly change (to give errors precent over cancellations)?

Bracket typing autocomplete issue.

When I typing the end bracket ']' for continueWithBlock:, the start bracket '[' cannot be autocomplete with Xcode,but this works good for other case,for example:

[[NSArray array] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    <#code#>
  }];

NSError cannot be used as a boolean

Refer to fetchAsync via Readme.md when comile with Xcode 6.2 will throw

Optional type 'NSError!' cannot be used as a boolean; test for '!= nil' instead

screen shot 2015-01-01 at 11 21 07 pm

Fix with Xcode suggestion seem to work BTW

Allow setting the result of a task completion source from an existing task.

Frequently, I have to write code like this:

BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
 [.... continueWithBlock:^id(BFTask *task) {
     if (task.faulted) {
         NSError *error = task.error;
         if (error) {
             [source trySetError:error];
        } else {
             [source trySetException:task.exception];
         }
     } else if (task.cancelled) {
         [source trySetCancelled];
     } else {
         [source trySetResult:task.result];
     }

     return task;
 }];

It would be nice if there was an API similar to [source trySetTask:task] instead of having this ugly block of code.

'sharedApplication' is unavailable

I seen the compiling error: 'sharedApplication' is unavailable: not available on iOS (App Extension) in Bolts 1.2.0.

Will you fix that?

No facility for automatically generating array of results for grouped tasks

Hi,

Fantastic framework! From what I can see, there's no facility for automatically creating an array of the results from grouped tasks (using taskForCompletionOfAllTasks:). Errors and exceptions are collected, but not the results. Of course this can be done from the array of tasks manually, but I just wanted to confirm I wasn't missing something, or that (more likely) this was intentionally left unimplemented.

Thanks,

J

fetchAsync example no longer valid

In Readme.md, the following function generates the error: Cannot invoke 'fetchInBackgroundWithBlock' with an argument list of type '((PFObject!, NSError!) -> Void)'.

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
}

[BFTask waitUntilFinished] wait forever.

I write this testcase for reproduce my issue.When runs to line [BFTask waitUntilFinished], it cannot return.

- (void)testAsyncWait {
  NSLog(@"Start call");
  BFExecutor *backgroundExecutor = [BFExecutor executorWithDispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)];
  BFTask *task = [[[BFTask taskFromExecutor:backgroundExecutor withBlock:^id(BFTask *task) {
    NSLog(@"Background call");
    return nil;
  }] continueWithSuccessBlock:^id(BFTask *task) {
    NSLog(@"Background call2");
    return nil;
  }] continueWithExecutor:[BFExecutor mainThreadExecutor] withSuccessBlock:^id(BFTask *task) {
    NSLog(@"Foreground call");
    return @"Test Result";
  }];
  NSLog(@"Wait call");
  [task waitUntilFinished];
  NSLog(@"End call");
  NSLog(@"%@", task.result);
}

Errors with Bolts-Pod when using Facbook-iOS-SDK on CI Server

I installed the iOS SDK via CocoaPods. The SDK is working fine.
I then run xctool to test my App via command line and everything is working out good!
But when i try to setup a Mac CI server with xctool and when I clone my project into another dir and try to run the tests via xctool is stops with various errors:

In file included from some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:11:
In file included from some Dir//Pods/Bolts/Bolts/iOS/BFAppLink_Internal.h:11:
some Dir//Pods/Headers/Private/Bolts/BFAppLink.h:1:1: error: expected identifier or '('
../../../Bolts/Bolts/iOS/BFAppLink.h
^
In file included from some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:11:
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink_Internal.h:21:1: error: missing context for method declaration
- (BOOL)isBackToReferrer;
^
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink_Internal.h:23:1: error: expected method body
@end
^
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:23:12: error: cannot find interface declaration for 'BFAppLink'
@interface BFAppLink ()
           ^
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:23:12: error: class extension has no primary class
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:23:12: error: class extension has no primary class
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:23:12: error: class extension has no primary class
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:23:12: error: class extension has no primary class
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:38:30: error: no known class method for selector 'alloc'
    BFAppLink *link = [[self alloc] initWithIsBackToReferrer:isBackToReferrer];
                             ^~~~~
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:39:10: error: property 'sourceURL' not found on object of type 'BFAppLink *'
    link.sourceURL = sourceURL;
         ^
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:40:10: error: property 'targets' not found on object of type 'BFAppLink *'
    link.targets = [targets copy];
         ^
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:41:10: error: property 'webURL' not found on object of type 'BFAppLink *'
    link.webURL = webURL;
         ^
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:55:18: error: 'BFAppLink' cannot use 'super' because it is a root class
    if ((self = [super init])) {
                 ^
some Dir//Pods/Bolts/Bolts/iOS/BFAppLink.m:56:12: error: property 'isBackToReferrer' not found on object of type 'BFAppLink *'
      self.isBackToReferrer = isBackToReferrer;
           ^

I managed to reduce the # of errors by switching to use precompiled headers from off to on in the Pods-Bolt target but these errors above still exists. I am developing an iOS 8 only app.

Any ideas?

Add 'isFaulted' property to BFTask - keep in line with Android

Hi there, can you please add an "isFaulted" property to BFTask on iOS?
The abstraction for the SDK should be as consistent as possible between Android and iOS - this one of the Bolts framework biggest benefits is that it allows cross-platform teams to write code that is architecturally identical across both the Objective-C and Java SDKs.

The API surface should be identical for all purposes, simply using language-specific mechanisms such as blocks in Obj-C, anonymous interfaces in Java to leverage each language's best native syntax for the API.

Using NSProgress as a Cancellation Token

After reading about the interesting idea of cancellation tokens in the readme,
I thought about using NSProgress instead of implementing it on my own.
Are there any known problems or issues with this approach?

-[BFTask waitUntilFinished] does not account for spurious thread wakeup

See https://en.wikipedia.org/wiki/Spurious_wakeup for a description of "spurious wakeup". This seems to apply to NSCondition, as Apple's documentation (Threading Programming Guide) states:

Due to the subtleties involved in implementing operating systems, condition locks are permitted to return with spurious success even if they were not actually signaled by your code. To avoid problems caused by these spurious signals, you should always use a predicate in conjunction with your condition lock.

(The reference documentation for NSCondition also touches on this topic.)

I think something like this may be more correct:

- (void)waitUntilFinished {
    if ([NSThread isMainThread]) {
        [self warnOperationOnMainThread];
    }

    @synchronized (self.lock) {
        if (self.completed) {
            return;
        }
        [self.condition lock];
    }
    while (YES) {
        [self.condition wait];

        @synchronized (self.lock) {
            if (self.completed)
                break;
        }
    }

    [self.condition unlock];
}

Alternately, using dispatch_semaphore_t may provided a simpler implementation.

(Note that I have not actually observed a spurious wakeup. I don't know if this is a possibility on iOS.)

How to execute the tasks parallel in separate thread ?

I have 10 elements in an array. After checking each element in that array, I created the task. Now I want to run all the tasks parallel in different thread not in the main thread. For that I used the BFExecutor.

BFExecutor* executor = [BFExecutor executorWithBlock:^void(void(^block)()) {
                        dispatch_async(dispatch_get_global_queue(0,0), block);
                    }];
BFTask* task = [BFTask taskWithResult: nil];

[[task continueWithExecutor:executor withBlock:^id(BFTask* task) {
   return task2;
}] continueWithBlock:^id(BFTask* task) {
   # Do something with that task.result
   return nil;
}];

Is this is the correct way of using BFTask in separate thread ?
Thanks.

2 issues in iOS 9

I've installed Bolts via cocoapods in Xcode 7, now I get 2 errors when I try to build the app.

The first error is "'CFURLCreateStringByAddingPercentEscapes' is deprecated: first deprecated in iOS 9.0 - Use [NSString stringByAddingPercentEncodingWithAllowedCharacters:] instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent (since each URL component or subcomponent has different rules for what characters are valid)."

And the second error is "'sendAsynchronousRequest:queue:completionHandler:' is deprecated: first deprecated in iOS 9.0 - Use [NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h"

The errors are in two different Files, the first is in BFAppLinkNavigation.m, the second in BFWebViewAppLinkResolver.m.

Here is the repro: https://github.com/telip007/testPods

BFAppLinkNavigation methods with custom URL scheme not working

I am using this code to navigate to another native app with the registered URL scheme 'receiver'

NSURL *url = [NSURL URLWithString:@"receiver://"];
[[BFAppLinkNavigation resolveAppLinkInBackground:url] continueWithSuccessBlock:^id(BFTask *task) {
    BFAppLink *link = task.result;
    NSLog(@"resolved link %@", link);
    BFAppLinkNavigation *navigation = [BFAppLinkNavigation navigationWithAppLink:link
                                                                          extras:@{ @"access_token": @"t0kEn" }
                                                                     appLinkData:@{ @"referer_app_link": @{
                                                                                   @"url": @"playlist://",
                                                                                   @"app_name": @"Playlist" }}];
    NSError *error = nil;
    [navigation navigate:&error];
    return nil;
}];

I can make this work with an http:// url like http://spotify.com, but I can't get it to work with a custom URL scheme like receiver://

However, I can open the receiver app using standard method openURL:@"receiver://"
But it's necessary for me to use the BFAppLinkNavigation methods because I need to send the appropriate metadata to let the receiver app generate the proper back button back to the first app. Any ideas why this isn't working?

Small hitTargets of BFAppLinkRefererView...

I am having a hard time hitting the link and especially the close button in the BFAppLinkRefererView. Looks like the hit area of both elements is very small; closeButton is 12x12 I believe and label has a height a bit bigger.

image copy

cheers,
H

Attempt to read non existent folder

[!] Error installing Bolts
[!] Attempt to read non existent folder /Volumes/ABC/Projects/TestApp/Pods/Bolts.

I encounter this error when pod install

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.

BFTask cancellation

I looked through issues and documentation and I have no idea how to cancel network task that I do not need anymore.

Task Cancellation

I was wondering, what is the intended approach to task cancellation?

BFTask only exposes methods for task consumption and continuation, while the cancellation is implemented through BFTaskCompletionSource (which actually just calls internal cancellation methods of BFTask).
There is no way to cancel a BFTask without having a reference to its completion source.

Does this mean that a task producer should keep a reference to all its long-running cancellable task completion sources and expose an interface for identifying and cancelling a specific task?

How to re-throw exceptions?

We are trying to re-throw exceptions as follows. But this way is not only ugly but also takes care of only the last continueWithBlock:.

What would you guys suggest on this?

[[task continueWithBlock:^id(BFTask *task) {
        //throws an exception
        return nil;
    }] continueWithBlock:^id(BFTask *task) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (task.exception) {
                @throw task.exception;
            }
        });
        return nil;
    }];

Prevent duplicate tasks

I have an operation executed when user taps a button, I want to be able to prevent duplicate tasks from being fired.

Completed set before the task has actually completed

[BFTask waitUntilFinished] shows this quite clearly. Fire off a few tasks, then call waitUntilFinished. The call to wait... will continue (and the task will be set to completed) before the execution block has been called.

This is due to [self runContinuations] always being called after completed/cancelled etc. being set. As a result.

I'm wondering if this is due to thread contention if the was called on the same thread as the executor block, but at the moment the behaviour seems incorrect and results in a lot of additional checks to make sure the task has actually finished.

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.

BFURL crash on initialization when target_url is null

when target_url is null in the url, a crash occurs in the method initWithURL:forOpenInboundURL:sourceApplication:forRenderBackToReferrerBar:when control reaches this line:

 _targetURL = target ? [NSURL URLWithString:target] : url;

target is of class NSNull

Provide a result for taskForCompletionOfAllTasks

I want to do multiple asynchronous tasks in parallel, each of which returns a value, and then when all have completed, get an array of those results.

As of now, taskForCompletionOfAllTasks just returns a result of nil.

For asynch parse operations when testing - waitUntilFinished hangs indefinitely

When you use "waitUntilFinished" at the end of a task, the parse completion block for a somethingInBackground is never called. It works with a dispatch semaphore.

The reason why may be that parse block callbacks always occur on the main thread.

Example:

-(BFTask *)createCorporation {
    NSLog(@"Creating corporation.");
    BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];

    PFObject *corporation = [PFObject objectWithClassName:@"AcctCorporation"];
    [corporation setObject:[_locationInfo objectForKey:@"locationName"] forKey:@"name"];
    [corporation setObject:[_locationInfo objectForKey:@"postalCode"] forKey:@"postal"];
    [corporation setObject:[_locationInfo objectForKey:@"country"] forKey:@"country"];
    [corporation setObject:[_locationInfo objectForKey:@"state"] forKey:@"state"];
    [corporation setObject:[_locationInfo objectForKey:@"address"] forKey:@"address"];

    [corporation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        NSLog(@"HERE!");  // Never gets called
        if(!error) {
            [task setResult: corporation];
        } else {
            [task setError: error];
        }
    }];
    return task.task;
}

Test:

- (void) testCreateCorporation {
    NSDictionary *userProfile = [NSDictionary dictionaryWithObjectsAndKeys:@"[email protected]", @"email", @"jimbo2s223s4", @"username", @"Jim", @"firstName", @"O'Brien", @"lastName", @"1234", @"pin", @"888xxxx8", @"password", @"619-xxxxxxx", @"cell", nil];
    NSDictionary *locationInfo = @{
                                   @"locationName": @"Joe's Garage",
                                   @"address": @"111 Frank Zappa Street",
                                   @"mainPhone": @"619-xxx-xxxx",
                                   @"postalCode": @"91910",
                                   @"city": @"Palmdale",
                                   @"state": @"California",
                                   @"country": @"USA",
                                   @"position": @"Head Chief and Cook"
                                   };

    __block PFObject *corporation = nil;

    TAPParseSignup *signup = [[TAPParseSignup alloc] initWithLocationInfo:locationInfo andUserProfile:userProfile];
    [[[signup createCorporation] continueWithBlock:^id(BFTask *task) {
        STAssertNil(task.error, @"Error should be nil");
        corporation = [task result];
        return nil;
    }] waitUntilFinished];
    STAssertNotNil(corporation, @"Corp should not be nil.");
    STAssertNotNil([corporation objectId], @"Object id should not be nil");
}

With semaphore working:

- (void) testCreateCorporation {
    NSDictionary *userProfile = [NSDictionary dictionaryWithObjectsAndKeys:@"[email protected]", @"email", @"jimbo2s223s4", @"username", @"Jim", @"firstName", @"O'Brien", @"lastName", @"1234", @"pin", @"8888", @"password", @"619-xxxxxxxx", @"cell", nil];
    NSDictionary *locationInfo = @{
                                   @"locationName": @"Joe's Garage",
                                   @"address": @"111 Frank Zappa Street",
                                   @"mainPhone": @"619-xxx-xxxx",
                                   @"postalCode": @"91910",
                                   @"city": @"Palmdale",
                                   @"state": @"California",
                                   @"country": @"USA",
                                   @"position": @"Head Chief and Cook"
                                   };

    __block PFObject *corporation = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    TAPParseSignup *signup = [[TAPParseSignup alloc] initWithLocationInfo:locationInfo andUserProfile:userProfile];
    [[signup createCorporation] continueWithBlock:^id(BFTask *task) {
        STAssertNil(task.error, @"Error should be nil");
        dispatch_semaphore_signal(semaphore);
        corporation = [task result];
        return nil;
    }];
    while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:20]];
    }
    STAssertNotNil(corporation, @"Corp should not be nil.");
    STAssertNotNil([corporation objectId], @"Object id should not be nil");
}

linker command failed with exit code 1 when bitcode enabled

using the latest Bolts framework...

Frameworks/Bolts.framework/Bolts(BFExecutor.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Codesign OSX10.9.5

Hello,
I am deep in trying to properly codesign Bolts for use with the Parse framework and it shows as "..not a valid format..."

I see here that this branch is newer than what Parse is using. I did a build from the master using your scripts and dropped the OSX framework into my project, added copy files phase and checked to code sign like I do for other frameworks. I am getting:

"...bundle format is ambiguous (could be app or framework)"

This is odd as Parse and Sparkle code sign perfectly. Any ideas? I tried re-working the plist but was not successful. I also noticed that your release build script place the OSX Bolts.frameworks inside itself.

-matt

Custom URL Schemes not supported in [BFAppLinkNavigation navigateToURLInBackground:url]

Similar issue: #38

The method [BFAppLinkNavigation navigateToURLInBackground:url] does not support custom URL Schemes. I personally feel like it should! It supports almost all other forms of opening URL's. The only way to actually navigate to links with custom URL schemes is to implement continueWithBlock:^id(BFTask *task). Is there a specific reason that custom URL schemes are not supported?

Bolts framework not parsing parameters that contain a non-breaking space.

When using:
BFURL *parsedUrl = [BFURL URLWithInboundURL:url sourceApplication:sourceApplication];
on url that contain non-breaking space - '%A0' the BFURL doesn't parse all the parameters correctly. So I have to manually change them like so:
NSString *newUrlString = [urlString stringByReplacingOccurrencesOfString:@"%A0" withString:@"%20"];

Blank view for BFAppLinkReturnToRefererView

According to the guide, you can either setup the view explicitly:

self.returnToRefererView = [[BFAppLinkReturnToRefererView alloc] initWithFrame:CGRectZero];
self.returnToRefererController = [[BFAppLinkReturnToRefererController alloc] init];
self.returnToRefererController.view = self.returnToRefererView;

Or implicitly, by not creating and assigning a BFAppLinkReturnToReferrerView — which will automatically be created when calling -showViewForRefererURL: on the controller.

Looking at the code I see that when this view is automatically created and _attachedToNavController is not nil, it is added to the view hierarchy. OTOH, when manually setting this view, it is not attached to the nav controller. I feel like this violates the Principle of Least Astonishment — ideally the whole return to referrer view would work like a black box that magically works with the least external setup possible.

Quick & dirty fix would be adding a [_attachedToNavController.view addSubview:_view]; on -setView:

Now, on to the real problem, initializing the view with CGRectZero always results in a blank view. Doesn't matter if it's explicitly created and provided to the returnToRefererController or internally created. I've set a few breakpoints throughout the code and the size always ends up being (0, 0, 0, 52) instead of (0, 0, 320, 52).

The only way I can get the view to behave properly (i.e. render as it should, the label and the X button with a blue background) is if I initialize it manually with the target width:

// Using a magic value for the sake of demo
self.returnToRefererView = [[BFAppLinkReturnToRefererView alloc] initWithFrame:CGRectMake(0, 0, 320, 0)];

This is contradictory to the instructions on the README which state that the view should be initialized with CGRectZero.

Couple suggestions

  • Make all control of view attachment and resizing internal to BFAppLinkReturnToRefererController — I like the ability of being able to pass my own implementation of BFAppLinkReturnToRefererView (should I ever need it) but having to write more code (that should be internal to the controller) when I do is cumbersome;
  • Drop both the -init and -initForDisplayAboveNavController: constructors on BFAppLinkReturnToRefererController and add a new one, `-initWithViewController: — I feel like it should make no difference whether it's a navigation controller or a regular view controller, this thing should be a black box

I'd be more than happy to discuss this further and/or advance a solution if you feel like any of this makes sense.

back view is missing

Hi,

I have a first app which performs a navigation to the second app:

- (IBAction)open:(id)sender
{
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"html"];

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

        BFAppLinkNavigation *navigation = [BFAppLinkNavigation navigationWithAppLink:link
                                                                              extras:@{ @"_test_": @"SURPRISE mother fucker!" }
                                                                         appLinkData:@{ @"referer_app_link": @{
                                                                                                @"url": @"applinks://",
                                                                                                @"app_name": @"AppLinks"
                                                                                                }}];
        NSError *error = nil;

        [navigation navigate:&error];

        return task;
    }];
}

The second app shows up but the back view is missing and the app crashes without any output.. did I miss something ?

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    if (self.appLinkReturnToRefererView) {
        self.appLinkReturnToRefererView.hidden = YES;
    }
    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    if (delegate.parsedUrl) {

        self.appLink = [delegate.parsedUrl appLinkReferer];

        [self _showRefererBackView];
    }

    delegate.parsedUrl = nil;

}

- (void) _showRefererBackView {
    if (nil == self.appLinkReturnToRefererView) {
        // Set up the back link navigation view
        BFAppLinkReturnToRefererView *backLinkView  = [[BFAppLinkReturnToRefererView alloc] initWithFrame:CGRectMake(0, 30, 320, 40)];
        self.appLinkReturnToRefererView = backLinkView;
    }
    self.appLinkReturnToRefererView.hidden = NO;
    // Initialize the back link view controller
    BFAppLinkReturnToRefererController *alc =[[BFAppLinkReturnToRefererController alloc] init];
    alc.view = self.appLinkReturnToRefererView;
    // Display the back link view

    [alc showViewForRefererAppLink:self.appLink];
}

EDIT:

If I use the solution without using Bolts, everything is fine. I followed this tutorial:

https://developers.facebook.com/docs/ios/share/#linking

It there an issue in the BFAppLinkReturnToRefererView class ?

How to stop BFTask Chain ?

Hi Guys

I have a question "how to stop the BFTask chain, break from the chain execution"

See Commnets

 BFTask *task = [_model getX1Task];

    [[task continueWithBlock:^id(BFTask *task) {
        if (task.error || task.exception || task.isCancelled) {
            
            return nil;
        } else {
            NSDictionary *dic = (NSDictionary *)task.result;
            if ([dic isKindOfClass:[NSDictionary class]])
            {
                if ([self checkX1OK])
                {
                
                    return [_model getX2Task];
                    
                }
                else
                {
                    //here I want break 
                    //so i return nil, but I found the continueWithBlock will still return a new task have a nil result, so the chain automaticall go to below 
                    return nil
                }
            }
        }
    }] continueWithBlock:^id(BFTask *task) {
    //but the code come here, even the before task is return nil 
        if (task.error || task.exception || task.isCancelled) {
            
            return nil;
        }
        else {
            if ([self checkX2OK])
            {
               
                return [_model getX3Task];
            }
            else
            {
                //here want break 
                //so i return nil
                return nil
            }
        }
    }] ;

I found why the nil case return default task in BFTask's method
continueWithExecutor:withBlock:

- (BFTask *)continueWithExecutor:(BFExecutor *)executor
                       withBlock:(BFContinuationBlock)block {
    BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
    ............................
    //because the result is nil, so it's not a BFTask class
            if ([result isKindOfClass:[BFTask class]]) {
                [(BFTask *)result continueWithBlock:^id(BFTask *task) {
                    ...........
                                        return nil;
                }];
            } else {
                tcs.result = result; //so nil result will go here
            }
        }];
    };
    
    ......
    
    return tcs.task; 
    //so even a nil result will return a default task which is create by BFTaskCompletionSource
}

I have thought a way like this to add one stop:BOOL in the block

typedef id(^BFContinuationWithStopBlock)(BFTask *task, BOOL *stop);
so I can mark &stop = YES, when I want to stop the execution, what are your opinions?

Tasks not chaining properly

I have a BFTask that is returning false to [ob isKindOfClass:[BFTask class]] and yet returns @"BFTask" for NSStringFromClass([obj class]).

Here is a sample project: [email protected]:krusek/Bolts-error.git

navigateToURLInBackground returns NSURLErrorDomain - code 1002

I am using the following code to open the Facebook app:

...
NSString *testUrl = @"fb://";
NSURL *url = [[NSURL alloc] initWithString:testUrl];
[[BFAppLinkNavigation navigateToURLInBackground:url] continueWithBlock:^id(BFTask *task) {
        NSLog(@"yay!");
         return 0;
}];
...

and what I get is an error (generated in BFWebViewAppLinkResolver.m) - NSURLErrorDomain - code 1002.

When I use [[UIApplication sharedApplication] openURL:url] on the same URL it works just fine..

Please help me guys :)
I want to use Bolts to interact with Facebook app.

Avoid using unavailable APIs when linking against app extension targets.

When using Bolts in an app extension development. Some Blots classes have references to [UIApplication sharedApplication] which is not available in an app extension target.

Need a way to avoid the unavailable API usage when linking against app extension targets. Maybe a macro for conditional compile?

forCompletionOfAllTasksWithResults: exception 'Cannot set the result on a completed task.'

Hello,
I am trying to implement the following logic:

  1. Get user`s posts from Parse
  2. For each post, get a URL of the movie poster from iTunes (func getMovieInfoByITunesID)
  3. After URLs for all posts are received, report completion.

However I get the exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot set the result on a completed task.'

The exception rises in this line: tempPost.timeSincePosted = self.getTimeSincePostedfromDate(post.createdAt!)

What does the setResult in the (self.getTimeSincePostedfromDate(post.createdAt!))
has to do with the main task?

func loadFeedPosts() -> BFTask {
    let mainTask = BFTaskCompletionSource()

    let user = PFUser.currentUser()!
    let relation = user.relationForKey("feed")
    let query = relation.query()

    query?.addDescendingOrder("createdAt")

    query?.findObjectsInBackground().continueWithBlock({ (task: BFTask!) -> AnyObject! in
      var tasks = [BFTask]()
      if task.error == nil, let result = task.result {
        let posts = result as! [PFObject]

        for post in posts {
          var tempPost = Post()
          tempPost.userName = (post["createdBy"] as! PFUser).username
          tempPost.timeSincePosted = self.getTimeSincePostedfromDate(post.createdAt!)
          tempPost.profileImageURL = (post["createdBy"] as! PFUser)["smallProfileImage"] as? String

          tasks.append(self.getMovieInfoByITunesID(post["trackID"] as! Int))
         }

      }

      return BFTask(forCompletionOfAllTasksWithResults: tasks)
    }).continueWithBlock({ (task: BFTask!) -> AnyObject! in

      for var i = 0; i < Post.sharedInstance.feedPosts.count; ++i {
         Post.sharedInstance.feedPosts[i].bigPosterImageURL = self.getBigPosterImageURL((task.result as! JSON)[i]["artworkUrl100"].stringValue)
      }

      mainTask.setResult(nil)
      return nil
    })

    return mainTask.task

    }

func getMovieInfoByITunesID(iTunesID: Int) -> BFTask {
      let task = BFTaskCompletionSource()
      ITunesApi.lookup(iTunesID).request({ (responseString: String?, error: NSError?) -> Void in
        if
          //        error == nil,
          let responseString = responseString, let dataFromString = responseString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
            let json = JSON(data: dataFromString)
            task.setResult(json["results"].arrayObject!)
        } else {
          // process error
        }
      })
      return task.task
    }

[Crash] on iPhone 4 iOS 7.0.4 FYI

DEVICE: iPhone 4
IOS VERSION: 7.0.4

Thread : Crashed: com.apple.main-thread
0 ??? 0x2beeb0c4
1 ??? 0x2beebf07
2 ??? 0x2beed69f
3 libdyld.dylib 0x38ba80d0 dyld_stub_binder + 20
4 APP 0x000f3281 38+[BFTask taskForCompletionOfAllTasks:]_block_invoke (BFTask.m:102)
5 APP 0x000f4521 __41-[BFTask continueWithExecutor:withBlock:]_block_invoke_2 (BFTask.m:287)
6 APP 0x000f235d __29+[BFExecutor defaultExecutor]_block_invoke_2 (BFExecutor.m:43)
7 APP 0x000f2795 -BFExecutor execute:
8 APP 0x000f44af __41-[BFTask continueWithExecutor:withBlock:]_block_invoke (BFTask.m:284)
9 APP 0x000f4331 -BFTask continueWithExecutor:withBlock:
10 APP 0x000f4869 -BFTask continueWithBlock:
11 APP 0x000f2f87 +BFTask taskForCompletionOfAllTasks:
12 APP 0x0022544b -PFTaskQueue enqueue:
13 APP 0x001e76a7 -PFObject saveInBackground
14 APP 0x0022303d -PFInstallation saveInBackground
15 APP 0x0004c155 -AppDelegate application:didRegisterForRemoteNotificationsWithDeviceToken:
16 UIKit 0x30d9a473 _UIXXRemoteNotificationRegistrationSucceeded + 150
17 UIKit 0x30d9afe1 _XRemoteNotificationRegistrationSucceeded + 92
18 AppSupport 0x31ae6a97 migHelperRecievePortCallout + 190
19 CoreFoundation 0x2e2db9df __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
+ 34
20 CoreFoundation 0x2e2db97b __CFRunLoopDoSource1 + 346
21 CoreFoundation 0x2e2da14f __CFRunLoopRun + 1398
22 CoreFoundation 0x2e244c27 CFRunLoopRunSpecific + 522
23 CoreFoundation 0x2e244a0b CFRunLoopRunInMode + 106
24 GraphicsServices 0x32f23283 GSEventRunModal + 138
25 UIKit 0x30ae8049 UIApplicationMain + 1136
26 APP 0x000510fb main (main.m:16)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.