iOS - Unit Testing asynchronous private function in Presenter of MVP - ios

Hello I'm trying to unit testing a private function which located in Presenter
This is my Presenter Codes and I'm using Networking Singleton Object APIService
class MyPresenter {
weak var vc: MyProtocol?
func attachView(vc: MyProtocol?) {
self.vc = vc
}
func request(_ id: String) {
if id.count == 0 {
vc?.showIDEmptyAlert()
return
}
fetch(id)
}
private func fetch(_ id:String) {
DispatchQueue.global.async {
APIService.shared.fetch(id) { (data, err) in
if let err = err {
self.vc?.showErrorAlert()
return
}
self.vc?.update(data)
}
}
}
}
and this is my ViewController codes
class MyViewController: UIViewController, MyProtocol {
private var presenter: MyPresenter = MyPresenter()
override func viewDidLoad() {
super.viewDidLoad()
presenter.attachView(vc: self)
}
func showIDEmptyAlert() {
self.present ..
}
func showErrorAlert() {
self.present ..
}
func update(data: String) {
self.label.text = data
}
#IBAction func handleRegisterButton(_ sender: UIButton) {
guard let id = idTextField.text else { return }
presenter.request(id)
}
}
These are my Presenter and View. And I wrote Test Code like this
First, I made Mock PassiveView Like this
class MyViewMock: MyProtocol {
private (set) var showEmptyIdAlertHasBeenCalled = false
private (set) var showErrorAlertHasBeenCalled = false
private (set) var updateHasBeenCalled = false
func showEmptyIdAlert() {
showEmptyIdAlertHasBeenCalled = true
}
func showErrorAlert() {
showErrorAlertHasBeenCalled = true
}
func update(data: String) {
updateHasBeenCalled = true
}
}
So I expected that if I could test Presenter's request(_:) methods with valid id and invalid
but because request(_:) didn't get handler parameter and APIService.shared.fetch is asynchronous, I couldn't get
correct result by calling request(_:). (Always false)
How can I test this kind of Presenter?

In terms ofXCTests, there is the XCTestExpectation class to test asynchronous functions.
But there is an issue in your approach of testing the Presenter. You should use mock for your network service and stub it with expected arguments. It doesn't make sense to call the actual service. Unit tests are the fastest kind of tests. Private methods are a part of black-box which you should not care about its internal structure. Test public interfaces but don't test private methods.
If you will try to mock and stub APIService, then you notice it's impossible to do if it's a singleton. You'll end up with injecting service as a dependency into Presenter for the better testability.
If the service will be mocked and the stub will be used, then there is no need in using XCTestExpectation, because there won't be any asynchronous code.

To test asynchronous methods, you must use XCTestExpectation to make your test wait for the asynchronous operation to complete.
it's a test asynchronous method example
// Asynchronous test: success fast, failure slow
func testValidCallToiTunesGetsHTTPStatusCode200() {
// given
let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
// 1
let promise = expectation(description: "Status code: 200")
// when
let dataTask = sessionUnderTest.dataTask(with: url!) { data, response, error in
// then
if let error = error {
XCTFail("Error: \(error.localizedDescription)")
return
} else if let statusCode = (response as? HTTPURLResponse)?.statusCode {
if statusCode == 200 {
// 2
promise.fulfill()
} else {
XCTFail("Status code: \(statusCode)")
}
}
}
dataTask.resume()
// 3
waitForExpectations(timeout: 5, handler: nil)
}
You can read more about Unit test from here

Related

Unit test API Call with internal switch case

I am trying to unit test a class that has different modes of setup.
class Controller{
enum Mode{
case listing
case pages(String?)
}
private (set) var mode : Mode = .listing
private (set) var models = [Model]()
init() {
...
}
init(id : String) {
mode = .pages(id)
}
func fetchInfo(){
switch mode{
case .listing:
ApiManager.firstNetworkCall(){ (json, error) in ...
setupModel()
}
case .pages(let id):
ApiManager.secondNetworkCall(id : id){ (json, error) in ...
setupModel()
}
}
}
}
Both of these will update the models array with different quantity of data.
What I have right now:
var controller : Controller!
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
controller = Controller()
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
controller = nil
try super.tearDownWithError()
}
func testDefaultListingMode() throws {
switch controller.mode{
case .listing:
XCTAssertTrue(true)
default:
XCTAssertFalse(false)
}
}
func testAPISetup() throws {
controller.fetchInfo()
//now what?
}
This checks if the mode is correct but I am trying to go one step further and check if the correct number of items is setup depending on the mode. And want to call the fetchInfo() method directly from the XCTestCase and just validate the model count.
All the tutorials and guides I have seen just talk about faking the behaviour with a URLSession. But the API call is dependent on the mode that happens as an internal check inside the fetchInfo method and is the only method exposed to other classes. I would simply like to test the method (in case something breaks inside that method causing a bug).
How do I go about doing that? I can't figure out how to complete the testAPISetup() method.
What I had for networking:
class NetworkingManager{
static var alamoFireManager = Session.default
static func POST(...., completion : ()->()) {
sendRequest(....., completion : completion)
}
private static func sendRequest(...., completion : ()->()) {
let request = alamoFireManager.request(.....)
request.responseJSON{
completion()
}
}
}
class APIManager{
static func firstNetworkCall(completion : ()->()){
NetworkingManager.POST(..., completion : completion)
}
}
I had to change the above and removed all mentions of static and singletons. I decided to go ahead with using class inheritance. I tried to avoid it and use protocols but it frankly was quite easier to use classes!
class NetworkingManager{
private (set) var sessionManager: Session
init(config : URLSessionConfiguration = .default){
config.timeoutIntervalForResource = 8.0
config.timeoutIntervalForRequest = 8.0
self.sessionManager = Session(configuration: config)
}
func request(...) {
//hit alamofire
}
}
class APIManager : NetworkingManager{
override init(config: URLSessionConfiguration = .default) {
super.init(config: config)
}
//other methods
...
}
class Controller{
private let apiManager : APIManager
init(manager : APIManager = APIManager()){
self.apiManager = manager
}
}
And in my test class:
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
let config = URLSessionConfiguration.ephemeral
apiManager = APIManager(config : config)
controller = Controller(manager : apiManager)
}
func testApiCalled() throws{
controller.fetchNecessaryInfo()
//had to add one second delay as alamofire adds the request on another queue. Wasn't able to put it on main queue.
sleep(1)
let promise = expectation(description: "Check request called")
apiManager.sessionManager.session.getAllTasks { (taskArray) in
if taskArray.count > 1{
XCTFail("Multiple requests when there should be only one")
}
if let task = taskArray.first, let request = task.currentRequest{
if let string = request.url?.absoluteString{
XCTAssert(...)
}else{
XCTFail("Incorrect URL")
}
}else{
XCTFail("Somehow no task exists. So this is an error")
}
promise.fulfill()
}
wait(for: [promise], timeout: 1.0)
}
I couldn't figure out any other way without having to instantiate an object for APIManager, so had to refactor!

How can I assert a delegate is called in my unit test

I would like to asset that the correct delegate method is called depending on the result of a check in my presenter.
Having mocked out my IdentityProvider to return true, how would I write a test to assert delegate?.userIsAuthenticated() is called?
import Foundation
import InjectStory
protocol StartPresenterDelegate: class {
func userIsAuthenticated()
func userNeedsToAuthenticate()
}
class StartPresenter {
weak var delegate: StartPresenterDelegate?
weak var view: StartViewInterface!
private lazy var identityProvider = Dependencies.identityProvider.inject()
init(view: StartViewInterface) {
self.view = view
}
private func checkUserAuthState() {
if identityProvider.isAuthorized() {
delegate?.userIsAuthenticated()
} else {
delegate?.userNeedsToAuthenticate()
}
}
}
extension StartPresenter: StartPresentation {
func onViewDidLoad() {
checkUserAuthState()
}
}
extension StartPresenter {
struct Dependencies {
static let identityProvider = Injection<IdentityProviderProtocol>(IdentityProvider.shared)
}
}
You need to do some trick. Create MockDelegateClass for your protocol StartPresenterDelegate
example:
class MockDelegate: StartPresenterDelegate {
var isUserIsAuthenticatedCalled = false
var isUserNeedsToAuthenticateCalled = false
func userIsAuthenticated() {
isUserIsAuthenticatedCalled = true
}
func userNeedsToAuthenticate() {
isUserNeedsToAuthenticateCalled = true
}
}
then in your test try to do something like that:
func test_MyTest() {
// init your class StartPresenter that you wanna test
var presenter = StartPresenter(...)
var mockDelegate = MockDelegate()
presenter.delegate = mockDelegate
presenter.onViewDidLoad()
XCTAssertTrue(mockDelegate.isUserIsAuthenticatedCalled)
XCTAssertFalse(mockDelegate.isUserNeedsToAuthenticateCalled)
// or change isUserIsAuthenticatedCalled and isUserNeedsToAuthenticateCalled if you expect another states
}
For different states you need different tests, for you it will be the easiest way to test delegate calling.
You have to mock the StartPresenterDelegate as follows too.
class MockStartPresenterDelegate: StartPresenterDelegate {
var userIsAuthenticated_wasCalled = false
func userIsAuthenticated() {
userIsAuthenticated_wasCalled = true
}
}
Inject MockStartPresenterDelegate as the delegate and check that userIsAuthenticated_wasCalled is true
In this way you can confirm the unit test for delegates in Swift apps

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>()
}

callback function from caller to callee in swift

I have a view controller and a class for doing the bits to call the services and get the data from server.
The ViewController code is below,
class ViewController : UIViewController
{
override func viewDidLoad() {
let parser = Parser()
parser.connectServer("abc URL" , ..... <gotDataFromServer> ..... )
}
func gotDataFromServer(response:String)
{
...... Do our things here .......
}
}
and the parser code is below,
class Parser
{
func connectServer(apiURL:String,...<call back function name>...)
{
let manager = RequestOperationManager.sharedManager()
manager.GET(apiURL ,
parameters: nil,
success: { (operation,responseObject) ->Void in
.....<Call back the function which is passed in parameter> ....
},
failure: { (operation , error) in
print ("error occurred")
})
}
}
Now in the above sample code i want to pass call back function "gotDataFromServer" as a parameter and when the inner function get the response from the server then i want to call this function back.
Can anyone please help.
You can use delegates to achieve that. Try out following code
class ViewController : UIViewController, DataDelegate
{
override func viewDidLoad() {
let parser = Parser()
parser.delegate = self
parser.connectServer("abc URL" , ..... <gotDataFromServer> ..... )
}
func gotDataFromServer(response:String)
{
...... Do our things here .......
}
}
And add protocol in parser as follows
protocol DataDelegate {
func gotDataFromServer(response:String)
}
class Parser
{
var delegate : DataDelegate!
func connectServer(apiURL:String,...<call back function name>...)
{
let manager = RequestOperationManager.sharedManager()
manager.GET(apiURL ,
parameters: nil,
success: { (operation,responseObject) ->Void in
delegate.gotDataFromServer("") //parameter is your data
},
failure: { (operation , error) in
print ("error occurred")
})
}
}
Here's an example how you can do it using closure
class Parser {
func connectServer(apiURL: String, completion: String -> Void) {
// ... make call, get data
// share the results via completion closure
completion("data")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
let parser = Parser()
// Option #1
parser.connectServer("mybackend.com/connect") {
print("received data \($0)")
}
// Option #2 is the same as Option #1 but a bit longer
parser.connectServer("mybackend.com/connect") { (data) -> Void in
print("received data \(data)")
}
// Option #3 - Or if you have a separate funciton
// be careful with retain cycle
parser.connectServer("mybackend.com/connect", completion: gotDataFromServer)
}
func gotDataFromServer(response:String) { }
}

Resources