Matt Le Fleur
Matt Le Fleur

Reputation: 2856

Is there a way of using a completion handler passed through as an argument to detect when a long request is completed

I'm having trouble understanding how to use a closure to handle completed events when passing in a function as a parameter.

Here's a very contrived example:

class MessageService {
    func sendMessage(s: String) {
        print(s)
    }
    
    var messenger: Messenger {
        createMessenger(completion: sendMessage(s:))
    }
}

func createMessenger(completion: @escaping (String) -> Void) -> Messenger {
    return Messenger { completion("This is a hardcoded message.") }
}

struct Messenger {
    let sendMessage: () -> Void
    init(sendMessage: @escaping () -> Void) {
        self.sendMessage = sendMessage
    }
}

let service = MessageService()
let messenger = service.messenger
messenger.sendMessage()

I want to find out when sendMessage is finished (if for example it was performing something like a network request), so is there a way of having a completion handler for sendMessage so that I could write something along the lines of:

messenger.sendMessage {
    print("I finished sending a message!")
}

I've tried adding a completion handler like this in the service class:

func sendMessage(s: String, completion: @escaping () -> Void) {
    MessageRequest(with: s) {
        completion()
    }
}

But things started getting very confusing when I'm trying to use the createMessenger method, because the above function has some crazy type of (String, () -> ()) -> () which I don't know how to handle. Any help would be greatly appreciated, thanks.

Upvotes: 0

Views: 236

Answers (1)

New Dev
New Dev

Reputation: 49590

So, it sounds like what you want is an arbitrary Messenger type, whose creator tell it what action to do, and once the action is done, it invokes its caller's completion handler.

It helps if you use typealias with descriptive names to keep track of all the closures. And if you don't mind, I'll name it more generically as Agent:

struct Agent {

   typealias Completion = () -> Void
   typealias Action = (Completion) -> Void

   private let action: Action

   static func create(action: @escaping Action) -> Agent {
      Agent(action: action)
   }

   func execute(_ completion: @escaping Completion) {
      action(completion)
   }
}

So, Agent can be created with an arbitrary action that accepts a completion handler to signal when it's done:

let agent = Agent.create { completion in
   print("started executing action")
   DispatchQueue.main.asyncAfter(deadline: .now() + 2) { completion() }
}
agent.execute { print("done") }

Now, you can adapt it to your MessengerService class:

class MessageService {
   func sendMessage(s: String) {
      print(s)
   }
    
   var messenger: Agent {
      Agent.create { completion in 
         sendMessage("This is a hardcoded message.") 
         completion()
      }
   }
}

Upvotes: 1

Related Questions