Property injection of generic class in controller - ios

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

Related

Swift class throwing immutable self

I have a method in a protocol extension that plays music files. Since the protocol doesn't know if its conformer will be a class or struct and the methods in it change an ivar, it requires them to be marked as mutating.
When I conform a class to that protocol and try to call the method I'm getting the below error even though a class should always be muteable...
Cannot use mutating member on immutable value: 'self' is immutable
Here's the protocol...
import AVFoundation
/// Conformers are required to implement `player` property to gain access to `playMusic:fileLloops`
/// method.
protocol CanPlayMusic {
/// This instance variable stores the audio player for `playMusic:file:loops` method.
var player: AVAudioPlayer! { get set }
}
extension CanPlayMusic {
/// This method creates a new audio player from url and then plays for a number of given.
///
/// - Parameter file: The url where sound file is stored.
/// - Parameter loops: The number of loops to play (-1 is infinite).
mutating func playMusic(file url: URL, loops: Int = -1) {
player = try! AVAudioPlayer(contentsOf: url)
player.numberOfLoops = loops
player.play()
}
/// This method starts playing intro music.
mutating func playIntroMusic() {
let file = Assets.Music.chargeDeBurkel
let ext = Assets.Music.ext
guard let url = Bundle.main.url(forResource: file,
withExtension: ext) else { return }
playMusic(file: url)
}
/// This method starts playing game over music based on win/loss condition.
mutating func playGameOverMusic(isWinning: Bool) {
guard let lose = Bundle.main.url(forResource: Assets.Music.taps,
withExtension: Assets.Music.ext),
let win = Bundle.main.url(forResource: Assets.Music.reveille,
withExtension: Assets.Music.ext)
else { return }
playMusic(file: isWinning ? win : lose, loops: 1)
}
}
And here's how I call it in a class...
import UIKit
import AVFoundation
class EntranceViewController: UIViewController, CanGetCurrency, CanPlayMusic {
...
// MARK: - Properties: CanPlayMusic
var player: AVAudioPlayer!
...
// MARK: - Functions: UIViewController
override func viewDidLoad() {
playIntroMusic() // <-- Error thrown here
startButton.setTitle("", for: .normal)
getCurrency()
}
UPDATE
I use these methods in multiple places, both in UIKit scenes and SwiftUI views; moving it into the protocol extension was an attempt to reduce duplication of code.
For now, I'm using a class wrapper that I can call in both contexts; but I still haven't seen an explanation for the error triggering on a class (since they're pass by ref and all of their functions are considered mutating by default).
To clarify, my question is "Why is this class having issues with a mutating function?"
The error is a bit misleading, but I believe the reason of it is that you call a method marked with mutating (defined in your protocol extension) from a class – it's illegal.
Consider this simplified example:
protocol SomeProtocol {
var state: Int { get set }
}
extension SomeProtocol {
mutating func doSomething() {
state += 1
}
}
struct SomeValueType: SomeProtocol {
var state = 0
init() {
doSomething()
}
}
final class SomeReferenceType: SomeProtocol {
var state = 0
init() {
doSomething() // Cannot use mutating member on immutable value: 'self' is immutable
}
}
One way to get rid of the error is not using the same implementation for both structs and classes and defining their own implementations:
protocol SomeProtocol {
var state: Int { get set }
mutating func doSomething()
}
struct SomeValueType: SomeProtocol {
var state = 0
init() {
doSomething()
}
mutating func doSomething() {
state += 1
}
}
final class SomeReferenceType: SomeProtocol {
var state = 0
init() {
doSomething()
}
func doSomething() {
state += 1
}
}
Another way is to, at least, defining an own implementation for classes, which will shade the default implementation from the protocol extension:
protocol SomeProtocol {
var state: Int { get set }
}
extension SomeProtocol {
mutating func doSomething() {
state += 1
}
}
struct SomeValueType: SomeProtocol {
var state = 0
init() {
doSomething()
}
}
final class SomeReferenceType: SomeProtocol {
var state = 0
init() {
doSomething()
}
func doSomething() {
state += 1
}
}

Swift how to conform to different associate type in a protocol

I am developing a state management library. The original design only has 1 listener, which works great until I need to support multiple listeners.
The original design is here:
Swift how to use generic protocol in generic class
This is what I have done to support multiple listeners:
public protocol StateObserver: AnyObject {
associatedtype State
func didUpdateState(_ state: State)
}
public final class StateStore<Observer: StateObserver> {
struct WeakRef<T: AnyObject> {
weak var value: T?
}
public private(set) var state: Observer.State
private var observers = [WeakRef<Observer>]()
public init(initialState: Observer.State) {
state = initialState
}
public func addObservers(_ observers: [Observer]) {
self.observers += observers.map { WeakRef(value: $0) }
}
public func update(_ block: (inout Observer.State) -> Void) {
var nextState = state
block(&nextState)
state = nextState
notify()
}
public func notify() {
for observer in observers {
observer.value?.didUpdateState(state)
}
}
}
Now I need to create the store with 2 observers:
class MyScene: SKScene {
init {
let leftPanel = LeftPanelSKNode()
let topBar = TopBarSKNode()
let store: StateStore<?> // How to make this support `LeftPanelSKNode `, `TopBarSKNode`, and `MyScene`?
store.addObservers([leftPanel, topBar, self])
}
Now I am stuck here. I need to create a StateStore<?> of something, which can be either MyScene, LeftPanelSKNode and TopBarSKNode.
First of all, I have to say that what you are building already exists in many reactive libraries:
CurrentValueSubject in Apple's Combine;
BehaviorSubject in RxSwift;
You can also check the small internal class I've made myself, it allows to hold the state and observe it ObservableProperty.
Back to your question, I've found a way to add the StateObserver one by one while keeping only the weak reference to them.
public protocol StateObserver: AnyObject {
associatedtype State
func didUpdateState(_ state: State)
}
class Node1: StateObserver {
typealias State = Int
func didUpdateState(_ state: Int) { }
}
class Node2: StateObserver {
typealias State = Int
func didUpdateState(_ state: Int) { }
}
class StateStore<StateType> {
private(set) var state: StateType
init(_ initialState: StateType) {
self.state = initialState
}
private var observers: [(StateType) -> Void] = []
func observe<Observer: StateObserver>(by observer: Observer) where Observer.State == StateType {
weak var weakObserver = observer
observers.append { state in
weakObserver?.didUpdateState(state)
}
}
func notify() {
observers.forEach {
$0(self.state)
}
}
}
let store = StateStore<Int>(0)
let node1 = Node1()
let node2 = Node2()
store.observe(by: node1)
store.observe(by: node2)
Adding the array-based observe API might be a problem because of the associatedtype in the StateObserver.

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?

REST API, Swift, Automatic Update

I'm currently struggling to find an easy-to-use programming approach/design pattern, which solves the following problem:
I've got an REST API where the iOS app can request the required data. The data is needed in different ViewControllers. But the problem is, that the data should "always" be up to date. So I need to set up a timer which triggers a request every 5-20 seconds, or sth like that. Everytime the data changes, the view needs to be updated (at the current viewcontroller, which is displayed).
I tried some stuff with delegation and MVC Pattern, but it's kind a messy. How is it done the right way?
In my current implementation I only can update the whole UICollectionView, not some specific cells, because I don't know how the data changed. My controller keeps track of the data from the api and updates only if the hash has changed (if data changed on the server). My models always holds the last fetched data.
It's not the perfect solution, in my opinion..
I also thought about models, that keep themselves up to date, to abstract or virtualise my Rest-API. In this case, my controller doesn't even know, that it isn't directly accessible data.
Maybe someone can help me out with some kind of programming model, designpattern or anything else. I'm happy about anything!
UPDATE: current implementation
The Controller, which handles all the data
import Foundation
import SwiftyJSON
import SwiftyTimer
class OverviewController {
static let sharedInstance = OverviewController()
let interval = 5.seconds
var delegate : OverviewControllerUpdateable?
var model : OverviewModel?
var timer : NSTimer!
func startFetching() -> Void {
self.fetchData()
timer = NSTimer.new(every: interval) {
self.fetchData()
}
timer.start(modes: NSRunLoopCommonModes)
}
func stopFetching() -> Void {
timer.invalidate()
}
func getConnections() -> [Connection]? {
return model?.getConnections()
}
func getConnectionsSlave() -> [Connection]? {
return model?.getConnectionsSlave()
}
func getUser() -> User? {
return model?.getUser()
}
func countConnections() -> Int {
if let count = model?.getConnections().count {
return count
}
return 0
}
func countConnectionsSlave() -> Int {
if let count = model?.getConnectionsSlave().count {
return count
}
return 0
}
func fetchData() {
ApiCaller.doCall(OverviewRoute(), completionHandler: { (data, hash) in
if let actModel = self.model {
if (actModel.getHash() == hash) {
//no update required
return
}
}
var connections : [Connection] = []
var connectionsSlave : [Connection] = []
for (_,connection):(String, JSON) in data["connections"] {
let connectionObj = Connection(json: connection)
if (connectionObj.isMaster == true) {
connections.append(connectionObj)
} else {
connectionsSlave.append(connectionObj)
}
}
let user = User(json: data["user"])
//model needs update
let model = OverviewModel()
model.setUser(user)
model.setConnections(connections)
model.setConnectionsSlave(connectionsSlave)
model.setHash(hash)
self.model = model
//prevent unexpectedly found nil exception
if (self.delegate != nil) {
self.delegate!.reloadView()
}
}, errorHandler: { (errors) in
}) { (progress) in
}
}
}
protocol OverviewControllerUpdateable {
func reloadView()
}
The model, which holds the data:
class OverviewModel {
var user : User!
var connections : [Connection]!
var connectionsSlave : [Connection]!
var connectionRequests : [ConnectionRequest]!
var hash : String!
...
}
And in the ViewController, I use it like this:
class OverviewVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, OverviewControllerUpdateable {
let controller = OverviewController.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
self.controller.delegate = self
self.controller.startFetching()
}
//INSIDE THE UICOLLECTIONVIEW DELEGATE METHODS
...
if let user : User = controller.getUser() {
cell.intervalTime = interval
cell.nameLabel.text = "Ihr Profil"
}
...
func reloadView() {
self.userCollectionView.reloadData()
}
}
You could use a Singleton object to fetch your data periodically, then post notifications (using NSNotificationCenter) when the data is updated. Each view controller dependent on the data would listen for these notifications, then reload UI based on the updated data.

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

Resources