iosswiftbolts-framework

How to better chain tasks using BoltsSwift?


First time using Bolts and I can almost understand how they work, however I'm sure there has to be a cleaner way to do what I'm doing.

In my example, I want to download a JSON with categories, import them to Core Data, then download a JSON with ingredients and import them as well. Things need to happen in this order since my parser requires the Category objects to exist already before parsing the Ingredient objects.

I am using BoltsSwift in order to avoid the nested blocks so I made my "downloader" and "importer" return a Task.

The network call (using Moya):

static func downloadJSON(target: MoyaAPI) -> BoltsSwift.Task<JSONArray> {
    let taskCompletionSource = TaskCompletionSource<JSONArray>()
    requestJSONArray(target, success: { (jsonArray) in
        taskCompletionSource.set(result: jsonArray)
    }, error: { (error) in
        taskCompletionSource.set(error: error)
    }, failure: { (moyaError) in
        taskCompletionSource.set(error: moyaError)
    })
    return taskCompletionSource.task
}

The parsing part (using Groot):

func importCategories(from json: JSONArray) -> BoltsSwift.Task<[Category]> {
    let taskCompletionSource = TaskCompletionSource<[Category]>()
    do {
        let categories: [Category] = try objects(fromJSONArray: json, inContext: managedObjectContext)
        taskCompletionSource.set(result: categories)
    } catch {
        taskCompletionSource.set(error: error)
    }
    return taskCompletionSource.task
}

So later when I try to actually chain these 4 tasks together, I came up with this:

    NetworkAdapter.downloadJSON(target: .categories).continueOnSuccessWithTask { (json) -> Task<[Category]> in
        return importer.importCategories(from: json)
    }.continueOnSuccessWith { (categories) in
        return NetworkAdapter.downloadJSON(target: .ingredients).continueOnSuccessWithTask(continuation: { (json) -> Task<[Ingredient]> in
            return importer.importIngredients(from: json)
        })
    }

This works but I have a feeling the syntax can be more readable. Can someone explain what is the proper way to chain these 4 tasks nicely?


Solution

  • You should be able to pull out the nested task following your second JSON download to the top level, making the code much cleaner:

    NetworkAdapter.downloadJSON(target: .categories)
      .continueOnSuccessWithTask { (json) in
        return importer.importCategories(from: json)
      }
      .continueOnSuccessWithTask { (_) in
        return NetworkAdapter.downloadJSON(target: .ingredients)
      }
      .continueOnSuccessWithTask { (json) in
        return importer.importIngredients(from: json)
      }
    

    (If the compiler gets confused, you might have to re-add the return type declarations inside the closures.)

    If the above works for you, you can further slim down your code using inline closures:

    NetworkAdapter.downloadJSON(target: .categories)
      .continueOnSuccessWithTask { importer.importCategories(from: $0) }
      .continueOnSuccessWith { NetworkAdapter.downloadJSON(target: .ingredients) }
      .continueOnSuccessWithTask { importer.importIngredients(from: $0) }