callback function from caller to callee in swift - ios

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

Related

Where to trigger Loading in Clean Architeture Swift

Where is the correct place I should put the code that would trigger a loading to display in my app.
It is correct to do is on view? since it is displaying something on screen, so it fits as a UI logic
class ViewController: UIViewController {
func fetchData() {
showLoading()
interactor?.fetchData()
}
}
or on interactor? since it's a business logic. something like, everytime a request is made, we should display a loading. View only knows how to construct a loading, not when to display it.
class Interactor {
func fetchData() {
presenter?.presentLoading(true)
worker?.fetchData() { (data) [weak self] in
presenter?.presentLoading(false)
self?.presenter?.presentData(data)
}
}
}
same question applies to MVVM and MVP.
it is totally up to you . i am showing loading using an Observable .
in my viewModel there is an enum called action :
enum action {
case success(count:Int)
case deleteSuccess
case loading
case error
}
and an Observable of action type :
var actionsObservable = PublishSubject<action>()
then , before fetching data i call onNext method of actionObservable(loading)
and subscribing to it in viewController :
vm.actionsObserver
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (action) in
switch action {
case .success(let count):
if(count == 0){
self.noItemLabel.isHidden = false
}
else{
self.noItemLabel.isHidden = true
}
self.refreshControl.endRefreshing()
self.removeSpinner()
case .loading:
self.showSpinner(onView : self.view)
case .error:
self.removeSpinner()
}
}, onError: { (e) in
print(e)
}).disposed(by: disposeBag)
You can use the delegate or completion handler to the update the UI from view model.
class PaymentViewController: UIViewController {
// for UI update
func showLoading() {
self.showLoader()
}
func stopLoading() {
self.removeLoader()
}
}
protocol PaymentOptionsDelegate : AnyObject {
func showLoading()
func stopLoading()
}
class PaymentOptionsViewModel {
weak var delegate : PaymentOptionsDelegate?
func fetchData() {
delegate?.showLoading()
delegate?.stopLoading()
}
}

iOS - Unit Testing asynchronous private function in Presenter of MVP

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

Sending a function to another function as a parameter

I want to send a func as a parameter to another func but I know how to do it only in case the func being sent has an input and an output parameters:
aFunc (sentFunc: Int -> String) {
...
}
But I want to achieve something like the following code where the function being sent does not have parameters:
func firstFunc(<i want to declare paramenter for a function here that itself does not have any parameters>) {
if (servicedCities != nil) {
<execute sent fuction>
} else {
objectMapper().getServicedCitiesifAvailable()
<execute sent fuction>
}
}
the func calling the above func should look like
func secondFunc() {
func myNestedFunction() {
....
}
....
firstFunc(myNestedFunction())
....
}
func firstFunc(passedFunc:()->()) {
passedFunc()
}
func secondFunc() {
func myNestedFunction() {
print("hi")
}
firstFunc(myNestedFunction)
}
secondFunc()
will ouput
hi
in the context of the firstFunc call.
Note that you have to omit the () in the last line after the myNestedFunction because you dont want to call the method but pass the method itself.
func firstFunc(sentFunc: () -> ()) {
sentFunc()
}
or
func firstFunc(sentFunc: Void -> Void) {
sentFunc()
}
I think you are looking for Swift closures.
Check this Swift closures and functions
func someFunc(paramFunc: () -> ()) {
paramFunc()
}
...
self.someFunc { () -> () in
//Do some code here;
}

block in Swift : return error " is not convertible to "

I made a mistake but I cannot see how to solve it. I would like to load all the assets from GameScene and send a Bool in a completion method. I use typealias : should it be renamed twice for the two files (gameScene and gameController)?
Then I have got an error on this line GameScene.loadSceneAssetsWithCompletionHandler{ :
((Bool) -> Void) is not convertible to 'GameScene'
Here is the code :
//gameController:
typealias OnComplete = (Bool) -> ()
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler{ (success:Bool)->Void in
println("2/ yes")
return
}
//gameScene : rewrite typealias?
typealias OnComplete = (Bool) -> ()
func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
self.loadSceneAssets()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
println("1/ yes")
completion(true)
})//main
})//global
}
I read some threads that said to add a "return", but it does not solve the error here.
Thanks
It's almost working, but you've got a couple things going wrong here. First of all, you can't redeclare a typealias. Second of all you're calling loadSceneAssetsWithCompletionHandler as a class function when it's set up as an instance function. Note changes:
typealias OnComplete = (Bool) -> ()
class GameController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler { success in
println("2/ yes")
return
}
}
}
class GameScene: UIViewController {
func loadSceneAssets() {
}
class func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let gameScene = GameScene()
gameScene.loadSceneAssets()
dispatch_async(dispatch_get_main_queue()) {
println("1/ yes")
completion(true)
}
}
}
}

Swift: Completion closures with instances that conforms to protocol

I try to use Swift's closures like the completion blocks in ObjC when calling async requests.
This seems to works. I'm using a protocol for my model classes and in conjunction with an Array I get problems. The relevant code:
//ModelProtocol.swift
protocol ModelProtocol {
// all my models should implement `all`
class func all(completion: ((models: Array<ModelProtocol>) -> Void) )
}
//Person.swift
// calls the HTTP request and should return all Person-Objects in `completion`
class func all(completion: ((models: Array<ModelProtocol>) -> Void) ) {
let request = HTTPRequest()
request.getAll() { (data:NSArray) in
var persons:Person[] = //... `data` is the result from the HTTP GET request and will be parsed here - this is ok
completion(models: persons)
}
}
//HTTPRequest.swift
func getAll(completion: ((data: NSArray) -> Void) ) {
//... some setup would be here
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
var jsonResponse: NSArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSArray
completion(data: jsonResponse)
}
}
//ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// use this whole code here - receive all Persons and show in a tableView or something like this
Person.all( { (models: Array<ModelProtocol>) in
println(models) //CRASH here
})
}
When I change the protocol definition of function all (and so the function all in Person.swift) to class func all(completion: ((models: Person[]) -> Void) ) it is working.
But I want to use Array<ModelProtocol> to use polymorphismus and only use classes that conforms to ModelProtocol, that can be Person or House or whatever.
I think I'm missing something important or basic here. I hope my problem is clear enough.
Edit:
In ViewController.swift the execution of the App stops at the println(models) statement with the message EXC_BAD_ACCESS.
Maybe this will do what you want:
protocol ModelProtocol {
// all my models should implement `all`
class func all(completion: ((models: Array<Self>) -> Void) )
}
class func all(completion: ((models: Array<Person>) -> Void) ) {
let request = HTTPRequest()
request.getAll() { (data:NSArray) in
var persons:Person[] = //... `data` is the result from the HTTP GET request and will be parsed here - this is ok
completion(models: persons)
}
}

Resources