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

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

Related

Using Arrays in Singleton Class in iOS

PetInfo.class
class PetInfo {
static let shared: PetInfo = PetInfo()
lazy var petArray: [PetInfo] = []
var PetID:Int
var PetName:String
...
init(){ .. }
}
ViewController.swift
class ViewController: UIViewController {
var PetArray = PetInfo.shared.petArray
override func viewDidLoad() {
super.viewDidLoad()
let pet = PetInfo()
pet.PetName = "Jack"
PetArray.append(pet). **Create Object and gave a name**
print(PetArray[0].PetName) //works!
}
}
secondViewController.swift
class secondViewController: UIViewController {
var PetArray = PetInfo.shared.petArray
override func viewDidLoad() {
super.viewDidLoad()
let label: UILabel = {
let label = UILabel()
...
label.text = PetArray[0].PetName **tried to print**
return label
}()
view.addSubview(label)
}
}
I want to share PetArray array in all of the view controllers.(It's more than two.)
It put data in the first VC, but doesn't work in the Second VC.
How can I share this array using a Singleton pattern?
Except for the array, It works perfect.(like String.. PetID, PetName.. )
Array in swift is implemented as Struct, which means Array is a value type and not a reference type. Value types in Swift uses copy on write (COW) mechanism to handle the changes to their values.
So in your ViewController when you assigned
var PetArray = PetInfo.shared.petArray
your PetArray was still pointing to the same array in your PetInfo.shared instance (I mean same copy of array in memory) . But as soon as you modified the value of PetArray by using
PetArray.append(pet)
COW kicks in and it creates a new copy of petArray in memory and now your PetArray variable in your ViewController and PetInfo.shared.petArray are no longer pointing to same instances instead they are pointing to two different copies of array in memory.
So all the changes you did by using PetArray.append(pet) is obviously not reflected when you access PetInfo.shared.petArray in secondViewController
What can I do?
remove PetArray.append(pet) and instead use PetInfo.shared.petArray.append(pet)
What are the other issues in my code?
Issue 1:
Never use Pascal casing for variable name var PetArray = PetInfo.shared.petArray instead use camel casing var petArray = PetInfo.shared.petArray
Issue 2:
class PetInfo {
static let shared: PetInfo = PetInfo()
lazy var petArray: [PetInfo] = []
var PetID:Int
var PetName:String
...
init(){ .. }
}
This implementation will not ensure that there exists only one instance of PetInfo exists in memory (I mean it cant ensure pure singleton pattern), though you provide access to instance of PetInfo using a static variable named shared there is nothing which stops user from creating multiple instances of PetInfo simply by calling PetInfo() as you did in let pet = PetInfo()
rather use private init(){ .. } to prevent others from further creating instances of PetInfo
Issue 3:
You are holding an array of PetInfo inside an instance of PetInfo which is kind of messed up pattern, am not really sure as to what are you trying to accomplish here, if this is really what you wanna do, then probably you can ignore point two (creating private init) for now :)
I think the best solution to use Combine and Resolver frameworks. Works perfectly in my case with shared arrays.
In your case it could be
import Combine
import Resolver // need to add pod 'Resolver' to Podfile and install it first
// Data Model
struct PetInfo: Codable {
var PetID:Int
var PetName:String
...
}
// Repository to read manage data (read/write/search)
class PetRepository: ObservableObject {
#Published var petArray = Array<PetInfo>()
override init() {
super.init()
load()
}
private func load() {
// load pets info from server
}
}
Need to add AppDelegate+Injection.swift that will contain repository registration:
import Foundation
import Resolver
extension Resolver: ResolverRegistering {
public static func registerAllServices() {
// Services
register { PetRepository() }.scope(application)
}
}
Then use it in any controllers
import UIKit
import Combine
import Resolver
class ViewController: UIViewController {
#LazyInjected var petRepository: PetRepository
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
petRepository.$petArray
.receive(on: DispatchQueue.main)
.debounce(for: 0.8, scheduler: RunLoop.main)
.sink { [weak self] petInfos in
// set UI here
}
.store(in: &cancellables)
}
}
If you want PetInfo to be a singleton, make its initializer private:
class PetInfo {
static let shared: PetInfo = PetInfo()
lazy var petArray: [PetInfo] = []
var PetID:Int
var PetName:String
...
private init(){ .. } // !!
}
This way, any attempt to create new instances (like you do in your first ViewController) will fail, and will remind you to always use PetInfo.shared to access the singleton.

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?

singleton class shared between app and extension

When i add value like reference for UIViewController or string Id to singleton class, then try to access them from share extension i could't get this value again
the share extension create new singleton with null value
How i can make this class and the data inside it shared between main app and extension?
class Gateway:NSObject {
private var id:String? = nil
private weak var delegate:GatewayDelegate!
func set(id:String){
self.id = id
}
func set(gatewayDelegate:GatewayDelegate){
self.delegate = gatewayDelegate
}
func dismiss() {
self.id = nil
self.delegate = nil
}
func append(str:String,_id:String) {
if let id = self.id ,id == _id ,self.delegate != nil {
self.delegate!.gatewayAppend(str: str)
}
}
static let shared = Gateway()
private override init() {}
}
An extension is a completely separate process. It runs in its own sandbox and its own memory. Both your main app and your extension can create an instance of the singleton object, but they will be separate instances. They won't share any data.
To exchange data between your main app and your extension you will need to use an app group with user defaults, the keychain or a file.

How to reset singleton instance?

I'm creating a singleton instance like this
static let currentUser = User()
private override init() {
super.init()
// custom initialisation
}
How can I reset this instance or set back to nil?
I create all my Singletons with an optional Singleton instance.
However I also make this private and use a function to fetch it.
If the Singleton is nil it creates a new instance.
This is actually the only good way to set up a Singleton. If you have a regular object that you can't deinitialize it's a memory problem. Singletons are no different, except that you have to write a function to do it.
Singletons have to be completely self managed. This means from init to deinit.
I have a couple of templates on github for Singeltons, one of them with a fully implemented read/write lock.
class Singleton {
private static var privateShared : Singleton?
class func shared() -> Singleton { // change class to final to prevent override
guard let uwShared = privateShared else {
privateShared = Singleton()
return privateShared!
}
return uwShared
}
class func destroy() {
privateShared = nil
}
private init() {
print("init singleton")
}
deinit {
print("deinit singleton")
}
}
You can not do that if you declare currentUser as let. It should be var instead, or better still private (set) var. Also you can not assign currentUser with nil if its type is User (inferred from the way you assign it at the moment). Instead, it should be User?.
For example, something like this:
/// ...
static private (set) var currentUser: User? = User()
static func resetCurrentUser() {
currentUser = nil
}
// ...
private (set) allows changes to the property only within the scope of current file, and for the rest of your code it will be seen as let. Then method resetCurrentUser() can be used to put it to nil.
Or even this:
// ...
private static var _currentUser: User?
static var currentUser: User {
if _currentUser == nil { _currentUser = User() }
return _currentUser!
}
static func resetCurrentUser() {
_currentUser = nil
}
// ...
You can have currentUser as computed property that guarantees to return a value. So you can reset the user to nil, yes. But if later you will try to read from there again a new instance will be created automatically.
Be careful with multithreaded access, though.
The accepted answer is working, but if you want to not deal with optionals you can create a private setter:
class Singleton {
static private(set) var shared: Singleton = Singleton()
static func reset() {
shared = Singleton()
}
private init() {
print("init singleton")
}
deinit {
print("deinit singleton")
}
}
All you want is possible, but highly unrecommended :) Because singletons by design should not fall back to nil.
First, if you want to change currentUser, it must be var. Then if you want it to be nil, it must by optional type and you should unwrap it when using.
static var currentUser: User? = User()
I would propose to not change currentUser or make it non-static (for example, a property of some UsersManager.
Also you can change properties of currentUser (like name, loggedIn). At last, take a look at this answer: https://stackoverflow.com/a/28398974/326017 - it describes your situation.

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