Refactoring large Swift struct with mutating functions - ios

I'm trying to simplify a huge file in Swift and I'm not sure what would be the best approach. It might be a very naive question as I'm still fairly new to Swift :)
For the sake of simplicity, let's say I have one User struct and in it I can call a ton of different setupModeA, setupModeB and so on, called depending on various conditions:
struct User {
var currentMode: String
mutating func setupModeA() {
currentMode = "A"
}
mutating func setupModeB() {
currentMode = "B"
}
// and so on for each mode
}
I'd like to be able to take the setupMode*() functions and their associated helper functions into a separate file to make it easier to read through, since most of the time I'd be focusing on only one mode.
I was thinking:
struct ModeA {
mutating func setup() {
// change the mode from there
}
}
However I can't figure out how to simply pass and mutate the User info to this new struct.
I thought about:
initializing a bunch of ModeX objects with a user variable in the User struct and passing the User on init, but it creates weird behaviours.
using static methods on the ModeX structs, but then I'm not quite sure how to mutate the User
taking a more functional approach & not mutating the User but instead cloning it and re-assigning it each time... this would probably work but since my code is very sequencial it feels like added complexity / memory usage for nothing.
switching from struct to classes and hoping it gives me other options... but I'm afraid everything would break
really complex things that feel too weird and unintuitive to be mentioned here :)
Any pointers would be much appreciated.

Related

Static variables vs methods to store properties

In my SwiftUI application I'm currently using a global struct that holds constants properties like colors or sizes.
E.g.
struct App {
struct Colors {
static let mainAppColor: UIColor = UIColor(red: 233/255, green: 230/255, blue: 231/255)
static let mainWhiteColorMiddleOpacity: UIColor = UIColor.white.opacity(0.5)
}
}
// usage
struct ContentView: View {
var body: some View {
Rectangle().foregroundColor(App.Colors.mainBlueColor)
}
}
I was debating whether this is a bad practice since I have LOTS of static variables, and was wondering if replacing them with static methods or computed properties would decrease memory usage (since body gets computed lots of times). E.g. replacement:
struct App {
struct Colors {
static var mainBlueColor: UIColor { UIColor.blue }
static var mainWhiteColorMiddleOpacity: UIColor { UIColor.white.opacity(0.5) }
}
}
// usage
struct ContentView: View {
var body: some View {
Rectangle().foregroundColor(App.Colors.mainBlueColor)
}
}
One would increase heap usage and the other stack usage, right?
What do you think would be the best practice?
Premature (and ill-conceived) optimization. You probably have no idea how little memory is actually taken up by even thousands of these values, in the grand scheme of things; it's trivial. And any decently written app will have a lot of them; it's what you do.
A struct is a great way to organize and namespace these sorts of things, although it happens that an enum is now Apple's preferred way (for reasons that I do not entirely understand).
Your "since body gets computed lots of times" is totally irrelevant: the name is a direct reference to the value with absolutely no overhead, since if you use let the whole thing is compiled away and replaced by the value right at the point of reference in your code. Lookup time at runtime is zero!
(A computed value or method, on the other hand, might require some serious overhead to run each time, potentially bringing the entire message dispatch mechanism into play, though it might in fact be inlined away perhaps. Serious backsliding there.)
Computed properties are only useful if there is something to compute so the variable makes sense.
You are definitely creating constants (as you mentioned), so use let
Regarding memory usage: How about declaring colors in the assets catalog?
I would say never use Computed properties for the things you already knows the answer or value, from the other side do you know how many times would SwiftUI run that Computed properties for using it? so many run code for getting the same thing!
you can use Computed properties for the things you need once or some times in your app just for reading value, for example you have a Rectangle that you know the width and height, then use Computed properties for getting the area, or something related like that, after reading value you would be finished with that as well, and there is no need to store it as well.

Swift instance members & functions calls, could someone please clarify?

I'm practicing functions in an Xcode project (not playground, if that matters) and when I set the constant's function name to the above function's name, Xcode won't let me call it.
In other functions I've done the same thing I haven't had this error, I don't know why it's being triggered. I've tried putting the "let fun" constant into it's own struct but that just throws up a different error.
Your let fun = playstation(... is code. It is an executable command. Code consisting of an executable command cannot just live anywhere. It cannot exist just free-floating the way you have it. It must live in a method. For example you could put it inside your viewDidLoad method.
I am sure you know this, but I would like to say that learning the basics/fundamentals of the Swift language is really good if you use playgrounds for that or any other online IDEs which support Swift language.
Swift Playgrounds Experience is very, very different than what an iOS dev has to do later, i.e. when it actually comes to building an app.
Of course in app making, the Swift language is used, and one uses the knowledge he practiced using playgrounds. BUT!
When it comes to storyboards, ViewControllers, etc., one has to understand that this time, it is all about OOP (classes and structs).
It is about managing views, loading them, displaying UIView objects, implementing logic, saving/loading data...
So as people mentioned above, here you are creating an instance method and trying to use it as a regular function inside that Class.
To be able to use this function as you expect, you have to create an object/instance of this class, then you can call this function.
In this picture, you may see that using a static keyword also might be a solution. static func(){} means that this function belongs to the class itself. You access that func by using the full name of the type/class.
You cannot call an instance method to initialize this same exact instance property, so you will need to convert to a lazy var like so:
lazy var fun = playStation(game: "Monsters")
This way, the playStation(game: "Monsters") call will be triggered lazily, in the exact moment when the fun property will be called for the first time. This can be very useful, especially when performing more intensive tasks. The quirk with using lazy keyword is that it can only be used with var, which means the property can be mutated or set again - we lose immutability of let constants.
But maybe you do not want to store the fun value as an instance property and only want to get it once? You could then move it into the viewDidLoad implementation, so the fun is no longer an instance property but a local one, available only within the viewDidLoad method scope.
override func viewDidLoad() {
super.viewDidLoad()
let fun = playStation(game: "Monsters")
// do what you want with the `fun` value.
// maybe set it as text to some label?
myLabel.text = fun
}
You can read more about Lazy Initialization in this blogpost, also make sure to read the official documentation.

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)

What should I do about frequently repeated constants?

This seems like a very simple question, but I can't find a clear answer. Also it's not specifically about swift or iOs, but I'm new to programming and swift is the only language I know anything about, so I don't know how to phrase it for a more general context.
I'm trying to write an iOs app and I found myself defining the same constants many times throughout my code.
I must have written this line about a hundred times, for instance:
let calendar = NSCalendar.currentCalendar()
Another example is getting my only User object from its persistent store:
let realm = try! Realm()
let user = realm.objects(User).first!
I define those calendar and user constants over and over throughout my whole code in classes and subclasses and extensions and computed properties.
That seems kind of stupid, though. I think I should be able to define calendar once and for all and just use it when I need it. So my first thought was to declare a global constant, but apparently everybody thinks anything with the word "global" in it should be avoided at all costs.
So what should I do? Is there another solution for this? Should I just keep writing the same stuff over and over again?
Thanks in advance,
Daniel
There are many different situations in which the best use of different approaches.
For example in your case:
let calendar = NSCalendar.currentCalendar()
currentCalendar is a static method that already returns a pointer to the object that you will use. And you don't need to set it to some constant for using with simple case:
print(NSCalendar.currentCalendar().calendarIdentifier)
Another thing that is most often better to use a shorter name for the object in your code when you need to refer to it often and this code looks much more readable:
print(calendar.calendarIdentifier)
If you have the functionality that you will often use in application from different places, you can just make it to the static method and does not create an object of this class every time you call it:
class NetworkConnection {
class func getDataFromServet(completion block: (data: SomeType) -> Void) {
...
}
}
And use it without object creation like:
NetworkConnection.getDataFromServer(completion: {(data: SomeType) -> Void in
...
})
If you need to use created object in many places, the best solution is not to make it global or singleton instance, but pass a pointer to it to the objects where you need to use it. This makes the code more readable, for example by looking at the input parameters of the init method, anyone can immediately understand which objects use this class for their work. And this class is easier to take from the project in a separate module and connect to another project. At that time, if you use the singleton instance, the class's interface is not clear what it can be used and this leads to code obfuscation. This applies and to the global objects.
If you're constantly changing it, why aren't you just using var instead of let?

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)

Resources