I have type aliased the completion handler as below, having a completion handler inside the completion handler.
typealias voidCompletionHandler = () -> Void
typealias dataCompletionHandler = (Data, [String: String], voidCompletionHandler) -> Void
I wanted to use computed properties to define the completion handler.
var completion: dataCompletionHandler {
return { data, _, completion in
jsonDataProcess(data: data) { json in // ERROR --> This is SwiftyJSON process with completion handler.
if json != nil {
Country.instantiate(json: json!) {. // ERROR --> The is Core Data process with completion handler
completion()
}
}
}
}
With the above code, I get "Escaping closure captures non-escaping parameter 'completion'.
The sub processes also has #escaping so, they are not the problem. It is the completion handler inside the dataCompletionHandler that I do not know how to define it as #escaping.
// Inside JSON Handler class
func jsonDataProcess(data: Data, completion: #escaping jsonCompletionHandler) {
do {
let json = try JSON(data: data)
if json["Status"].string == "Success" {
completion(json["Data"])
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
// Inside Country Class
static func instantiate(json: JSON, completion: #escaping voidCompletionHandler) {
container.performBackgroundTask { (context) in
let country = Country(context: context)
country.name = json["name"].string
country.iso2digit = json["alpha-2"].string
country.iso3digit = json["alpha-3"].string
country.region = json["region"].string
try? context.save()
print("Country.instantiate : \(String(describing: json["alpha-2"].string))")
completion()
}
}
How do I make the completion handler inside the completion handler as #escaping when using as computed property?
Make the escaping declaration part of the typealias.
Related
I have the following ClassA and ClassB in swift:
protocol ClassBProtocol {
func doSomething(
completionHandler: #escaping (
ClassBProtocol?,
Error?
) -> Void
)
}
class ClassB: ClassBProtocol
{
init(key: String) {
self.key = key
}
func doSomething(
completionHandler: #escaping (
ClassBProtocol?,
Error?
) -> Void
) {
// Does some network requests and if it was successful does the following:
completionHandler(ClassB(), nil)
}
}
public class ClassA: ClassAProtocol {
static var instance: ClassA? = nil
static let initQueue = DispatchQueue(label: "queue")
static let semaphore = DispatchSemaphore(value: 1)
#objc public static func getSomething(
withKey key: String,
completionHandler: #escaping (ClassA?, Error?) -> Void
) {
ClassA.initQueue.async {
ClassA.semaphore.wait()
DispatchQueue.main.async {
if let objectA = ClassA.instance {
ClassA.semaphore.signal()
completionHandler(objectA, nil)
return
}
let objectB = ClassB(withKey: key)
objectB.doSomething { response, error in
guard let response = response else {
ClassA.semaphore.signal()
completionHandler(nil, error)
return
}
let objectA = ClassA()
ClassA.instance = objectA
ClassA.semaphore.signal()
completionHandler(objectA, nil)
}
}
}
}
}
And the following test case to test ClassA.getSomething() and ensure a race condition does not happen:
func testgetSomethingReturnsSameInstance() {
let expectation1 = self.expectation(description: "getSomething 1 completed")
let expectation2 = self.expectation(description: "getSomething 2 completed")
let expectation3 = self.expectation(description: "getSomething 3 completed")
var client1: ClassA?
var client2: ClassA?
var client3: ClassA?
ClassA.getSomething() { (client, error) in
client1 = client
expectation1.fulfill()
}
ClassA.getSomething() { (client, error) in
client2 = client
expectation2.fulfill()
}
ClassA.getSomething() { (client, error) in
client3 = client
expectation3.fulfill()
}
waitForExpectations(timeout: 10) { (error) in
XCTAssertEqual(client1, client2)
XCTAssertEqual(client2, client3)
XCTAssertEqual(client1, client3)
}
}
The doSomething in ClassB sends a network request and will return an object. I need to mock this method to do the following:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completionHandler(response: ClassB(), error: nil)
}
But couldn't find any way to do it. Does anyone have a solution for this?
It'd be easier to understand your code if you used reasonable names. I suspect ClassB wraps an API, and ClassA holds some value (like a database) that you want to fetch from the API exactly once. So let's rename ClassBProtocol and ClassB accordingly:
protocol API {
func fetch(completion: #escaping (Result<Data, Error>) -> Void)
}
class LiveAPI: API {
let key: String
init(key: String) {
self.key = key
}
func fetch(completion: #escaping (Result<Data, Error>) -> Void) {
// Do some network requests to get the real `Data` or a real error, and then:
completion(.success(Data()))
// or in case of error:
// completion(.failure(error))
}
}
Then let's rename ClassA to Database and put getSomething on hold, leaving just this:
public class Database: NSObject {
private init(data: Data) {
// construct myself from the raw data
}
}
Now, how to fetch the database exactly once, the first time it's requested?
The general advice from Apple engineers is to avoid potentially blocking operations in blocks you send to Dispatch queues. In this case, semaphore.wait() is a potentially blocking operation.
Furthermore, synchronous code is easier to test than asynchronous code, but you've made everything asynchronous. The first thing your getSomething does is an async dispatch, and a significant chunk of state (the set of pending completion handlers) is hidden away in Dispatch data structures that we cannot access.
Instead of using semaphore and initQueue, let's manually and synchronously track the completion handlers that need to be called when the database has been fetched. There are three states:
We haven't started fetching the database.
We've started fetching the database, but it's still downloading and we have one or more completion handlers to be called when it's finished.
We've finished fetching the database and there are no completion handlers to call.
We'll store these three mutually-exclusive states using an enum, and guard access to the stored state using a DispatchQueue:
extension Database {
// All access to q_fetchState must be on q!
private static var q_fetchState: FetchState = .unstarted
private static let q = DispatchQueue(label: "initQueue")
private typealias Completion = (Result<Database, Error>) -> Void
private enum FetchState {
case unstarted
case started([Completion])
case done(Result<Database, Error>)
}
}
When asked for the database, we examine the state and act appropriately:
extension Database {
#objc
public static func getDatabase(
apiKey key: String,
completion objc_completion: #escaping (Database?, Error?) -> Void
) {
let completion: Completion = {
switch $0 {
case .failure(let error): objc_completion(nil, error)
case .success(let database): objc_completion(database, nil)
}
}
q.sync {
switch q_fetchState {
case .unstarted:
q_fetchState = .started([completion])
DispatchQueue.main.async {
let api = LiveAPI(key: key)
api.fetch { result in
let result = result.map { Database(data: $0) }
let completions = q.sync {
guard case .started(let completions) = q_fetchState else {
preconditionFailure()
}
q_fetchState = .done(result)
return completions
}
for completion in completions {
completion(result)
}
}
}
case .started(let array):
q_fetchState = .started(array + [completion])
case .done(let result):
DispatchQueue.main.async {
completion(result)
}
}
}
}
}
Notice that there are no blocking operations performed under q, so it's safe and efficient to use q.sync instead of q.async, and we'll see later that it makes the function more testable.
Okay, now back to your actual question, which I interpret as: How do we mock the API? Since we already have an API protocol, we want to make getDatabase generic over a type conforming to API, and make it take an instance of that type:
extension Database {
static func getDatabase<A: API>(
api: A,
completion: #escaping (Result<Database, Error>) -> Void
) {
q.sync {
switch q_fetchState {
case .unstarted:
q_fetchState = .started([completion])
DispatchQueue.main.async {
api.fetch { result in
let result = result.map { Database(data: $0) }
let completions = q.sync {
guard case .started(let completions) = q_fetchState else {
preconditionFailure()
}
q_fetchState = .done(result)
return completions
}
for completion in completions {
completion(result)
}
}
}
case .started(let array):
q_fetchState = .started(array + [completion])
case .done(let result):
DispatchQueue.main.async {
completion(result)
}
}
}
}
}
These changes mean the method is no longer compatible with Objective-C. So let's add an overload with the old, Objective-C-compatible signature:
#objc
public static func getDatabase(
apiKey key: String,
completion: #escaping (Database?, Error?) -> Void
) {
return getDatabase(api: LiveAPI(key: key)) {
switch $0 {
case .failure(let error): completion(nil, error)
case .success(let database): completion(database, nil)
}
}
}
}
Now we're ready to write a mock implementation of API. Based on the code you posted, it would look like this:
struct BadTestAPI: API {
let result: Result<Data, Error>
func fetch(completion: #escaping (Result<Data, Error>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion(result)
}
}
}
But I don't like this implementation for at least three reasons:
It hardcodes a 0.5 second delay. Yuck. We want test cases to run as fast as possible!
It doesn't make it easy to verify that fetch is only called once.
It doesn't let us control more precisely when it calls the completion handler.
Instead, let's write the mock implementation this way:
class TestAPI: API {
let ex = XCTestExpectation(description: "api.fetch called")
var completion: ((Result<Data, Error>) -> Void)? = nil
func fetch(completion: #escaping (Result<Data, Error>) -> Void) {
XCTAssertNil(self.completion)
self.completion = completion
ex.fulfill()
}
}
Now we can write the test case to use this implementation:
final class TestDatabase: XCTestCase {
func testGetDatabaseReturnsSameInstance() {
class Record {
let ex = XCTestExpectation()
var database: Database? = nil
}
let api = TestAPI()
let records = [Record(), Record(), Record()]
XCTAssertNil(api.completion)
for record in records {
Database.getDatabase(api: api) {
XCTAssertNil(record.database)
record.database = try! $0.get()
record.ex.fulfill()
}
}
self.wait(for: [api.ex], timeout: 10)
for record in records {
XCTAssertNil(record.database)
}
api.completion!(.success(Data()))
wait(for: records.map(\.ex), timeout: 10)
XCTAssertNotNil(records[0].database)
for record in records.dropFirst() {
XCTAssertEqual(record.database, records[0].database)
}
}
}
Here are some things this test case verifies:
api.fetch is not called before Database.getDatabase.
api.fetch is only called once.
No completion handler is called more than once.
The getDatabase completion handlers are called after the api.fetch completion handler.
I have a function that takes 2 callbacks. I want to convert this into async/await. But how can I await while continuously returning the progress also?
I am using https://github.com/yannickl/AwaitKit to get rid of callbacks.
typealias GetResultCallBack = (String) -> Void
typealias ProgressCallBack = (Double) -> Void
func getFileFromS3(onComplete callBack: #escaping GetResultCallBack,
progress progressCallback: #escaping ProgressCallBack) {
}
I am using it like this:
getFileFromS3() { [weak self] (result) in
guard let self = self else { return }
// Do something with result
} progress: { [weak self] (progress) in
guard let self = self else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self else {return}
// Update progress in UI
}
}
Here is what converted code looks without progress reporting:
func getFileFromS3() -> Promise<String> {
return async {
// return here
}
}
You could use a technique similar to this:
https://developer.apple.com/documentation/foundation/urlsession/3767352-data
As you can see from the signature...
func data(for request: URLRequest,
delegate: URLSessionTaskDelegate? = nil) async throws
-> (Data, URLResponse)
...it is async, but it also takes a delegate object:
https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate
As you can see, that delegate receives callbacks for task progress. You can declare something similar, and thus feed that info from the delegate over to the main actor and the interface.
I took a while but I finally achieved this result, first you have to start the task setting a delegate:
let (data, _) = try await URLSession.shared.data(for: .init(url: url), delegate: self)
In your delegate you subscribe to the progress update:
public func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) {
progressObservation = task.progress.observe(\.fractionCompleted) { progress, value in
print("progress: ", progress.fractionCompleted)
}
}
Don't forget that "progressObservation" needs to be a strong reference
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 7 years ago.
My question is simple but after many research and tests I do not succeed to wait for a function end before continuing the flow.
Example :
print("Before stuff")
do_stuff {
print("After stuff")
}
func do_stuff(onCompleted: () -> ()) {
let postEndpoint: String = "http://localhost:8080/users/1234567890987654"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on /users/1234567890987654")
print(error)
return
}
// parse the result as JSON
let user: NSDictionary
do {
user = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
// Means that user does not exist
return
}
print("The user is: " + user.description)
})
task.resume()
onCompleted()
}
How to wait do_stuff() end before the second print?
Thanks for your help, I think I miss something.
Thomas
There is something fundamental that you haven't understood. It's not actually the function, which is executing. It's the closure defined inside the function. What you need to wait is the closure to be called when the request has completed. And in fact, you should not wait, but assign an another closure outside the function to be called from the closure inside the function.
print("Before stuff")
do_stuff {
// Now the "function" has completed.
print("After stuff")
}
func do_stuff(onCompleted: () -> ()) {
let task = session.dataTaskWithRequest(urlRequest) { data, response, error in
...
onCompleted()
}
}
You could always use a completion handler for the do_stuff() function:
func do_stuff(completion: (() -> Void)?) -> () {
...
if completion != nil {
return completion!() // Return completion where you want you "after stuff" declarations to run.
}
}
And call the function as such:
do_stuff({
print("After stuff")
})
I'm trying to add a throws to my existing function with a completion handler but I keep getting a warning saying no calls throwing functions occur within try expression. In the section where I throw the errors, I get an error saying
invalid conversion from throwing function of type '() throwing -> Void' to non-throwing function type.
enum LoginError: ErrorType {
case Invalid_Credentials
case Unable_To_Access_Login
case User_Not_Found
}
#IBAction func loginPressed(sender: AnyObject) {
do{
try self.login3(dict, completion: { (result) -> Void in
if (result == true)
{
self.performSegueWithIdentifier("loginSegue", sender: nil)
}
})
}
catch LoginError.User_Not_Found
{
//deal with it
}
catch LoginError.Unable_To_Access_Login
{
//deal with it
}
catch LoginError.Invalid_Credentials
{
//deal with it
}
catch
{
print("i dunno")
}
}
func login3(params:[String: String], completion: (result:Bool) throws -> Void)
{
//Request set up
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary
if let parseJSON = json
{
let userID = parseJSON["user_id"] as? Int
let loginError = parseJSON["user_not_found"] as? String
let validationError = parseJSON["invalid_credentials"] as? String
let exception = parseJSON["unable_to_access_login"] as? String
var responseArray = [(parseJSON["user_id"] as? Int)]
if userID != nil
{
dispatch_async(dispatch_get_main_queue()) {
completion(result:true)
}
}
else if loginError != ""
{
dispatch_async(dispatch_get_main_queue()){
completion(result: false)
self.loginErrorLabel.text = loginError
throw LoginError.User_Not_Found
}
}
else if validationError != ""
{
dispatch_async(dispatch_get_main_queue()){
completion(result:false)
self.validationErrorLabel.text = validationError
throw LoginError.Invalid_Credentials
}
}
else if exception != nil
{
dispatch_async(dispatch_get_main_queue()){
completion(result:false)
self.exceptionErrorLabel.text = "Unable to login"
throw LoginError.Unable_To_Access_Login
}
}
}
else
{
}
}
catch let parseError {
// Log the error thrown by `JSONObjectWithData`
})
task.resume()
}
What you can do is encapsulating the error into a throwable closure like in the following code to achieve what you want:
func login3(params:[String: String], completion: (inner: () throws -> Bool) -> ()) {
let task = session.dataTaskWithRequest(request, completionHandler: { data, response, error -> Void in
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary
if let parseJSON = json {
let userID = parseJSON["user_id"] as? Int
let loginError = parseJSON["user_not_found"] as? String
let validationError = parseJSON["invalid_credentials"] as? String
let exception = parseJSON["unable_to_access_login"] as? String
var responseArray = [(parseJSON["user_id"] as? Int)]
if userID != nil {
dispatch_async(dispatch_get_main_queue()) {
completion(inner: { return true })
}
}
else if loginError != ""
{
dispatch_async(dispatch_get_main_queue()) {
self.loginErrorLabel.text = loginError
completion(inner: { throw LoginError.User_Not_Found })
}
}
else if validationError != ""
{
dispatch_async(dispatch_get_main_queue()) {
self.validationErrorLabel.text = validationError
completion(inner: {throw LoginError.Invalid_Credentials})
}
}
else if exception != nil
{
dispatch_async(dispatch_get_main_queue()){
self.exceptionErrorLabel.text = "Unable to login"
completion(inner: {throw LoginError.Unable_To_Access_Login})
}
}
}
else
{
}
}
task.resume()
}
And the you can call it like in the following way:
self.login3(dict) { (inner: () throws -> Bool) -> Void in
do {
let result = try inner()
self.performSegueWithIdentifier("loginSegue", sender: nil)
} catch let error {
print(error)
}
}
The trick is that the login3 function takes an additional closure called 'inner' of the type () throws -> Bool. This closure will either provide the result of the computation, or it will throw. The closure itself is being constructed during the computation by one of two means:
In case of an error: inner: {throw error}
In case of success: inner: {return result}
I strongly recommend you an excellent article about using try/catch in async calls Using try / catch in Swift with asynchronous closures
I hope this help you.
You're asking for X and I'm answering Y, but just in case...
There's always the possibility to add the throwing capability to your function instead of the completion handler:
func login3(params:[String: String], completion: (result:Bool) -> Void) throws {
...
}
Then you can call it from inside IBAction:
do {
try self.login3(dict) { result -> Void in
...
}
} catch {
print(error)
}
Read the below with a grain of salt. I haven't worked much with Swift 2.0 yet:
You created a "dataTask" task who's's completion handler has a throw in it, but the only actual code in your login3 method is task.resume(). The completion handler won't get executed until after login3 returns. (In fact, it's a parameter to another object, so the compiler has no idea what's going to happen with that code.)
As I understand it, the actual top-to-bottom body of your login3 method must contain a throw. Since it's an async method, you can't do that. Thus, don't make your login3 function throw. Instead have it pass an error object to it's completion handler.
In my impression this is caused by the signature of your functions. In #IBAction func loginPressed(sender: AnyObject) you don't have to use try when calling login3 as the function itself is not marked as throwing but the completion handler is. But in fact the completion closure of login3 will never throw as you execute all throwing functions inside a do {} catch {} block, so you could try to remove the throws annotation from the login3 completion closure and also call the closure if you catch an error in login3 with an according result. This way you handle all throwing functions inside of login3 in a do {} catch {} block and call the completion handler with a suitable value.
Generally I am also not aware that you can catch without a preceding do {} block like you did in the #IBAction.
I'd like to return some values after the long term operation is completed.
But furthermore I'd like to split the logic and the gui.
For example; I have two classes
SomeServices.swift which has a method named "getDataFromService..."
MyTableViewController.swift which will display the result from "getDataFromService"
So, previously in Objective-C I've just add a method in SomeServices like this:
(void)getDataFromService:(void (^)(NSArray *, NSError *))completionBlock{ ...... }
In this method I've just called completionBlock(myData, myError) to return my values to the tableviewcontroller.
What would be the equivalent closure which I have to define in SomeServices.swift and how will it be called in MyTableViewController?
I know how to call a simple closures like this one:
....({
responseData, error in
if(!error){
//Do something
}
})
But I don't have any ideas how to define a closure with a completionBlock equivalent.
Any help would be appreciated
The plus of closures are, that you can pass everything you want. Methods or functions - it doesn't matter.
You can pass a function within the parameters and just call it.
func someFunctionThatTakesAClosure(completionClosure: () -> ()) {
// function body goes here
if(error = false) {
completionClosure()
}
}
//Call it
someFunctionThatTakesAClosure({
//Completions Stuff
println("someFunctionThatTakesAClosure")
});
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/ch/jEUH0.l
The answer is in the language guide:
Assume you want to return a String. This is the syntax
({(responseData: DataClass, error: ErrorClass) -> String in
//do stuff - calculations etc..
return calculatedString
})
Here is an example that takes two strings and concatenates them, and returns the result:
let sumStrings = ({(first: String, second: String) -> String in
return first + " " + second
})
then you can do the following:
sumStrings("Hello","Swift") // "Hello Swift"
Here is how I use a singleton ServiceManager to achieve this.
class ServiceManager: NSObject {
// Static Instance variable for Singleton
static var sharedSessionManager = ServiceManager()
// Function to execute GET request and pass data from escaping closure
func executeGetRequest(with urlString: String, completion: #escaping (Data?) -> ()) {
let url = URL.init(string: urlString)
let urlRequest = URLRequest(url: url!)
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
// Log errors (if any)
if error != nil {
print(error.debugDescription)
} else {
// Passing the data from closure to the calling method
completion(data)
}
}.resume() // Starting the dataTask
}
// Function to perform a task - Calls executeGetRequest(with urlString:) and receives data from the closure.
func downloadMovies(from urlString: String, completion: #escaping ([Movie]) -> ()) {
// Calling executeGetRequest(with:)
executeGetRequest(with: urlString) { (data) in // Data received from closure
do {
// JSON parsing
let responseDict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
if let results = responseDict!["results"] as? [[String:Any]] {
var movies = [Movie]()
for obj in results {
let movie = Movie(movieDict: obj)
movies.append(movie)
}
// Passing parsed JSON data from closure to the calling method.
completion(movies)
}
} catch {
print("ERROR: could not retrieve response")
}
}
}
}
Below is the example how I use the singleton class.
ServiceManager.sharedSessionManager.downloadMovies(from: urlBase) { (movies : [Movie]) in // Object received from closure
self.movies = movies
DispatchQueue.main.async {
// Updating UI on main queue
self.movieCollectionView.reloadData()
}
}
I hope this helps anybody looking for the same solution.