MVC Structure in swift - ios

Do we need to create the Web services singleton class file out of the MVC folder, while following MVC Structure or it stays in Model folder together with the Mapper classes

It is a good practice to avoid using singletons. Although they are fast to create, they are not very scalable, difficult to test, etc, Try to use dependency injections instead.
Singletons are easier to create, easier to use, but it fix you to using specific object that is difficult to replace, if needed. For example you have Store class that supplying name of current user to your VC and it had NetworkClient that fetching User from the network. You can declare both of them as Singletons. It means that any VC had to use same Store.sharedInstance, and store had to use NetworkClient .sharedInstance. Imagine that you want to create UnitTests. As your Store depends on sharedInstance of Client you can't test them separately. It is difficult to test without network, or with timeouts etc. And who's responsible if test failed. Store itself? Network client? Mapper?
In Dependecy Injection pattern instead of using singular instance of store and client, you creating variable in AppDelegate for example, an provide them to all instances you need. Store instead of using shared instance of network client, using some instance that conforms to protocol. Developer can choose what client dependency Store had to use, inject appropriate depends on situation. Usually injection happening during initialisation. In case of tests you can supply you classes with small predictable mocks.
protocol UserStoring: class {
var userName: String { get }
}
final class Store: UserStoring {
private var client: NetworkClient
init (client: NetworkClient) {
self.client = client
}
private var cachedUser: User? = nil
var userName: String {
guard let user = cachedUser else {
client.fetchUser(userId: "current") { userFromClient in
self.cachedUser = userFromClient
}
return " ... "
}
return user.name
}
}
protocol DataFetching {
func fetchUser(userId: String, handler: #escaping (User)->() )
}
final class NetworkClient: DataFetching {
private var session: URLSession
private var baseURL: URL
private var mapper: Mapper
init(session: URLSession, baseURL: URLSession, mapper: Mapper) {
self.session = session
self.baseURL = baseURL
self.mapper = mapper
}
func fetchUser(userId: String, handler: #escaping (User)->() ) {
session.dataTask(with: baseURL.appendingPathComponent("user/\(userId)")) { (data, response, error) in
let user = self.mapper.map(data)
handler(user)
}
}
}
You can even to create a singleton that will keep all shared services in one place, but as you have UIApplication singleton, so you can utilise it: UIApplication.sharedApplication.dependencyService and initialise it in didFinishLaunch.
protocol DependencyServiceSharing {
var client: DataFetching { get }
var mapper: DataMapping { get }
var store: UserStoring { get }
}
class DependencyService: DependencyServiceSharing {
let client: DataFetching
lazy var mapper: DataMapping = Mapper()
lazy var store: UserStoring = Store(client: client)
init(basePath: String) {
client = NetworkClient(session: URLSession.shared, baseURL: URL(string: basePath) , mapper: self.mapper)
}
}
protocol DependencyServiceKeeping: class {
var dependencyService: DependencyServiceSharing? { get }
}
extension UIApplication: DependencyServiceKeeping {
var dependencyService: DependencyServiceSharing? {
return (delegate as? DependencyServiceKeeping)?.dependencyService
}
}
class AppDelegate: UIResponder, UIApplicationDelegate, DependencyServiceKeeping {
var dependencyService: DependencyServiceSharing?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
dependencyService = DependencyService(basePath: "https://google.com")
...

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!

query regarding mocking singleton in swift ,ios using xctest?

this is not a question regarding that should we use singleton or not. but rather mocking singleton related.
this is just a sample example, as i was reading about mocking singleton is tough. so i thought let me give a try.
i am able to mock it but not sure is this a correct approach ?
protocol APIManagerProtocol {
static var sharedManager: APIManagerProtocol {get set}
func doThis()
}
class APIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = APIManager()
private init() {
}
func doThis() {
}
}
class ViewController: UIViewController {
private var apiManager: APIManagerProtocol?
override func viewDidLoad() {
}
convenience init(_ apimanager: APIManagerProtocol){
self.init()
apiManager = apimanager
}
func DoSomeRandomStuff(){
apiManager?.doThis()
}
}
import Foundation
#testable import SingleTonUnitTesting
class MockAPIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = MockAPIManager()
var isdoThisCalled = false
func doThis(){
isdoThisCalled = true
}
private init(){
}
}
class ViewControllerTests: XCTestCase {
var sut: ViewController?
var mockAPIManager: MockAPIManager?
override func setUp() {
mockAPIManager = MockAPIManager.sharedManager as? MockAPIManager
sut = ViewController(mockAPIManager!)
}
func test_viewController_doSomeRandomStuffs(){
sut?.DoSomeRandomStuff()
XCTAssertTrue(mockAPIManager!.isdoThisCalled)
}
override func tearDown() {
sut = nil
mockAPIManager = nil
}
}
The basic idea is right: Avoid repeated references to the singleton directly throughout the code, but rather inject object that conforms to the protocol.
What’s not quite right is that you are testing something internal to the MockAPIManager class. The mock is only there to serve a broader goal, namely to test your business logic (without external dependencies). So, ideally, you should be testing something that is exposed by APIManagerProtocol (or some logical result of that).
So, let’s make this concrete: For example, let’s assume your API had some method to retrieve the age of a user from a web service:
public protocol APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void)
}
(Note, by the way, that the static singleton method doesn’t belong in the protocol. It’s an implementation detail of the API manager, not part of the protocol. No controllers that get a manager injected will ever need to call shared/sharedManager themselves.)
And lets assume that your view controller (or perhaps better, its view model/presenter) had a method to retrieve the age and create an appropriate message to be shown in the UI:
func buildAgeMessage(for userid: String, completion: #escaping (String) -> Void) {
apiManager?.fetchAge(for: userid) { result in
switch result {
case .failure:
completion("Error retrieving age.")
case .success(let age):
completion("The user is \(age) years old.")
}
}
}
The API manager mock would then implement the method:
class MockAPIManager: APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void) {
switch userid {
case "123":
completion(.success(42))
default:
completion(.failure(APIManagerError.notFound))
}
}
}
Then you could test the logic of building this string to be shown in your UI, using the mocked API rather than the actual network service:
class ViewControllerTests: XCTestCase {
var viewController: ViewController?
override func setUp() {
viewController = ViewController(MockAPIManager())
}
func testSuccessfulAgeMessage() {
let e = expectation(description: "testSuccessfulAgeMessage")
viewController?.buildAgeMessage(for: "123") { string in
XCTAssertEqual(string, "The user is 42 years old.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
func testFailureAgeMessage() {
let e = expectation(description: "testFailureAgeMessage")
viewController?.buildAgeMessage(for: "xyz") { string in
XCTAssertEqual(string, "Error retrieving age.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
}
i was reading about mocking singleton is tough
The notion is that if you have these APIManager.shared references sprinkled throughout your code, it’s harder to swap them out with the mock object. Injecting solves this problem.
Then, again, if you’ve now injected this APIManager instance everywhere to facilitate mocking and have eliminate all of these shared references, it begs the question that you wanted to avoid, namely why use a singleton anymore?

Property injection of generic class in controller

I built a state management in my ios applications similar to redux in javascript. It work and it's nice to work with, but i want to abstract and decouple some part in a framework for reusability, testing and sharing.
To add some context, the main idea is to create a class store with a getState and dispatch methods. A struct State defined by the user is passed at the store initialization.
/* Store signature: class MyStore<State>: MyStoreProcotol */
let store = MyStore(state: ApplicationState())`
Once the store is initialized, i'm trying to inject it in any class (UIViewController for example, but not mandatory) that conform to ConnectionProtocol.
In the code below, I pass the controller and the store to a function injectStoreInView. I'm trying to inject the store in the controller from this function, func injectStoreInView<State>(viewController: UIViewController, store: MyStore<State>)
I've try several approaches with generics, whithout success. I have read on "type erasure" and follow tutorials, but i'm not sure if it help for this case and how to apply it. I have troubles to handle the type of the parameter store of injectStoreInView, and can't assign it later in the controller controller.connection.store.
This is the code, where a lot of parts has been removed to illustrate the problem. (This code does not work.)
Framework part:
import UIKit
/* store */
protocol MyStoreProcotol {
associatedtype State
func getState() -> State
}
class MyStore<State>: MyStoreProcotol {
var state: State
init(state: State) {
self.state = state
}
func getState() -> State {
return self.state
}
}
/* connection */
protocol ConnectionProtocol {
associatedtype State
var connection: Connection<State> { get }
}
class Connection<State> {
var store: MyStore<State>? = nil
}
func injectStoreInView<State>(
viewController: UIViewController,
store: MyStore<State>
) {
if let controller = viewController /* as ConnectionProtocol */ {
controller.connection.store = store
print("yeah !")
}
}
In the application part:
struct ApplicationState {
var counter = 0
}
class StartViewConnector: UIViewController, ConnectionProtocol {
let connection = Connection<ApplicationState>()
}
func appDelegatedidFinishLaunchingWithOptions() -> Bool {
let store = MyStore(state: ApplicationState())
let controller = StartViewConnector() // connector can be any class who conform to ConnectionProtocol, StartViewConnector for test but self.window?.rootViewController in iOS App
injectStoreInView(viewController: controller, store: store)
return true
}
appDelegatedidFinishLaunchingWithOptions()
Following the advice of #Brandon about associatedTypes and protocolInjection solved the problem. The link he provided in the comment was really helpful (How to create generic protocols in Swift?).
This code works:
Framework:
import UIKit
/* store */
protocol MyStoreProcotol {
associatedtype State
func getState() -> State
}
class MyStore<State>: MyStoreProcotol {
var state: State
init(state: State) {
self.state = state
}
func getState() -> State {
return self.state
}
}
/* connection */
protocol Connectable {
associatedtype Store
var connection: Connection<Store> { get }
}
protocol ConnectionProtocol {
associatedtype Store
var store: Store? { get set }
}
class Connection<Store>: ConnectionProtocol {
var store: Store? = nil
}
func injectStoreInView
<
Store: MyStoreProcotol,
Controller: Connectable
>
(
viewController: Controller,
store: Store
) where Controller.Store == Store
{
viewController.connection.store = store
}
Usage in the applications:
struct ApplicationState {
var counter = 0
}
class StartViewConnector: UIViewController, Connectable {
let connection = Connection<MyStore<ApplicationState>>()
}
func appDelegatedidFinishLaunchingWithOptions() -> Bool {
let store = MyStore(state: ApplicationState())
let connector = StartViewConnector()
injectStoreInView(viewController: connector, store: store)
// Now the store is properly injected, connector.connection.store = store
return true
}
appDelegatedidFinishLaunchingWithOptions()

What is best practice for global variables and functions in Swift?

I coding an app with several (15-25 different swigft files one for each view.
Some variables and functions I will use in every viewcontroller.
What would be best practice to enable code reusage?
For instance I need to communicate with a server in which the first request is for an access token, this request I imagine could be a global function setting a global variable (access token). And then using it for the more specific requests.
I started placing a lot of global constants in appdelegate file, in a Struct is there a problem with this?
LibraryAPI.swift
import UIKit
import CoreData
class LibraryAPI: NSObject
{
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
private var loginD: LoginDetails
private var isOnline: Bool
class var sharedInstance: LibraryAPI
{
struct Singleton
{
static let instance = LibraryAPI()
}
return Singleton.instance
}
override init()
{
super.init()
}
func getIsOnline() -> Bool
{
return isOnline
}
func setIsOnline(onlineStatus: Bool)
{
isOnline = onlineStatus
}
func getLoginDetails() -> LoginDetails
{
return loginD
}
func setLoginDetails(logindet: LoginDetails)
{
loginD = logindet
}
// Execute the fetch request, and cast the results to an array of objects
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LoginDetails] {
setLoginDetails(fetchResults[0])
}
}
You should avoid using global variables.
Depending on what you have / what you need to do, either you can :
Create a class and make an instance on your first call. Then, you can pass the object through your views (prepareForSegue). But that can still be repetitive to implement everytime ;
Create a singleton class that will be instantiate only once and accessible from everywhere (singleton are considered as a bad practice by some);
Use the NSUserDefaults to store String ;
Save your data somehow (CoreData, ...).
You can do like this
User.swift
import Foundation
import UIKit
class User: NSObject {
var name: String = ""
func getName() -> String{
name = "Nurdin"
return name
}
}
ViewController.swift
import Foundation
import UIKit
let instanceOfUser = User()
println(instanceOfUser.getName()) // return Nurdin

Swift Passing data from appDelegate to another class

I need to pass a variable from the AppDelegate to another class that I have created to hold global variables of the project and I'm not able to find a way to make it work.
This is the code in the AppDelegate:
func application(application: UIApplication!, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData!) {
println("Device's token is: \(deviceToken)")
//Global Variables Class Instance
let globals:Globals = Globals()
globals.setDeviceToken("test1") //method1 not working
globals.deviceToken = "test2" //method2 not working
}
This is my Globals Class:
public class Globals {
var deviceToken = String()
init() {
//nothing
}
func setDeviceToken(s:String){
deviceToken = s
}
func getDeviceToken() -> String {
return deviceToken
}
}
If i try to print the value, from other files of the project, I'm not able to get anything, just an empty string.
class ViewController: UIViewController {
//Global Variables Class Instance
let globals:Globals = Globals()
override func viewDidLoad() {
println(globals.getDeviceToken()) //return empty string
println(globals.deviceToken) //return empty string
There are several patterns you can use to achieve what you want
You could access the AppDelegate through the UIApplication:
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
let deviceToken = delegate.deviceToken
Look into singletons. A quick google search for 'Swift singleton' will get you a long way. The first result:
class SingletonB {
class var sharedInstance : SingletonB {
struct Static {
static let instance : SingletonB = SingletonB()
}
return Static.instance
}
}
Then use sharedInstance to instantiate the singleton anywhere and access the same variables.
The first one is quick and dirty, so for more serious projects I would recommend the singleton pattern.
There are probably a million ways to do this, but this should get you started
(More at this link, which explores a few ways to implement singletons: https://github.com/hpique/SwiftSingleton )
I simply solved my problem using NSUserDefaults
in the AppDelegate:
NSUserDefaults.standardUserDefaults().setObject(deviceToken, forKey: "deviceToken")
NSUserDefaults.standardUserDefaults().synchronize()
From other classes:
NSUserDefaults.standardUserDefaults().objectForKey("deviceToken")
Honestly I don't know if this is a good way to do it but it's working

Resources