Read / Write Data NSCoding iOS - Singelton or Not? - ios

This has been asked many times, but I'm still at a crossroads.
My application is required to save data, this data can be accessed through many areas of the application.
I wanted to avoid core data as it is overkill for what I need to save 3-4 simple different classes.
So I’m using NSCoding, and its working without issue.
Im not stuck on whats the “best” way to access this stored data.
Essentially, singleton or not. Been reading a variety of posts and most say avoid the use of a singleton.
This issue became apparent as my current design uses a singlelton object to provide a way for me to access my data from disc easily. Problem is testing… couldn’t find a way to overcome the inability to test - this is when I decided to ask should i not be using this pattern.
So, what is the better way to access my data - to allow read and write from anyplace within my application.
Use the singleton and just don’t test - or create a new instance, and ask that instance to retrieve my data. I also don’t want to pass data along - I want to be able to easily access data - read and write (using tab bar controller etc)
This was my singleton implementation
class InvalidClientCollection {
static var errorList = InvalidClientCollection.loadErrataClients()
static private let kErrataClientSaveFileName = "errorClientsFile"
static var clientCount:Int {
get {
return errorList.count
}
}
static func loadErrataClients() -> [Client] {
let mysavefile = FilePath(fileName: kErrataClientSaveFileName).filePath
if let data = NSKeyedUnarchiver.unarchiveObjectWithFile(mysavefile) as? [Client] {
return data
}
return [Client]()
}
static func saveErrataClientList() {
let mysavefile = FilePath(fileName: kErrataClientSaveFileName).filePath
NSKeyedArchiver.archiveRootObject(errorList, toFile: mysavefile)
}
}
Should I redesign, so to get my data I would
let mycolleection = InvalidClientCollection()
let mystuff = mycollection.errorList
I guess something like this ?
class InvalidClientCollection {
var errorList:[Client] {
get {
return loadErrataClients()
}
set {
self.errorList = newValue
// maybe even save at same time
//saveErrataClientList()
}
}
// etc...
}
Any ideas - improved code would be much appreciated.

Yes, avoiding a singleton for this is a good plan. One simple solution is to create a single instance of your data class and keep a reference as a property in the app delegate. It's easy to access the app delegate from any class. Then from there you can access the property. This eliminates the singleton and makes testing easier.
Another option is to pass the needed data instance to each of your view controllers in the app. This has the benefit of not assuming there is only one data instance needed by the app. This can start in the app delegate by passing the data instance to the root view controller. Then the root controller can pass it on to any view controller it presents or pushes, etc. This is made easier by subclassing UIViewController and having all of your view controller extend your base class. A UIViewController category is even better.

Related

How can I utilize dependency injection in Swift with minimal reliance on external libraries?

I am trying to understand how dependency injection works in Swift so I can inject into my ViewController. I have some experience with it in Kotlin but am unsure of how to approach it in Swift. I would prefer to have minimal reliance on external libraries but am not opposed to using one or two if they are reliable and well-maintained. Here is some code I found on a blog post about DI in Swift.
import UIKit
class ViewController: UIViewController {
lazy var requestManager: RequestManager? = RequestManager()
}
// Initialize View Controller
let viewController = ViewController()
// Configure View Controller
viewController.requestManager = RequestManager()
I understand that instead of instantiating RequestManager in the ViewController, I can create an instance of the ViewController itself and instantiate it using property access but I don't understand where I should declare the instance of the ViewController. I feel like I am missing some code or some context. Can someone help me to understand this?
To quickly answer the exact question asked, of how to pass in variables to VC's inside a storyboard. You can simply do something like this in the previous viewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let vc = segue.destination as? SomeViewController else {
return
}
vc.requestManger = SomeRequestManger()
}
To give a longer, more opinionated answer:
I personally only use DI in very small and very limited ways. There are a few exceptions where more advanced use cases crop up. But in general I feel like a lot of people way over think this and just do it because they "should" as oppose to actually needing too. A lot of cases I see online of creating mock instances of every service class, and passing them around where needed, is simply a bad idea as you wind up testing very little of your actual code, and create a tonne of extra effort for yourself for no reason.
For an app i'm working on, all I did was create a mock instance of URLProtocol and hold onto a reference of a separate URLSession instance in a class in my test bundle, something like this:
public let mockURLSession: URLSession
private init() {
let sessionConfig = URLSessionConfiguration.ephemeral // Uses no caching / storage
sessionConfig.protocolClasses = [MockURLProtocol.self]
mockURLSession = URLSession(configuration: sessionConfig)
...
}
and then I had a DependencyManger.swift that was a singleton. Any Service class that needed a URLSession, was created and held inside that class, and I had a function to pass the mock session or the real session into each of those classes, from the manager.
From then on I didn't have to worry about adding 20K lines of code of third party libraries, just so I could have fancy Init's on my VC's. Or worry about writing hundreds of lines of code passing references between one viewController to another. Inside each VC I would just do:
DependencyManger.shared.networkService.get(...) {
...
}
All I need to do then is flip the toggle while running my tests, and all my networking code will use my MockURLProtocol, where I can override the methods and have it return stubbed data per URL. In this situation I only needed to mock my networking data, if you need to also mock push notifications for example, you'd need to go one step further.
Incredibly simple, works with interface builder or UI written in code, extremely flexible, a tiny amount of code, no third party dependencies to maintain

Best way to avoid singleton

For our iOS programming class we must make a framework for Swift iOS. We had the idea of a framework simplifying CoreData manipulation. I began by creating a class where you put the NSManagedObjectContext created in AppDelegate at the beginning, so you don't have to write this long (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext anymore.
open class SimpleCoreData {
var context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
}
func delete(entity: NSManagedObject) /*-> Bool*/ {
// some code
}
func getAll(entityClass: NSManagedObject.Type) throws -> [NSManagedObject]? {
// some code
}
func create(entityDescr: NSManagedObject.Type) -> NSManagedObject? {
// some code
}
But I would like it to be accessible from everywhere in the application, and this simplification would be useless if you have to instantiate this each time.
I was first thinking about a singleton, but I recently learned it wasn't a good practice.
So do you know any solution to make this accessible from everywhere in the client app? Or maybe singleton is okay in this case?
Keeping Rob Napier's excellent comments in mind, if you decide to avoid a singleton in this case, a common approach would be
Create an instance of your SimpleCoreData class in the app delegate when the app launches.
Have the app delegate pass this to your initial view controller. That view controller would have a SimpleCoreData property but would not create the instance-- it would expect one to be assigned by whichever code creates it, which here is the app delegate.
Repeat this pattern everywhere you need a SimpleCoreData. That is, when you create an object that needs a SimpleCoreData, make sure it has a property with that type, and assign a value when you create it. For view controllers, a good place to do this is in prepare(for:sender:), if you're using segues.
It's not necessary to create the SimpleCoreData in the app delegate, though. You could create it at the first point in the app hierarchy where it's needed, and pass it along from there. So if it's only needed in the second view controller in the hierarchy and in other objects loaded from there, create it in that view controller.
This doesn't make your SimpleCoreData instance available everywhere automatically, it means that you're creating one and then passing it around. Often that works fine, but as Rob notes it's not always the best approach. It can lead to passing an object along to an object that doesn't need it, because some other object that gets created later on does. For example if you need SimpleCoreData in your initial view controller but then not again until five levels down the hierarchy, you still need to pass it along every step of the way. That's an example of when a shared instance can be useful. It's not a singleton since other instances are allowed, but it's a default instance that can be used as needed.
I finally learned thanks to you that singletons aren't so evil, they can be used in some case, including this one, and it seemed in my case that it was a good choice. Maybe I will change it for a shared instance pattern.
So the singleton works well. Thank you everybody for your advices, I learned a lot in design patterns.

Using an Array Variable in All Views

In an app that I am working for, I need array variable that can be used in all UIViews. Currently when the array is changed in a view it is stored in the database, and when I need the updated version of the array in the previous view, I use the viewWillAppear method and retrieve the updated array from the database. While going to another view by a segue, I use the passing data by prepareForSegue, but if I use the back button, or just change the screens via tab bar, I use the viewWillAppear and a query.
Is there any way that when the array is created in a view, the data in it will be accessible in all views?
As I've stated in my comment, singletons are generally frowned upon for a myriad of reasons. However, there is much debate on this topic:
What is so bad about singletons?
Having said that, the best way I know to make a variable globally available for the session is by creating a singleton.
struct myArray {
static var data: [Int] = []
}
You could set this singleton up to fetch the records using CoreData and store the current working version in a static variable for quick access.
note: I am really very curious to see some other approaches.
Singleton
Singleton is basically a global variable that you can use them in any views, but some developers experience some bugs and difficulties, use it at your own risk, I recommend this method when you're definite that you will use that data a lot (STILL RISKY), but this method is like goddess of data handling :).
Create a NSObject subclass and call it DataManager.swift (I call it data manager cause it handle data.) as following:
import UIKit
class DataManager: NSObject {
//Store Data Globally
static var someData: NSArray! //This Boolean, you can choose whatever you want.
}
the static is what keep your data live.
Now you can store and receive someData from anywhere like you handle any data type like this.
//Store
DataManager.someData = []
//Receive
print(DataManager.someData)

iOS Swift storing simple data globally

New to iOS development and trying to develop my app with correct practices. My main issue is trying to figure out the best way to store a bunch of simple data globally.
The app connects to a computer adhoc wifi and gathers information about the current state. (ex// gpu temp, computer name, display resolution, date/time, etc). The various view controllers I have then allow the user to adjust some of these settings so I want to be able to keep track of these changes across all view controllers.
I have been wrestling between using a plist, core data, or singleton class to store all the information. Since the user will be connected to one computer for the life of the app I am leaning towards singleton since it seems easiest to get and set data. However, I have read that using a singleton is really not the best practice for various reasons. I have read up on plists and core data a little and seems like a decent amount of work just to get and set values. Should I spend the time using those or would some other method be a better way to accomplish all of this.
Update:
After both answers from Aaoli and Swift Rabbit. I wanted to clarify for my use case that the data I am storing did not need to be "saved" when the app was closed. So if you are coming to this question and need to store your data even if the app closes look at #SwiftRabbit's answer.
To use Global variable in Swift you need to define struct outside the class and because you don't need the variable to be swimming in the whole app memory without binding:
struct GlobalVar {
static var myvar = 0
}
To use simply the global variable from anywhere use :
GlobalVar.myvar = 10
In my case, using "simple" data, which is not sensitive, I usually use the user defaults. They can be accessed globally through the project. For example, it can be set this way :
var prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
prefs.setObject(myValue, forKey: "myKey") // myValue is of type typeOfValue
prefs.synchronize()
It can be accessed this way :
var prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
prefs.valueForKey("myKey") as? typeOfValue
Using setObject can be used for any kind of value, that's why I cast it when retrieving it from the userDefaults. You could actually use other methods to insert it and retrieve it that would make your task easier. For example, you could tell you are inserting a bool with :
prefs.setBool(<#value: Bool#>, forKey: <#String#>)
And specify you are retrieving one with :
prefs.boolForKey(<#defaultName: String#>)
All methods can be found in the apple documentation here
My opinion: create a singleton, e.g. in this way:
class MyClass {
/// The singleton instance
private static var singletonInstance: MyClass!
class var sharedInstance: MyClass! {
get {
singletonInstance = singletonInstance ?? MyClass()
return singletonInstance
}
}
private init() { }
}
Or in this way:
class MySingletonClass {
static let sharedInstance = MySingletonClass()
private init() {
}
Many people consider Singleton a poor design pattern, but in my opinion it is not. It is good to have a pattern which ensures that a piece of information is only available once. What is not good is to have global access to this Singleton from anywhere in the application. Some people counter-act this with "Dependency Injection", which basically means that your classes does not "pull" the information from the Singleton but the information is "pushed" into the class from (some other instance) outside. This promotes loose coupling and better test-ability. But this is an advanced topic which needs much more information. (Google "typhoon framework" for iOS if you are interested in this)

Pass a variable from foreground to background in Swift

I am developing an iOS application where I want to record the time when a user presses a particular button and keep it. Later I will use this time record in background. Is there a nice way of doing that without invoking NSUserDefaults or CoreData or whatever other database?
I am very new to iOS development. I think this is very likely to be a naive question. But I'm just curious. Please don't laugh at me haha.
Edit: This is indeed a very naive question haha.
A simple way to make sure your data is available everywhere in your app and only persists for each app session would be to use a singleton. Something like this.
// Create a class to store the data
class SessionData : NSObject {
// Create a shared instance of your class
static let sharedInstance = SessionData()
// Create a variable to store the date object
var timeStore:NSDate?
}
This will mean that anywhere in your app you can get and set this data as below.
// Get
if let time = SessionData.sharedInstance.timeStore {
println(time)
}
// Set
SessionData.sharedInstance.timeStore = NSDate()
It is worth mentioning that despite the above being a valid method, You may be able to avoid doing this by re-thinking your app structure and passing data between classes instead. You should have a look at Delegation.
Also as #CouchDeveloper mentions in their comment below, you may want to included a dispatch queue to prevent crashes or locks in the situation where two or more classes try to read and or write data at the same time.

Resources