Limiting the number of fetch requests in URLSession with SwiftUI? - ios

I'm using an async image loader to fetch images from a URLRequest, and I'm trying to wrap my code inside of an Operation so I can use .maxConcurrentOperationCount for an OperationQueue, because I'm supposed to limit the number of downloads to 3 at a time.
I've overriden the Operation class to try and support async downloads, however, I'm not able to achieve this, and I think it's because my downloading function is inside of a Task group.
The error i get is as follows:
Invalid conversion from 'async' function of type '(URL?, URLResponse?,
(any Error)?) async throws -> Void' to synchronous function type
'(URL?, URLResponse?, (any Error)?) -> Void'
Here are the code snippets:
for the overriden Operation class:
class DownloadOperation: Operation {
private var task: URLSessionDataTask!
init(session: URLSession, downloadTaskURL: URLRequest, completionHandler: ((URL?, URLResponse?, Error?) -> Void)?) {
super.init()
// use weak self to prevent retain cycle
task = session.dataTask(with: downloadTaskURL, completionHandler: { [weak self] (URLRequest, response, error) in
/*
set the operation state to finished once
the download task is completed or have error
*/
self?.state = .finished
})
}
enum OperationState : Int {
case ready
case executing
case finished
}
private var state : OperationState = .ready {
willSet {
self.willChangeValue(forKey: "isExecuting")
self.willChangeValue(forKey: "isFinished")
}
didSet {
self.didChangeValue(forKey: "isExecuting")
self.didChangeValue(forKey: "isFinished")
}
}
override var isReady: Bool { return state == .ready }
override var isExecuting: Bool { return state == .executing }
override var isFinished: Bool { return state == .finished }
override func start() {
/*
if the operation or queue got cancelled even
before the operation has started, set the
operation state to finished and return
*/
if(self.isCancelled) {
state = .finished
return
}
// set the state to executing
state = .executing
print("downloading")
// start the downloading
self.task.resume()
}
override func cancel() {
super.cancel()
// cancel the downloading
self.task.cancel()
}
}
and here is me trying to use it inside of a task in the loader function:
public func loadImage(_ urlRequest: URLRequest) async throws -> UIImage {
if let status = images[urlRequest]{
switch status{
case .fetched(let image):
return image
case .inProgress(let task):
return try await task.value
case .failure(let error):
self.hasError = true
self.error = error as? InternetError
}
}
let task: Task<UIImage, Error> = Task {
do {
let imageQueue = OperationQueue()
imageQueue.maxConcurrentOperationCount = 3
let operation = DownloadOperation(session: URLSession.shared, downloadTaskURL: urlRequest, completionHandler: {_, response ,_ in
let (imageData, response) = try await URLSession.shared.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw InternetError.invalidServerResponse
}
guard let image = UIImage(data: imageData) else {
throw InternetError.noInternet
}
})
imageQueue.addOperation(operation)
// return image
}
catch {
self.hasError = true
images[urlRequest] = .failure(error)
print("error caught in Loader")
let image = UIImage(systemName: "wifi.exclamationmark")!
return image
}
}
do{
images[urlRequest] = .inProgress(task)
var image = try await task.value
if let imageFromCache = imageCache.object(forKey: urlRequest as AnyObject) as? UIImage {
image = imageFromCache
return image
}
images[urlRequest] = .fetched(image)
//storing image in cache
imageCache.setObject(image, forKey: urlRequest as AnyObject)
return image
}
}
}
I would appreciate any help about this! Thank you!!

There are several issues:
You are creating a new operation queue every time you call loadImage, rendering the maxConcurrentOperationCount moot. E.g., if you quickly request five images, you will end up with five operation queues, each with one operation on them, and they will run concurrently, with none of the five queues exceeding their respective maxConcurrentOperationCount.
You must remove the local variable declaration of the operation queue from the function, and make it a property.
DownloadOperation is starting a dataTask but not calling the completion handler. Also, when you create the DownloadOperation you are suppling a completion handler in which you are starting yet another download operation. If you are going to use an Operation to encapsulate the download, you should not have any URLSession code in the completion handler. Use the parameters returned.
The asynchronous operation is not thread-safe. One must synchronize the access to this shared state variable.
Thus, perhaps:
var images: [URLRequest: ImageRequest] = [:]
let queue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
return queue
}()
let session: URLSession = .shared
public func loadImage(_ request: URLRequest) async throws -> UIImage {
switch images[request] {
case .fetched(let image):
return image
case .inProgress(let task):
return try await task.value
case .failure(let error):
throw error
case nil:
let task: Task<UIImage, Error> = Task {
try await withCheckedThrowingContinuation { continuation in
let operation = ImageRequestOperation(session: session, request: request) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .failure(let error):
self?.images[request] = .failure(error)
continuation.resume(throwing: error)
case .success(let image):
self?.images[request] = .fetched(image)
continuation.resume(returning: image)
}
}
}
queue.addOperation(operation)
}
}
images[request] = .inProgress(task)
return try await task.value
}
}
Where the above, the async-await code, uses the following operation:
class ImageRequestOperation: DataRequestOperation {
init(session: URLSession, request: URLRequest, completionHandler: #escaping (Result<UIImage, Error>) -> Void) {
super.init(session: session, request: request) { result in
switch result {
case .failure(let error):
DispatchQueue.main.async { completionHandler(.failure(error)) }
case .success(let data):
guard let image = UIImage(data: data) else {
DispatchQueue.main.async { completionHandler(.failure(URLError(.badServerResponse))) }
return
}
DispatchQueue.main.async { completionHandler(.success(image)) }
}
}
}
}
The the above abstracts the image-related part, above, from the network-related stuff, below. Thus:
class DataRequestOperation: AsynchronousOperation {
private var task: URLSessionDataTask!
init(session: URLSession, request: URLRequest, completionHandler: #escaping (Result<Data, Error>) -> Void) {
super.init()
task = session.dataTask(with: request) { data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
200 ..< 300 ~= response.statusCode
else {
completionHandler(.failure(error ?? URLError(.badServerResponse)))
return
}
completionHandler(.success(data))
self.finish()
}
}
override func main() {
task.resume()
}
override func cancel() {
super.cancel()
task.cancel()
}
}
And the above inherits from an AsynchronousOperation that abstracts all of your asynchronous operation stuff, below, from the substance of what the operation does, above. Thus:
/// AsynchronousOperation
///
/// Encapsulate the basic asynchronous operation logic in its own class, to avoid cluttering
/// your concrete implementations with a ton of boilerplate code.
class AsynchronousOperation: Operation {
enum OperationState: Int {
case ready
case executing
case finished
}
#Atomic var state: OperationState = .ready {
willSet {
willChangeValue(forKey: #keyPath(isExecuting))
willChangeValue(forKey: #keyPath(isFinished))
}
didSet {
didChangeValue(forKey: #keyPath(isFinished))
didChangeValue(forKey: #keyPath(isExecuting))
}
}
override var isReady: Bool { state == .ready && super.isReady }
override var isExecuting: Bool { state == .executing }
override var isFinished: Bool { state == .finished }
override var isAsynchronous: Bool { true }
override func start() {
if isCancelled {
state = .finished
return
}
state = .executing
main()
}
/// Subclasses should override this method, but *not* call this `super` rendition.
override func main() {
assertionFailure("The `main` method should be overridden in concrete subclasses of this abstract class.")
}
func finish() {
state = .finished
}
}
And, note, that I addressed the lack of thread-safe access to the state using this property wrapper:
/// Atomic
///
/// Property wrapper providing atomic interface.
///
/// - Note: It is advised to use this with value types only. If you use reference types, the object could theoretically be mutated beyone the knowledge of this property wrapper, losing atomic behavior.
#propertyWrapper
struct Atomic<T> {
var _wrappedValue: T
let lock = NSLock()
var wrappedValue: T {
get { synchronized { _wrappedValue } }
set { synchronized { _wrappedValue = newValue } }
}
init(wrappedValue: T) {
_wrappedValue = wrappedValue
}
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock.lock()
defer { lock.unlock() }
return try block()
}
}
This yields asynchronous behaviors with max concurrency count of 3. E.g., here I download 10 images, then another 10, and then another 20:

Related

XCTest failing even after fulfilling expectation

I have a task aggregator which aggregates and executes tasks in a synchronized manner. It's thread safe and all that. Here's what it looks like
class DaddysMagicalTaskAggregator {
private let tasks: ThreadSafeValueContainer<[KewlTask]>
private let cancelled: ThreadSafeValueContainer<Bool>
private var completion: ((Result<Bool, Error>) -> Void)?
private var alreadyCompleted: Result<Bool, Error>?
private var getTasks: [KewlTask] {
return tasks.value ?? []
}
private var isCancelled: Bool {
return cancelled.value ?? true
}
init(queue: DispatchQueue = DispatchQueue(label: "DaddysMagicalTaskAggregator")) {
self.tasks = ThreadSafeValueContainer(value: [], queue: queue)
self.cancelled = ThreadSafeValueContainer(value: false, queue: queue)
}
/// Add a task to the list of tasks
func addTask(_ task: KewlTask) -> DaddysMagicalTaskAggregator {
self.tasks.value = getTasks + [task]
return self
}
/// Add tasks to the list of tasks
func addTasks(_ tasks: [KewlTask]) -> DaddysMagicalTaskAggregator {
self.tasks.value = getTasks + tasks
return self
}
/// Start executing tasks
#discardableResult
func run() -> DaddysMagicalTaskAggregator {
guard !isCancelled else {
return self
}
guard !getTasks.isEmpty else {
alreadyCompleted = .success(true)
completion?(.success(true))
return self
}
var currentTasks = getTasks
let taskToExecute = currentTasks.removeFirst()
self.tasks.value = currentTasks
taskToExecute.execute { (result) in
switch result {
case .success:
self.run()
case.failure(let error):
self.taskFailed(with: error)
}
}
return self
}
private func taskFailed(with error: Error) {
tasks.value = []
alreadyCompleted = .failure(error)
completion?(.failure(error))
completion = nil
}
/// Add a completion block which executes after all the tasks have executed or upon failing a task.
func onCompletion(_ completion: #escaping (Result<Bool, Error>) -> Void) {
if let result = alreadyCompleted {
completion(result)
} else {
self.completion = completion
}
}
/// Cancel all tasks
func cancelAllTasks(with error: Error) {
cancelled.value = true
taskFailed(with: error)
}
}
public class KewlTask {
private let closure: ((KewlTask) -> Void)
private var completion: ((Result<Bool, Error>) -> Void)?
public init(_ closure: #escaping (KewlTask) -> Void) {
self.closure = closure
}
public func execute(_ completion: #escaping (Result<Bool, Error>) -> Void) {
self.completion = completion
closure(self)
}
/// Task succeeded
func succeeded() {
completion?(.success(true))
}
/// Task failed with given error
func failed(with error: Error) {
completion?(.failure(error))
}
/// Take action based on the result received
func processResult(_ result: Result<Bool, Error>) {
switch result {
case .success:
succeeded()
case .failure(let error):
failed(with: error)
}
}
}
public class ThreadSafeContainer {
fileprivate let queue: DispatchQueue
public init(queue: DispatchQueue) {
self.queue = queue
}
}
public class ThreadSafeValueContainer<T>: ThreadSafeContainer {
private var _value: T
public init(value: T, queue: DispatchQueue) {
self._value = value
super.init(queue: queue)
}
public var value: T? {
get {
return queue.sync { [weak self] in
self?._value
}
}
set(newValue) {
queue.sync { [weak self] in
guard let newValue = newValue else { return }
self?._value = newValue
}
}
}
}
It works as expected. However, when I write a test to make sure it deallocates, the test keeps failing even after the expectation is fulfilled.
Please look at the test code below
import XCTest
class AggregatorTests: XCTestCase {
func testTaskAggregatorShouldDeallocatateUponSuccess() {
class TaskContainer {
let aggregator: CustomKewlAggregator
init(aggregator: CustomKewlAggregator = CustomKewlAggregator()) {
self.aggregator = aggregator
}
}
class CustomKewlAggregator: DaddysMagicalTaskAggregator {
var willDeinit: (() -> Void)?
deinit {
willDeinit?()
}
}
let myExpecation = self.expectation(description: "Task Aggregator should deallocate")
var container: TaskContainer? = TaskContainer()
let t1 = KewlTask { t in
t.succeeded()
}
let t2 = KewlTask {
$0.succeeded()
}
container?.aggregator.willDeinit = {
myExpecation.fulfill()
}
container?.aggregator
.addTasks([t1, t2])
.run()
.onCompletion { (result) in
container = nil
}
waitForExpectations(timeout: 4, handler: nil)
}
}
I've added breakpoints and everything to ensure the expectation fulfillment code does execute.
It doesn't seem to be an xCode issue since I've tested it on XCode 11.7, 12.1, 12.2, and 12.4.
Any idea what's going on here? To me, it looks like a bug in XCTests.

How to get an array from URLSession

Trying to make a program for a news site. I take information from the site through the api, everything works fine.
The only question is, how do I get this array out of the loop?
Here is my code:
import UIKit
class ViewController: UIViewController {
var news:[News] = []
override func viewDidLoad() {
super.viewDidLoad()
getUsers()
print(news)
}
func getUsers() {
guard let url = URL(string: "http://prostir.news/swift/api2.php") else {return}
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
news = try JSONDecoder().decode([News].self, from: data)
// print(self.news)
} catch let error {
print(error)
}
}
}.resume()
}
}
struct News:Codable, CustomStringConvertible{
let href:String?
let site:String?
let title:String?
let time:String?
var description: String {
return "(href:- \(href), site:- \(site), title:- \(title), time:- \(time))"
}
}
Declare news array in your class and assign the response to this array in getUsers method
var news:[News] = []
func getUsers(){
guard let url = URL(string: "https") else {return}
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
self.news = try JSONDecoder().decode([News].self, from: data)
print(self.news)
} catch let error {
print(error)
}
}
}.resume()
}
The fundamental problem is you are retrieving data asynchronously (e.g. getUsers will initiate a relatively slow request from the network using URLSession, but returns immediately). Thus this won’t work:
override func viewDidLoad() {
super.viewDidLoad()
getUsers()
print(news)
}
You are returning from getUsers before the news has been retrieved. So news will still be [].
The solution is to give getUsers a “completion handler”, a parameter where you can specify what code should be performed when the asynchronous request is done:
enum NewsError: Error {
case invalidURL
case invalidResponse(URLResponse?)
}
func getUsers(completion: #escaping (Result<[News], Error>) -> Void) {
let queue = DispatchQueue.main
guard let url = URL(string: "http://prostir.news/swift/api2.php") else {
queue.async { completion(.failure(NewsError.invalidURL)) }
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
queue.async { completion(.failure(error)) }
return
}
guard
let data = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
queue.async { completion(.failure(NewsError.invalidResponse(response))) }
return
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let news = try decoder.decode([News].self, from: data)
queue.async { completion(.success(news)) }
} catch let parseError {
queue.async { completion(.failure(parseError)) }
}
}.resume()
}
Then your view controller can fetch the news, passing a “closure”, i.e. code that says what to do when the asynchronous call is complete. In this case, it will set self.news and trigger the necessary UI update (e.g. maybe refresh tableview):
class ViewController: UIViewController {
var news: [News] = []
override func viewDidLoad() {
super.viewDidLoad()
fetchNews()
}
func fetchNews() {
getUsers() { result in
switch result {
case .failure(let error):
print(error)
case .success(let news):
self.news = news
print(news)
}
// trigger whatever UI update you want here, e.g., if using a table view:
//
// self.tableView.reloadData()
}
// but don't try to print the news here, as it hasn't been retrieved yet
// print(news)
}

UITextField to change API URL in Swift 5

I am new iOS Developer
I want to change the websiteLogo API with a textfield to change the URL.
how can I change the line with the ***
with a var and a textfield in my viewcontroller?
With screenshoot it's will be easier to understand what I want? Thank you !!! Guys. OneDriveLink. 1drv.ms/u/s!AsBvdkER6lq7klAqQMW9jOWQkzfl?e=fyqOeN
private init() {}
**private static var pictureUrl = URL(string: "https://logo.clearbit.com/:http://www.rds.ca")!**
private var task: URLSessionDataTask?
func getQuote(callback: #escaping (Bool, imageLogo?) -> Void) {
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: QuoteService.pictureUrl) { (data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
callback(false, nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil)
return
}
let quote = imageLogo(image: data)
callback(true, quote)
print(data)
}
}
task?.resume()
}
First, please don't use screenshots do show your code. If you want help, others typically copy/paste your code to check whats wrong with it.
There are some minor issues with your code. Some hints from me:
Start your types with a big letter, like ImageLogo not imageLogo:
Avoid statics
Avoid singletons (they are almost statics)
Hand in the pictureUrl into getQuote
struct ImageLogo {
var image:Data
}
class QuoteService {
private var task: URLSessionDataTask?
func getQuote(from pictureUrl:URL, callback: #escaping (Bool, ImageLogo?) -> Void) {
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: pictureUrl) {
(data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
callback(false, nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil)
return
}
let quote = ImageLogo(image: data)
callback(true, quote)
print(data)
}
}
task?.resume()
}
}
Store an instance of QuoteService in your view controller
Call getQuote on that instance, handing in the pictureUrl
class ViewController : UIViewController {
var quoteService:QuoteService!
override func viewDidLoad() {
self.quoteService = QuoteService()
}
func toggleActivityIndicator(shown:Bool) { /* ... */ }
func update(quote:ImageLogo) { /* ... */ }
func presentAlert() { /* ... */ }
func updateconcept() {
guard let url = URL(string:textField.text!) else {
print ("invalid url")
return
}
toggleActivityIndicator(shown:true)
quoteService.getQuote(from:url) {
(success, quote) in
self.toggleActivityIndicator(shown:false)
if success, let quote = quote {
self.update(quote:quote)
} else {
self.presentAlert()
}
}
}
/* ... */
}
Hope it helps.
I think you want to pass textfield Text(URL Enter By user) in Web Services
Add a parameter url_str in getQuote function definition first and pass textfield value on that parameters
fun getQuote(url_str : String, callback : #escaping(Bool, ImgaeLogo/)->void){
}

How to do two concurrent API calls in swift 4

Thanks in advance for help,
I have two API calls, both are concurrent and any call could be success first(I don't want call in sequence), after success of both calls, I have to stop my activity indicator and reload my tableView,
Here is my code but I don't know is this the right way or not and how to reload my tableView and stop my activity indicator.
func downloadDetails(){
let operationQueue: OperationQueue = OperationQueue()
let operation1 = BlockOperation() {
WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
}
}
})
let operation2 = BlockOperation() {
webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
}
}
})
}
operationQueue.addOperation(operation2)
}
operationQueue.addOperation(operation1)
}
downloadDetails() "calling function"
This is exactly the use case for DispatchGroup. Enter the group for each call, leave the group when the call finishes, and add a notification handler to fire when they're all done. There's no need for a separate operation queue; these are already asynchronous operations.
func downloadDetails(){
let dispatchGroup = DispatchGroup()
dispatchGroup.enter() // <<---
WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
dispatchGroup.leave() // <<----
}
}
})
dispatchGroup.enter() // <<---
webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
dispatchGroup.leave() // <<----
}
}
})
dispatchGroup.notify(queue: .main) {
// whatever you want to do when both are done
}
}
I would use OperationQueue.
It is preferred for long running task and give you control to cancel request, if needed.
At the end of each operation you can check operation count to know remaining operations.
I have added pseudo code.
let operationQueue: OperationQueue = OperationQueue()
func downloadDetails(){
let operation1 = BlockOperation() { [weak self] in
guard let strongSelf = self else {
return
}
sleep(2)
DispatchQueue.main.async {
strongSelf.handleResponse()
}
let operation2 = BlockOperation() { [weak self] in
guard let strongSelf = self else {
return
}
sleep(2)
DispatchQueue.main.async {
strongSelf.handleResponse()
}
}
strongSelf.operationQueue.addOperation(operation2)
}
self.operationQueue.addOperation(operation1)
}
func handleResponse() {
print("OPERATIONS IN PROGRESS: \(self.operationQueue.operations.count)")
if self.operationQueue.operations.count == 0 {
print("ALL OPERATIONS ARE COMPLETE")
}
}
func cancelOperation() {
self.operationQueue.cancelAllOperations()
}
This prints
OPERATIONS IN PROGRESS: 1
OPERATIONS IN PROGRESS: 0
ALL OPERATIONS ARE COMPLETE

How to run synchronically two functions with async operations on iOS using Swift

lets propose this scenario
a method with async network operations
func asyncMethodA() -> String?
{
result : String?
Alamofire.manager.request(.POST, "https://www.apiweb.com/apimethod", parameters: parameters, encoding:.JSON)
.response { (request, response, rawdata, error) in
if (response?.statusCode == 200)
{
//DO SOME HEAVY LIFTING
}
}
return result //string
}
another method with async network operations
func asyncMethodB() -> String?
{
result : String?
Alamofire.manager.request(.POST, "https://www.yetanotherapiweb.com/apimethod", parameters: parameters, encoding:.JSON)
.response { (request, response, rawdata, error) in
if (response?.statusCode == 200)
{
//DO SOME HEAVY LIFTING
}
}
return result //string
}
a method in which i shall call those methods A and B, to do some operations
func displayResult
{
1) let a = asyncMethodA()
2) let b = asyncMethodB()
3) println(a + b) //some chaotic stuff might happen :(
}
so the question is how i could make that (2) waits for (1) to run, and (3) waits for (2) and so on (that 1,2 and 3 run syncronised)?
(i know that one answer is to chain asyncMethodA and displayResult into asyncMethodB, but want to know if there is some other method)
thank you!.
func anAsyncMethod(resultHandler: (result: AnyObject) -> Void) {
...
}
func anotherAsyncMethod(resultHandler: (result: AnyObject) -> Void) {
...
}
let operationQueue = NSOperationQueue()
func performWithCompletionHandler(completion: (AnyObject?, AnyObject?) -> Void) {
var resultOfOperation1: AnyObject?
var resultOfOperation2: AnyObject?
let operation1 = NSBlockOperation {
let dispatchGroup = dispatch_group_create()
dispatch_group_enter(dispatchGroup)
self.anAsyncMethod {
result in
resultOfOperation1 = result
dispatch_group_leave(dispatchGroup)
}
// wait until anAsyncMethod is completed
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER)
}
let operation2 = NSBlockOperation {
let dispatchGroup = dispatch_group_create()
dispatch_group_enter(dispatchGroup)
self.anotherAsyncMethod {
result in
resultOfOperation2 = result
dispatch_group_leave(dispatchGroup)
}
// wait until anotherAsyncMethod is completed
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER)
}
let completionOperation = NSBlockOperation {
// send all results to completion callback
completion(resultOfOperation1, resultOfOperation2)
}
// configuring interoperation dependencies
operation2.addDependency(operation1)
completionOperation.addDependency(operation2)
operationQueue.addOperations([operation1, operation2, completionOperation], waitUntilFinished: false)
}
Thanks Yimin for the code above. I've updated it to the latest Swift syntax so just posting to be helpful:
func anAsyncMethod(resultHandler: (_ result: AnyObject) -> Void) {
...
}
func anotherAsyncMethod(resultHandler: (_ result: AnyObject) -> Void) {
...
}
func performWithCompletionHandler(completion: #escaping (AnyObject?, AnyObject?) -> Void) {
let operationQueue = OperationQueue()
var resultOfOperation1: AnyObject?
var resultOfOperation2: AnyObject?
let operation1 = BlockOperation {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
self.anAsyncMethod {
result in
resultOfOperation1 = result
dispatchGroup.leave()
}
// wait until anAsyncMethod is completed
dispatchGroup.wait(timeout: DispatchTime.distantFuture)
}
let operation2 = BlockOperation {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
self.anotherAsyncMethod {
result in
resultOfOperation2 = result
dispatchGroup.leave()
}
// wait until anotherAsyncMethod is completed
dispatchGroup.wait(timeout: DispatchTime.distantFuture)
}
let completionOperation = BlockOperation {
// send all results to completion callback
completion(resultOfOperation1, resultOfOperation2)
}
// configuring interoperation dependencies
operation2.addDependency(operation1)
completionOperation.addDependency(operation2)
operationQueue.addOperations([operation1, operation2, completionOperation], waitUntilFinished: false)
}
With the below, you can launch both async methods at the same time and do your heavy lifting after whichever one finishes last.
var methodAFinished = false
var methodBFinished = false
func asyncMethodA() -> String?
{
Alamofire.manager.request(.POST, "https://www.apiweb.com/apimethod", parameters: parameters, encoding:.JSON)
.response { (request, response, rawdata, error) in
if (response?.statusCode == 200) {
methodAFinished = true
doStuff()
}
}
return result //string
}
The guts of asyncMethodB would be methodBFinished = true; doStuff()
func doStuff() {
if methodAFinished && methodBFinished {
// do crazy stuff
}
}

Resources