Completion block not called (Swift) - ios

I have written a function with a completion block as an argument. When I call this function in my view controller, the completion block is not executed. This is my first time writing my own custom completion block, so I may be making just an easy mistake. Here is the function in my model:
func iterateThrough2(userInput: String, completion: (completed: Bool) -> Bool) -> Double {
while numberOfCharactersDone < userInput.characters.count {
let startIndex = userInput.startIndex.advancedBy(numberOfCharactersDone)
let letterIndex = userInput.characters[startIndex]
let letter = String(letterIndex)
if isLetter(letter) {
isALetter(letter, input: userInput)
}
else {
isNotLetter(letter)
}
numberOfCharactersDone++
}
return total
}
And in my view controller, I call it:
#IBAction func calculate(sender: UIButton) {
if let text = self.enteredText.text {
let result = calculator.iterateThrough2(text, completion: { (completed) -> Bool in
if completed {
print("good")
return true
}
else {
print("not good")
return false
}
})
self.outputLabel.text = String(result)
calculator.resetData()
}
}
The function does successfully return a double, as it should, and it is working otherwise. It just doesn't call the closure. What am I doing wrong?
Do I have to include and specify in my method when exactly I want the closure to be ran? I read here: https://www.codefellows.org/blog/writing-completion-blocks-with-closures-in-swift and tried to call the completion block at the end of my method like:
completion(completed: completed)
return total
but I got an error "use of unresolved identifier "completed""

Related

Swift. How to return a function from a closure

I have a question regarding closure
function AA(){
localPlayer.authenticateHandler{
//…
if trigger {
retrun
}
}
dosomething()
}
In the above code,
I want to write a code that wants to return to the AA() function when the trigger is satisfied in the authenticateHandler closure that is called by an asynchronous callback.
The result of the code above is
When trigger occurs, only the closure is returned and the dosomething() method is executed below.
Is there any way
I have short English skills, but thank you for your help.
Closure does not support return statement. Instead use completion block inside main thread to perform the task:
i.e
function AA( completion: #escaping (Result<[Return Type], Error>) -> Void) {
localPlayer.authenticateHandler{
//…
if trigger {
DispatchQueue.main.async {
completion(//Whatever Return Value)
}
}
dosomething()
}
}
Closure does not support return statement.
Do it this way.
here i used string comparison to show how completion block will work.
func call() {
compareString(str1: "hey", str2: "hey") { isMatch in
if isMatch! { //Trigger
self.doSomething()
}
}
}
func compareString(str1:String,str2:String,completion:#escaping ((Bool?) -> Void)) {
if str1 == str2 {
completion(true)
}else {
completion(false)
}
}
func doSomething() {
}
It doesn’t work that way. A synchronous function can’t wait for the result of an asynchronous function. Give AA a callback closure and call it when authenticateHandler returns.

Should we continue explicitly capturing variable as weak in iOS

Suppose we got a chain of closures, like so:
var myOtherVc: UIViewController! // get it somehow
self.dismiss(animated: true, completion: { [weak myOtherVc] in
myOtherVc?.present(sthElse, animated: true, completion: { [weak myOtherVc] in // <-- HERE
})
})
My question is if we captured variable myOtherVc in the topmost block as weak should we keep being explicit about weak in all the children blocks or is compiler smart enough to tell ARC to not retain?
Update
I guess I need to clarify, what if the block was escaping?
Also, i DO care about delayed deallocation. This is the whole point of using weak for me.
public func doWhatever(_ success: #escaping () -> Void) {
// do whatever
})
var myOtherVc: UIViewController! // get it somehow
self.dismiss(animated: true, completion: { [weak myOtherVc] in
SomeClass.doWhatever({ [weak myOtherVc] in // <-- HERE
myOtherVc?.present(sthElse, animated: true, completion: { [weak myOtherVc] in // <-- and HERE, too
})
})
})
We suppose all closures are escaping, so I made this playground and I conclude that you must capture your variable as weak in the latest closure that is using your variable and it won't infer its reference type from the parent or top closure:
typealias Closure = () -> Void
class A {
var closureA : Closure?
func runClosureA(closure: #escaping Closure) {
self.closureA = closure
closureA?()
}
func print() {
debugPrint("A is here!")
}
deinit {
debugPrint("A deinited")
}
}
another class which operates on chained closure:
class B {
func runClosureB(closure: #escaping Closure) {
closure()
}
func operate() {
let a : A = A()
runClosureB { [weak a] in
a?.runClosureA { [a] in
a?.print()
}
}
}
deinit {
debugPrint("B deinited")
}
}
The code is:
var b: B? = B()
b?.operate()
b = nil
It will prints:
// "A is here!"
// "B deinited"
But by changing the operate function to this:
func operate() {
let a : A = A()
runClosureB { [a] in
a.runClosureA { [weak a] in
a?.print()
}
}
}
The result will change to this:
"A is here!"
"A deinited"
"B deinited"
Update: In class A I made a strong reference to the closure as closureA, if you don't create the reference, there is no need to capture self as weak in closures.
In fact, it depends on which closure you are using and the relations between them and if there can be retained cycle so you should consider capturing the right closure as weak.
In your case, with present(_, animated:, completion:), the completion block is non-escaping so if you want to use weak reference you can use it but it is not necessary to use.
Non-escaping closures do not require [weak self] unless you care about delayed deallocation
Please check the article about weak, unowned references in nested closures.
I made a small test in the playground that shows me that the compiler is indeed quite smart and tells ARC not to retain.
class WeakThingy {
var variable: Int
init(variable: Int) {
self.variable = variable
}
deinit {
print("deinit WeakThingy")
}
}
class ClosureTest {
var maybeNil: WeakThingy
init(maybeNil: WeakThingy) {
self.maybeNil = maybeNil
}
deinit {
print("deinit ClosureTest")
}
func bien() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak maybeNil] in
print("first \(String(describing: maybeNil))")
maybeNil?.variable = 12
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
print("second \(String(describing: maybeNil))")
maybeNil?.variable = 12
}
}
}
}
var closureTest:ClosureTest? = ClosureTest(maybeNil: WeakThingy(variable: 12))
closureTest?.bien()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
closureTest = nil
}
What's printed is the following
first Optional(__lldb_expr_34.WeakThingy)
deinit ClosureTest
deinit WeakThingy
second nil
What happen here is that I pass maybeNil in the first closure as weak, but not in the second. Then I make it nil before the second closure gets executed. We can see in the output that it is well deallocated before entering the second closure.

How to test method that is called with DispatchQueue.main.async?

In code I do it like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateBadgeValuesForTabBarItems()
}
private func updateBadgeValuesForTabBarItems() {
DispatchQueue.main.async {
self.setBadge(value: self.viewModel.numberOfUnreadMessages, for: .threads)
self.setBadge(value: self.viewModel.numberOfActiveTasks, for: .tasks)
self.setBadge(value: self.viewModel.numberOfUnreadNotifications, for: .notifications)
}
}
and in tests:
func testViewDidAppear() {
let view = TabBarView()
let model = MockTabBarViewModel()
let center = NotificationCenter()
let controller = TabBarController(view: view, viewModel: model, notificationCenter: center)
controller.viewDidLoad()
XCTAssertFalse(model.numberOfActiveTasksWasCalled)
XCTAssertFalse(model.numberOfUnreadMessagesWasCalled)
XCTAssertFalse(model.numberOfUnreadNotificationsWasCalled)
XCTAssertFalse(model.indexForTypeWasCalled)
controller.viewDidAppear(false)
XCTAssertTrue(model.numberOfActiveTasksWasCalled) //failed
XCTAssertTrue(model.numberOfUnreadMessagesWasCalled) //failed
XCTAssertTrue(model.numberOfUnreadNotificationsWasCalled) //failed
XCTAssertTrue(model.indexForTypeWasCalled) //failed
}
But all my four latest assertions failed. Why? How can I test it with success?
I think the best approach to test this is to mock the DispatchQueue. You can create a protocol that defines the functionality that you want to use:
protocol DispatchQueueType {
func async(execute work: #escaping #convention(block) () -> Void)
}
Now extend DispatchQueue to conform to your protocol, like:
extension DispatchQueue: DispatchQueueType {
func async(execute work: #escaping #convention(block) () -> Void) {
async(group: nil, qos: .unspecified, flags: [], execute: work)
}
}
Note I had to omit from the protocol the parameters you didn't use in your code, like group, qos, and flags, since protocol don't allow default values. And that's why the extension had to explicitly implement the protocol function.
Now, in your tests, create a mocked DispatchQueue that conforms to that protocol and calls the closure synchronously, like:
final class DispatchQueueMock: DispatchQueueType {
func async(execute work: #escaping #convention(block) () -> Void) {
work()
}
}
Now, all you need to do is inject the queue accordingly, perhaps in the view controller's init, like:
final class ViewController: UIViewController {
let mainDispatchQueue: DispatchQueueType
init(mainDispatchQueue: DispatchQueueType = DispatchQueue.main) {
self.mainDispatchQueue = mainDispatchQueue
super.init(nibName: nil, bundle: nil)
}
func foo() {
mainDispatchQueue.async {
*perform asynchronous work*
}
}
}
Finally, in your tests, you need to create your view controller using the mocked dispatch queue, like:
func testFooSucceeds() {
let controller = ViewController(mainDispatchQueue: DispatchQueueMock())
controller.foo()
*assert work was performed successfully*
}
Since you used the mocked queue in your test, the code will be executed synchronously, and you don't need to frustratingly wait for expectations.
You don't need to call the code in the updateBadgeValuesForTabBarItems method on the main queue.
But if you really need it, you can do something like this:
func testViewDidAppear() {
let view = TabBarView()
let model = MockTabBarViewModel()
let center = NotificationCenter()
let controller = TabBarController(view: view, viewModel: model, notificationCenter: center)
controller.viewDidLoad()
XCTAssertFalse(model.numberOfActiveTasksWasCalled)
XCTAssertFalse(model.numberOfUnreadMessagesWasCalled)
XCTAssertFalse(model.numberOfUnreadNotificationsWasCalled)
XCTAssertFalse(model.indexForTypeWasCalled)
controller.viewDidAppear(false)
let expectation = self.expectation(description: "Test")
DispatchQueue.main.async {
expectation.fullfill()
}
self.waitForExpectations(timeout: 1, handler: nil)
XCTAssertTrue(model.numberOfActiveTasksWasCalled)
XCTAssertTrue(model.numberOfUnreadMessagesWasCalled)
XCTAssertTrue(model.numberOfUnreadNotificationsWasCalled)
XCTAssertTrue(model.indexForTypeWasCalled)
}
But this is not good practice.
I had use DispatchQueue.main.asyncAfter() in my test along with expectation otherwise it was failing before text could set inside DispatchQueue.main.async {}
Methods to Test:
func setNumpadTexts(_ numpad: NumericalKeyboardVC) {
numpad.setTexts(belowNumberLabelText: Currency.symbol, enterKeyText: NSLocalizedString("Add", comment:""))
}
func setTexts(belowNumberLabelText: String? = "", enterKeyText: String) {
DispatchQueue.main.async {
self.belowNumberDisplayLbl.text = belowNumberLabelText
self.enterBtn.setTitle(enterKeyText, for: .normal)
}
}
Test:
func testSetNumpadTexts() {
sut.setNumpadTexts(numpad)
let expectation = expectation(description: "TextMatching")
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
//then
XCTAssertEqual(self.numpad.enterBtn.title(for: .normal), NSLocalizedString("Add", comment:""))
XCTAssertEqual(self.numpad.belowNumberDisplayLbl.text, Currency.symbol)
expectation.fulfill()
})
wait(for: [expectation], timeout: 2.0)
}
You should
Inject the dependency (DispatchQueue) into your view controller, so that you can change it in the tests
Invert the dependency using a protocol, to better conform to SOLID principles (Interface seggregation and Dependency Inversion)
Mock DispatchQueue in your tests, so that you can control your scenario
Lets apply those three items:
To invert the dependency, we will need an abstract type, that is, in Swift, a protocol. We then extend DispatchQueue to conform to that protocol
protocol Dispatching {
func async(execute workItem: DispatchWorkItem)
}
extension DispatchQueue: Dispatching {}
Next, we need to inject the dependency into our view controller. That means, pass anything that is dispatching to our view controller
final class MyViewController {
// MARK: - Dependencies
private let dispatchQueue: Dispatching // Declading that our class needs a dispatch queue
// MARK: - Initialization
init(dispatchQueue: Dispatching = DispatchQueue.main) { // Injecting the dependencies via constructor
self.dispatchQueue = dispatchQueue
super.init(nibName: nil, bundle: nil) // We must call super
}
#available(*, unavailable)
init(coder aCoder: NSCoder?) {
fatalError("We should only use our other init!")
}
// MARK: - View lifecycle
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateBadgeValuesForTabBarItems()
}
// MARK: - Private methods
private func updateBadgeValuesForTabBarItems() {
dispatchQueue.async { // Using our dependency instead of DispatchQueue directly
self.setBadge(value: self.viewModel.numberOfUnreadMessages, for: .threads)
self.setBadge(value: self.viewModel.numberOfActiveTasks, for: .tasks)
self.setBadge(value: self.viewModel.numberOfUnreadNotifications, for: .notifications)
}
}
}
Lastly, we need to create a mock for our tests. In this case, by following the testing doubles, we should create a Fake, that is, a DispatchQueue mock that doesn't really work in production, but works on our tests
final class DispatchFake: Dispatching {
func async(execute workItem: DispatchWorkItem) {
workItem.perform()
}
}
When we're testing, all we need to do is create our System Under Test(the controller, in this case), passing a fake dispatching instance
You can easy achieve this by checking if current thread is main and execute code synchronously in this case.
For example in presenter I update view in this way:
private func updateView(with viewModel: MyViewModel) {
if Thread.isMainThread {
view?.update(with: viewModel)
} else {
DispatchQueue.main.async {
self.view?.update(with: viewModel)
}
}
}
And then I can write synchronous unit tests for my presenter:
func testOnViewDidLoadFetchFailed() throws {
presenter.onViewDidLoad()
// presenter is calling interactor.fetchData when onViewDidLoad is called
XCTAssertEqual(interactor.fetchDataCallsCount, 1)
// test execute fetchData completion closure manually in the main thread
interactor.fetchDataCalls[0].completion(.failure(TestError()))
// presenter will call updateView(viewModel:) internally in synchronous way
// because we have check if Thread.isMainThread in updateView(viewModel:)
XCTAssertEqual(view.updateCallsCount, 1)
guard case .error = view.updateCalls[0] else {
XCTFail("error expected, got \(view.updateCalls[0])")
return
}
}
Here is a small proof of concept of how you could achieve it:
func testExample() {
let expectation = self.expectation(description: "numberOfActiveTasks")
var mockModel = MockModel()
mockModel.numberOfActiveTasksClosure = {() in
expectation.fulfill()
}
DispatchQueue.main.async {
_ = mockModel.numberOfActiveTasks
}
self.waitForExpectations(timeout: 2, handler: nil)
}
and here is the MockModel:
struct MockModel : Model {
var numberOfActiveTasks: Int {
get {
if let cl = numberOfActiveTasksClosure {
cl()
}
//we dont care about the actual value for this test
return 0
}
}
var numberOfActiveTasksClosure: (() -> ())?
}
To test asynchronous code you should modify your updateBadgeValuesForTabBarItems function and call it directly from your tests with a completion closure:
func updateBadgeValuesForTabBarItems(completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
self.setBadge(value: self.viewModel.numberOfUnreadMessages, for: .threads)
self.setBadge(value: self.viewModel.numberOfActiveTasks, for: .tasks)
self.setBadge(value: self.viewModel.numberOfUnreadNotifications, for: .notifications)
completion?()
}
}
Now you are be able to call this function as before in your regular code e.g.: updateBadgeValuesForTabBarItems(). But for tests you can add a completion closure and use XCTestExpectation to wait:
func testBadge() {
...
let expectation = expectation(description: "Badge")
updateBadgeValuesForTabBarItems {
XCTAssertTrue(model.numberOfActiveTasksWasCalled)
XCTAssertTrue(model.numberOfUnreadMessagesWasCalled)
XCTAssertTrue(model.numberOfUnreadNotificationsWasCalled)
XCTAssertTrue(model.indexForTypeWasCalled)
expectation.fulfill()
}
wait(for: [expectation], timeout: 1)
}

How can I unit test that a block of code is run on DispatchQueue.main

Caveat - I read the few questions about testing threads but may have missed the answer so if the answer is there and I missed it, please point me in the right direction.
I want to test that a tableView call to reloadData is executed on the main queue.
This should code should result in a passing test:
var cats = [Cat]() {
didSet {
DispatchQueue.main.async { [weak self] in
tableView.reloadData()
}
}
}
This code should result in a failing test:
var cats = [Cat]() {
didSet {
tableView.reloadData()
}
}
What should the test look like?
Note to the testing haters: I know this is an easy thing to catch when you run the app but it's also an easy thing to miss when you're refactoring and adding layers of abstraction and multiple network calls and want to update the UI with some data but not other data etc etc... so please don't just answer with "Updates to UI go on the main thread" I know that already. Thanks!
Use dispatch_queue_set_specific function in order to associate a key-value pair with the main queue
Then use dispatch_queue_get_specific to check for the presence of key & value:
fileprivate let mainQueueKey = UnsafeMutablePointer<Void>.alloc(1)
fileprivate let mainQueueValue = UnsafeMutablePointer<Void>.alloc(1)
/* Associate a key-value pair with the Main Queue */
dispatch_queue_set_specific(
dispatch_get_main_queue(),
mainQueueKey,
mainQueueValue,
nil
)
func isMainQueue() -> Bool {
/* Checking for presence of key-value on current queue */
return (dispatch_get_specific(mainQueueKey) == mainQueueValue)
}
I wound up taking the more convoluted approach of adding an associated Bool value to UITableView, then swizzling UITableView to redirect reloadData()
fileprivate let reloadDataCalledOnMainThreadString = NSUUID().uuidString.cString(using: .utf8)!
fileprivate let reloadDataCalledOnMainThreadKey = UnsafeRawPointer(reloadDataCalledOnMainThreadString)
extension UITableView {
var reloadDataCalledOnMainThread: Bool? {
get {
let storedValue = objc_getAssociatedObject(self, reloadDataCalledOnMainThreadKey)
return storedValue as? Bool
}
set {
objc_setAssociatedObject(self, reloadDataCalledOnMainThreadKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
dynamic func _spyReloadData() {
reloadDataCalledOnMainThread = Thread.isMainThread
_spyReloadData()
}
//Then swizzle that with reloadData()
}
Then in the test I updated the cats on the background thread so I could check if they were reloaded on the main thread.
func testReloadDataIsCalledWhenCatsAreUpdated() {
// Checks for presence of another associated property that's set in the swizzled reloadData method
let reloadedPredicate = NSPredicate { [controller] _,_ in
controller.tableView.reloadDataWasCalled
}
expectation(for: reloadedPredicate, evaluatedWith: [:], handler: nil)
// Appends on the background queue to simulate an asynchronous call
DispatchQueue.global(qos: .background).async { [weak controller] in
let cat = Cat(name: "Test", identifier: 1)
controller?.cats.append(cat)
}
// 2 seconds seems excessive but NSPredicates only evaluate once per second
waitForExpectations(timeout: 2, handler: nil)
XCTAssert(controller.tableView.reloadDataCalledOnMainThread!,
"Reload data should be called on the main thread when cats are updated on a background thread")
}
Here is an updated version of the answer provided by Oleh Zayats that I am using in some tests of Combine publishers.
extension DispatchQueue {
func setAsExpectedQueue(isExpected: Bool = true) {
guard isExpected else {
setSpecific(key: .isExpectedQueueKey, value: nil)
return
}
setSpecific(key: .isExpectedQueueKey, value: true)
}
static func isExpectedQueue() -> Bool {
guard let isExpectedQueue = DispatchQueue.getSpecific(key: .isExpectedQueueKey) else {
return false
}
return isExpectedQueue
}
}
extension DispatchSpecificKey where T == Bool {
static let isExpectedQueueKey = DispatchSpecificKey<Bool>()
}
This is an example test using Dispatch and Combine to verify it is working as expected (you can see it fail if you remove the receive(on:) operator).:
final class IsExpectedQueueTests: XCTestCase {
func testIsExpectedQueue() {
DispatchQueue.main.setAsExpectedQueue()
let valueExpectation = expectation(description: "The value was received on the expected queue")
let completionExpectation = expectation(description: "The publisher completed on the expected queue")
defer {
waitForExpectations(timeout: 1)
DispatchQueue.main.setAsExpectedQueue(isExpected: false)
}
DispatchQueue.global().sync {
Just(())
.receive(on: DispatchQueue.main)
.sink { _ in
guard DispatchQueue.isExpectedQueue() else {
return
}
completionExpectation.fulfill()
} receiveValue: { _ in
guard DispatchQueue.isExpectedQueue() else {
return
}
valueExpectation.fulfill()
}.store(in: &cancellables)
}
}
override func tearDown() {
cancellables.removeAll()
super.tearDown()
}
var cancellables = Set<AnyCancellable>()
}

Swift 3 pass data through completion with userID

I'm trying to run a check on Firebase to see if a user exists, then I need to check for specific vales before continuing. I currently have this:
func myFirebaseNetworkDataRequest(finished: () -> Void) {
if let user = FIRAuth.auth()?.currentUser {
self.userUUID = (user.uid)
getUser(userUUID: self.userUUID)
finished()
}
}
In my view did load:
myFirebaseNetworkDataRequest {
// perform further operations here after data is fetched
if AppState.sharedInstance.user == true {
//present 1st view controller
} else {
//present 2nd view controller
}
In my "getUser" function:
func getUser(userUUID: String) {
let userFacebookRef = FIRDatabase.database().reference(withPath: "users").child(userUUID)
//The rest of the Firebase function.
AppState.sharedInstance.user == results.active
//active is = to true
What currently happens is that if presents the 2nd view controller because firebase hasent finished yet. I realize I need a block because firebase is already asnyc but how do I send userUUID through the closure/block?
You can have your closure parameter have an parameter of it's own. Something like,
func myFirebaseNetworkDataRequest(finished: (_ isAuthenticated: Bool) -> Void)
Then in your viewDidLoad, you would make your request...
open override func viewDidLoad() {
self.myFirebaseNetworkDataRequest( (isAuthenticated) -> {
if isAuthenticated {
// Present VC1
} else {
// Present VC 2
}
}
}

Resources