I have this custom implementation of Alamofire:
protocol HTTPProtocol: class {
typealias RequestType
typealias RespondType
func doRequest(requestData: RequestType) -> Self
func completionHandler(block:(Result<RespondType, NSError>) -> Void) -> Self
}
//example of a request:
locationInfo
//Make a request
.doRequest(HTTPLocationInfo.RequestType(coordinate: $0))
//Call back when request finished
.completionHandler { result in
switch result {
case .Success(let info): self.locationInfoRequestSuccess(info)
case .Failure(let error): self.locationInfoRequestFailed(error)
}
}
I want to apply MVVM and RxSwift into my project. However, I can't find a proper way to do this.
What I want to achieve is a ViewModel and a ViewController that can do these things:
class ViewController {
func googleMapDelegate(mapMoveToCoordinate: CLLocationCoordinate2D) {
// Step 1: set new value on `viewModel.newCoordinate` and make a request
}
func handleViewModelCallBack(resultParam: ...*something*) {
// Step 3: subscribeOn `viewModel.locationInfoResult` and do things.
}
}
class ViewModel {
//Result if a wrapper object of Alamofire.
typealias LocationInfoResult = (Result<LocationInfo.Respond, NSError>) -> Void
let newCoordinate = Variable<CLLocationCoordinate2D>(kInvalidCoordinate)
let locationInfoResult: Observable<LocationInfoResult>
init() {
// Step 2: on newCoordinate change, from step 1, request Location Info
// I could not find a solution at this step
// how to make a `completionHandler` set its result on `locationInfoResult`
}
}
Any help is deeply appreciated. Thank you.
You can use RxAlamofire as #Gus said in the comment. But if you are using any library that doesn't support Rx extensions by default you may need to do the conversion by hand.
So for the above code snippet, you can create an observable from the callback handler you had implemented
func getResultsObservable() -> Observable<Result> {
return Observable.create{ (observer) -> Disposable in
locationInfo
//Make a request
.doRequest( .... )
//Call back when request finished
.completionHandler { result in
switch result {
case .Success(let info): observer.on(Event.Next(info))
case .Failure(let error): observer.on(Event.Error(NetworkError()))
}
}
return Disposables.create {
// You can do some cleaning here
}
}
}
Callback handlers are implementation to observer pattern, so mapping it to a custom Observable is a straight forward operation.
A good practice is to cancel the network request in case of disposing, for example this is a complete disposable Post request:
return Observable<Result>.create { (observer) -> Disposable in
let requestReference = Alamofire.request("request url",
method: .post,
parameters: ["par1" : val1, "par2" : val2])
.validate()
.responseJSON { (response) in
switch response.result{
case .success:
observer.onNext(response.map{...})
observer.onCompleted()
case .failure:
observer.onError(NetworkError(message: response.error!.localizedDescription))
}
}
return Disposables.create(with: {
requestReference.cancel()
})
Note: before swift 3 Disposables.create() is replaced with NopDisposable.instance
It doesn't seem like you need to subscribe to newCoordinate so I would just make that a request func.
Then, using the info you get back from Alamofire, just set the value on the locationInfoResult and you will get the new result in the ViewController
class ViewController: UIViewController {
func viewDidLoad() {
super.viewDidLoad()
//subscribe to info changes
viewModel.locationInfoResult
.subscribeNext { info in
//do something with info...
}
}
func googleMapDelegate(mapMoveToCoordinate: CLLocationCoordinate2D) {
viewModel.requestLocationInfo(mapMoveToCoordinate)
}
}
class ViewModel {
let locationInfoResult: Variable<LocationInfoResult?>(nil)
init() {
}
func requestLocationInfo(location: CLLocationCoordinate2D) {
//do Alamofire stuff to get info
//update with the result
locationInfoResult.value = //value from Alamofire
}
}
Related
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()
}
}
Instead of putting network model loading in every controller, I'm trying to abstract it in only one controller ModelLoader
Here is what I came up with so far:
protocol ModelLoaderDelegate: class {
func didFetch(model: Any)
func modelFailedToFetch(errorMessage: String)
}
final class ModelLoader<ModelType> {
let api: API
weak var delegate: ModelLoaderDelegate?
private let client = dependencies.client
init(api: API) {
self.api = api
}
func fetchModel() {
client.performRequest(api: api, decodeTo: ModelType.self, completion: { result in
switch result {
case .success(let value):
self.delegate?.didFetch(model: value)
case .failure(let error):
self.delegate?.modelFailedToFetch(errorMessage: error.localizedDescription)
}
})
}
}
The one thing I'm not able to do until now is to replace Any in the didFetch method of the ModelLoaderDelegate to be a generic-like parameter.
I tried to do it like this:
func didFetch<T>(info: T)
but in the implementer of the delegate:
func didFetch<T>(info: T) {
// I need a concerete type here not a generic
}
Couldn't find another approach.
Instead to do this via delegate, you can make the logic by closures:
final class ModelLoader<ModelType> {
let api: API
private let client = dependencies.client
init(api: API) {
self.api = api
}
func fetchModel<ModelType>(completion: #escaping (Result<ModelType, Error>) -> ()) {
client.performRequest(api: api, decodeTo: ModelType.self, completion: { result in
switch result {
case .success(let value):
completion(.success(value))
case .failure(let error):
completion(.failure(error))
}
})
}
}
I think with this approach will be much easier to handle response :)
I am trying to call a function reloadTable in my HomeViewController from my Task class. But I keep being thrown an
Use of instance member 'reloadTable' on type 'HomeViewController'; did you mean to use a value of type 'HomeViewController' instead?
This is my HomeViewController code:
import UIKit
import Alamofire
import SwiftyJSON
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
func reloadTable() {
self.displayTask.reloadData()
}
}
This is my Tasks class:
import Foundation
import Alamofire
import SwiftyJSON
class Tasks {
static let sharedInstance = Tasks()
var datas: [JSON] = []
func getTaskDetails(){
Alamofire.request(.GET, Data.weeklyEndpoint).validate().responseJSON { response in
switch response.result {
case .Success(let data):
let json = JSON(data)
if let buildings = json.array {
for building in buildings {
if let startDate = building["start_date"].string{
print(startDate)
}
if let tasks = building["tasks"].array{
Tasks.sharedInstance.datas = tasks
HomeViewController.reloadTable()
for task in tasks {
if let taskName = task["task_name"].string {
print(taskName)
}
}
}
}
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
}
}
// for prevent from creating this class object
private init() { }
}
In this case reloadTable() is an instance method. You can't call it by class name, you have to create object for HomeViewController and then you have to call that method by using that object.
But in this situation no need to call the method directly by using HomeViewController object. You can do this in another way by using NSNotification
Using NSNotification :
Add a notification observer for your HomeViewController
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(HomeViewController.reloadTable), name:"ReloadHomeTable", object: nil)
Add the above lines in your viewDidLoad method of HomeViewController
Now replace this line HomeViewController.reloadTable() by NSNotificationCenter.defaultCenter().postNotificationName("ReloadHomeTable", object: nil) in your Task class
As many of the other answers states, you are trying to access a non-static function on a static reference.
I would suggest the use of closures, consider the following;
Create a closure for .Success and the .Failure, this enables you to act accordingly to the data request result. The onError closure is defined as an optional, this means you don't have to implement the onError in the ´getTaskDetails` function call, implement it where you feel you need the error information.
func getTaskDetails(onCompletion: ([Task]) -> (), onError: ((NSError) -> ())? = nil) {
Alamofire.request(.GET, Data.weeklyEndpoint).validate().responseJSON {
response in
switch response.result {
case .Success(let data):
let json = JSON(data)
if let buildings = json.array {
for building in buildings {
if let startDate = building["start_date"].string{
print(startDate)
}
if let tasks = building["tasks"].array{
Tasks.sharedInstance.datas = tasks
onCompletion(tasks)
}
}
}
case .Failure(let error):
print("Request failed with error: \(error)")
onError?(error)
}
}
}
From your HomeViewController:
// Call from wherever you want to reload data.
func loadTasks(){
// With error handling.
// Fetch the tasks and reload data.
Tasks.sharedInstance.getTaskDetails({
tasks in
self.displayTask.reloadData()
}, onError: {
error in
// Handle error, display a message or something.
print(error)
})
// Without error handling.
// Fetch the tasks and reload data.
Tasks.sharedInstance.getTaskDetails({
tasks in
self.displayTask.reloadData()
})
}
Basics
Closures
Make class Task as Struct, make function getTaskDetails as static and try to call function getTaskDetails() in HomeViewController. In result this of this function use realoadTable()
In HomeViewController you have defined reloadTable() as instance method not the class function.You should call reloadTable() by making instance of HomeViewController as HomeViewController(). Replace
HomeViewController.showLeaderboard()
HomeViewController().showLeaderboard()
Hope it helps. Happy Coding.
I am experimenting with generics in Swift and I am attempting to push it to its limits.
In my application I have a super simple API wrapper around Alamofire. The structure is like so:
API -> Request -> Alamofire request
Here is some generic code that I threw into a playground to test some concepts. Here is what I have so far:
protocol SomeProtocol {
var cheese: String { get }
init()
}
class Something: SomeProtocol {
required init() { }
var cheese: String {
return "wiz"
}
}
class API {
class func performRequest<T: SomeProtocol>(completion: (T?, NSError) -> Void) {
// This code is irrelevant, just satisfying the completion param
let test = T()
let error = NSError(domain: "Pizza", code: 1, userInfo: nil)
completion(test, error)
}
}
func test() {
API.performRequest<Something> { item, error in
}
}
Calling the function gives the error:
"Cannot explicitly specialize a generic function"
****** UPDATE ******
As per the answer below, removing the typical <> generic type specifier and instead adding the expected type to the completion params solves the issue. Just a quick example:
func test() {
API.performRequest { (item: Something?, error) in
}
}
Additionally, I have discovered that making the API wrapper class a generic class solves the issue like so:
protocol SomeProtocol {
var pizza: String { get }
}
class SomeObject: SomeProtocol {
var pizza: String { return "pie" }
}
class API<T: SomeProtocol> {
class func performRequest(completion: (T?, NSError?) -> Void) {
}
}
func test() {
API<SomeObject>.performRequest { item, error in
// Do something with item, which has a type of SomeObject
}
}
Either way, the end goal is accomplished. We have a single generic method that will perform a set of tasks and return, via completion closure, the object based on the type passed in with each use.
The way generics work is they allow a function to use unspecialized variables inside of its implementation. One can add functionality to these variables by specifying that the variables must conform to a given protocol (this is done within the declaration). The result is a function that can be used as a template for many types. However, when the function is called in the code itself, the compiler must be able to specialize and apply types to the generics.
In your code above, try replacing
func test() {
API.performRequest<Something> { item, error in
}
}
with
func test() {
API.performRequest { (item: Something?, error) in
}
}
this lets the compiler know which type it must apply to the function without explicitly specifying. The error message you received should now make more sense.
Here is what i did using alamofire and alamofire object mapper:
Step 1: Create modal classes that conforms to Mappable protocols.
class StoreListingModal: Mappable {
var store: [StoreModal]?
var status: String?
required init?(_ map: Map){
}
func mapping(map: Map) {
store <- map["result"]
status <- map["status"]
}
}
Step 2: Create a fetch request using the generic types:
func getDataFromNetwork<T:Mappable>(urlString: String, completion: (T?, NSError?) -> Void) {
Alamofire.request(.GET, urlString).responseObject { (response: Response<T, NSError>) in
guard response.result.isSuccess else{
print("Error while fetching: \(response.result.error)")
completion(nil, response.result.error)
return
}
if let responseObject = response.result.value{
print(responseObject)
completion(responseObject, nil)
}
}
}
Step 3: Now all you need is to call this fetch function. This can be done like this:
self.getDataFromNetwork("your url string") { (userResponse:StoreListingModal?, error) in
}
You will not only get your response object but it will also be mapped to your modal class.
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) { }
}