func with completion

Hi


I'm unit testing some functions with completion handlers but I'm unable to get the callbacks to fire 😕 Here is the code being tested:


public class MyClass1
{
    static func updateName(with myStruct: inout MyStruct, completion: @escaping (Error?) -> Void)
    {
        var newName: String?
        var updateError: Error?

        MyClass2.createNewName()
        {
            (name, error) in
            newName = name
            updateError = error
        }

        /
        if let newName = newName
        {
            myStruct.name = newName
        }
        completion(updateError)
    }
}


public class MyClass2
{
    static func createNewName(completion: @escaping (String?, Error?) -> Void)
    {
        guard let url = URL(string: "http://jsonplaceholder.typicode.com/users/1") else
        {
            return completion(nil, nil)
        }

        let task = URLSession.shared.dataTask(with: url)
        {
            (data, response, error) in
            if error != nil
            {
                completion(nil, error)
            }
   
            if let data = data
            {
                let result = String(data: data, encoding: .utf8)
                completion(result, error)
            }
        }
        task.resume()
    }
}

public struct MyStruct
{
    private var _name: String

    init(name: String)
    {
        self._name = name
    }

    public var name: String
    {
        get {
            return self._name
        }
        set {
            self._name = newValue
        }
    }
}


When I execute MyClass2.createNewName directly, the completion block executes and the function suceeds fine.


However, when I call MyClass1.updateName which calls into MyClass2.createNewName the completion block in MyClass2.createNewName doesn't get executed and the myStruct.name = newName is never hit.


Here's the Playground test to demonstrate:


import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely()

var pet = MyStruct(name: "dog")
print("--Using Class1--")
print("Old name: \(pet.name)")
MyClass1.updateName(with: &pet)
{
    (error) in

    print("error: \(String(describing: error))")
}
print("New name: \(pet.name)")
print("\n")
var person = MyStruct(name: "Jimmy")
print("--Using Class2--")
print("Old name: \(person.name)")
MyClass2.createNewName()
{
    (newName, error) in

    guard error == nil else
    {
        print("error: \(String(describing: error))")
        return
    }

    person.name = newName!
    print("New name: \(person.name)")
}


Any suggestions to resolve this? If I add myStruct.name = newName withing the completion block, I end up with this error:

error: escaping closures can only capture inout parameters explicitly by value


Thanks

I worked up a solution that's achieving the desired result. Here is the updated code sample for MyClass1:


public class MyClass1
{
    static func updateName(with myStruct: inout MyStruct, completion: @escaping (Error?) -> Void)
    {
        var newName: String?
        var updateError: Error?
      
        let group = DispatchGroup()
        group.enter()
      
        MyClass2.createNewName()
        {
            (name, error) in
          
            newName = name
            updateError = error
            print("error: \(String(describing: error))")
            group.leave()
        }
      
        group.wait()
      
        /
        if let newName = newName
        {
            myStruct.name = newName
        }
      
        completion(updateError)
    }
}


If there is a better way?


Thanks

Your updated code is one thing you should never use in actual apps.


It calls `wait` in (possibly) the main thread, which blocks the UI until the dataTask is finished.


With two prerequisites:

  • You should never wait in the main thread.
  • You cannot capture and update the inout paramter in the @escaping closure.


Conclusion:

Give up using inout parameter.


Your code may be too simplified that I cannot understand why you need `MyClass1`.

func with completion
 
 
Q