NSUserDefault constant not updating - ios

I have NSUserDefaults implemented in my app and the correct values persist between launches, but when referencing a constant I created the values don't update when the default values are changed during a session.
I'm certain my problem stems from using constants to reference NSUserDefaults, but I can see how much code it's saved me and I'd like to stick with this approach, if feasible/good practice. I'm not sure how I'd go about updating the constant for new NSUserDefault values. I'm not getting crashes--just stale values.
My Setup
AppDelegate.swift
I use NSUserDefaults to store some integer values in my app. In AppDelegate.swift I do a check to see if they're set. This works.
PreferencesViewController.swift
I have a PreferencesVC that I've configured that populates with the defaults and enables the user to change the default values. I put some print statements to verify new NSUserDefaults values when they're saved. This works.
MyTableViewController.swift
I declared constants on a so I can trim down verbose code.
// Import Statements
#import UIKit
// Constants I declare here so they're accessible throughout the app.
let kDefaultActiveTime = NSUserDefaults.standardUserDefaults().integerForKey("defaultActiveTime")
let kDefaultBreakTime = NSUserDefaults.standardUserDefaults().integerForKey("defaultBreakTime")
let kDefaultRounds = NSUserDefaults.standardUserDefaults().integerForKey("defaultRounds")
// Class declaration
class MyTableViewController: UITableViewController
Within my cellForRowAtIndexPath I use the default constants to compute a time and output a string for a label on a cell. Here's the line:
// boilerplate cellForRowAtIndexPath omitted
let customCell = tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! RoutineSelectionTableViewCell
let routine = fetchedResultsController.objectAtIndexPath(indexPath) as! SavedRoutines
// ** This doesn't update w/ tableView.reloadData()
customCell.routineInformationLabel.text = String(format: "Time to complete - %#", formatTimeInSeconds(totalRoutineTime(routine.items! as NSOrderedSet)))
Here are the methods used to create the string for routineInformationLabel.text:
// Time computation methods
func totalRoutineTime(routine: NSOrderedSet) -> Int {
var totalRounds = 0
for item in routine {
// get the total of rounds
totalStretchRounds += (((item as! MyNSManagedObject).sideMultiplier?.integerValue)!) * kDefaultRounds
}
let totalActiveTime = totalRounds * kDefaultActiveTime
let totalBreakTime = (totalRounds - 1) * kDefaultBreakTime // subtract 1 rest period for last item
return totalActiveTime + totalBreakTime
}
func formatTimeInSeconds(totalSeconds: Int) -> String {
let seconds = totalSeconds % 60
let minutes = (totalSeconds / 60) % 60
let hours = totalSeconds / 3600
let stringHours = hours > 9 ? String(hours) : "0" + String(hours)
let stringMinutes = minutes > 9 ? String(minutes) : "0" + String(minutes)
let stringSeconds = seconds > 9 ? String(seconds) : "0" + String(seconds)
if hours > 0 {
return "\(stringHours):\(stringMinutes):\(stringSeconds)"
}
else {
return "\(stringMinutes):\(stringSeconds)"
}
}
I tried creating an observer to reload the tableView when NSUserDefaults are saved, but that doesn't seem to be doing the trick. I've also tried throwing in tableView.reloadData() in viewDidAppear, viewWillAppear, etc. w/o much luck.
Thank you for reading. I welcome your suggestions.

It appears that all the constants are really giving you is a little cleaner, more readable code. I would recommend writing private helper functions in the class to do the same thing. Reading in and out of the NSUserDefaults is pretty quick and shouldn't present a problem. I also agree with the comments on the other answer, there is no need to synchronize. It's usually recommended against. Let the iOS system handle this for you. It's plenty capable of doing so. Syncronizing the defaults writes them to disk, it will do this when it is ready. Otherwise, it will keep and read the data from memory.
I don't see any glaring errors in your code shown, but my best guess is that at some point the defaults are getting updated and the constants have already been set. So basically, they'll never get set again once the class has been loaded.
To get the values in more concise, easily typed manner, consider a private function like so:
private func getDefaultRounds() -> Int {
return NSUserDefaults.standardUserDefaults().integerForKey("defaultRounds")
}
Which could be accessed any where in the class with just getDefaultRounds()

I have accepted Kyle's solution, but I experimented a little as a learning exercise and I'm throwing this up to see what folks think:
Rather than type the code repeatedly, remember what my key is, whether it's an Int, a Float, or a Bool, etc., I created a Struct to access the values:
struct Defaults {
let activeTime = NSUserDefaults.standardUserDefaults().integerForKey("defaultActiveTime")
let breakTime = NSUserDefaults.standardUserDefaults().integerForKey("defaultBreakTime")
let rounds = NSUserDefaults.standardUserDefaults().integerForKey("defaultRounds")
}
When I need to access the NSUserDefaults values, they're updated dynamically. It seems declaring them as a constant "snapshots" them in time, but they don't update.
When I need a default and I don't want to type them, I type:
Defaults().rounds
Defaults().activeTime
Defaults().breakTime
I haven't stumbled into any "gotchas" with this approach...yet. If anyone has suggestions on how to improve on this, I'm all ears.

Related

App crashes if playlist count = 0 (Do empty playlists have a persistentid?)

My app is crashing if a playlist is empty (no songs). My app works for all non-empty playlists. It seems like there isn't a persistentid for an empty playlist, but I think I am wrong on that.
let qryPlaylists = MPMediaQuery.playlistsQuery()
var selectedPlaylistTitle: String!
var selectedPlaylistPersistentID: UInt64!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath: NSIndexPath = playlistsTableView.indexPathForSelectedRow!
let rowItem = qryPlaylists.collections![indexPath.row]
let playlistSize = rowItem.count
print("playlistSize = ", playlistSize)
selectedPlaylistTitle = rowItem.valueForProperty(MPMediaPlaylistPropertyName) as! String
print("selectedPlaylistTitle = ", selectedPlaylistTitle)
// Will crash on this next line if the playlistSize = 0
selectedPlaylistPersistentID = rowItem.items[0].persistentID
// If I use following line instead, it will crash on any playlist
// selectedPlaylistPersistentID = rowItem.valueForProperty(MPMediaPlaylistPropertyPersistentID) as! UInt64
// This line will never be printed
print("selectedPlaylistPersistentID = ", selectedPlaylistPersistentID)
}
Thanks in advance for any help!
If an array such as items is empty, it has no index 0 and will crash if you refer to it, with an out-of-bounds error. So if you don't want to crash, don't do that. You already know that items is empty, because rowItem.count told you so; as you said yourself, playlistSize is 0 in that case.
A simple-minded way to look at it is this: the largest legal index in an array is one less than its count.
Another issue you asked about is that this line always crashes:
selectedPlaylistPersistentID = rowItem.valueForProperty(MPMediaPlaylistPropertyPersistentID) as! UInt64
The problem here is that you are apparently using Swift 2.x. (You should have said that in your question; I deduce it, though, from the fact that valueForProperty has not changed to value(forProperty:), which is what it is called in Swift 3.)
In Swift 2, you cannot cast directly down to UInt64. (I am surprised that the compiler does not draw your attention to this fact with a warning.) Thus, the cast fails and you crash. You need to cast down to NSNumber and then take that NSNumber's unsignedLongLongValue.
And while you are doing this, you really should stop using exclamation marks in your code. When I say "cast down", I mean "cast down safely". My own Swift 2 code for doing this sort of thing looks like this:
if let id = (rowItem.valueForProperty(MPMediaItemPropertyAlbumPersistentID) as? NSNumber)?.unsignedLongLongValue {
// ...
}

Putting array into label in swift

#IBAction func generateBtn(sender: UIButton) {
let strt = UInt32(strtNum.text!)
let end = UInt32(endNum.text!)
let ttlNums = Int(amtNums.text!)
let x = RandomNum()
var z = 0
while z<ttlNums{
let y = x.rndNumGen(strt!, end: end!)
z += 1
var h = [String]()
h.append(String(y))
let display:String = h.joinWithSeparator(", ")
winningNums.text = display
print (display)
}
}
I don't know what is wrong with this code. I am trying to put the string display into the label and it prints out the last number from the random number generator. When i print it to the console it shows all of the random numbers.
The primary issue here is that your array is created fresh in every loop iteration, and your label is being set in every loop iteration. That means that the array will only ever contain the element made in that iteration, after which it's reset to a new array, and a new element is added. The array needs to be initialized once at the start, and have elements added to it repeatedly in the loop, then put into the label once at the end.
#IBAction func generateBtn(sender: UIButton) {
guard let startText = strtNum.text, let start = UInt32(startText),
let endText = endNum.text, let end = UInt32(endText),
let ttlText = amtNums.text, let ttlNums = UInt32(ttlText) else {
//one of these is nil, handle it gracefully here
return
}
let randomGenerator = RandomNum()
var h = [String]()
h.reserveCapacity(ttlNums)
for _ in 0..<ttlNums {
let randomNum = randomGenerator.rndNumGen(start, end: end)
h.append(String(RandomNum))
}
let display = h.joinWithSeparator(", ")
winningNums.text = display
print(display)
}
I've made a few other changes to bring this code in line with Swift best practices and conventions:
Don't force unwrap. Use an if let or guard let binding to safely handle nil values.
Give your variables meaningful names. Avoid single-letter names except in specific instances.
Don't put spaces beside a function/method name and the proceeding brackets.
Don't use a while loop to iterate over a known range. Instead, use a for in loop.
Dn't type in t3xtspk, it mks ur code look lik an angsty teenagr wrote it. Autocomplete will finish off words for you, so you barely end up typing anyway. Make it easy and readable.
I would suggest you make a few changes yourself:
Rename generateBtn. Functions/methods DO things, they're actions. They should be named with verbs, or verb phrases. Perhaps try something like displayRandomArray.
Refactor the random array generation into its own method.
Rename RandomNum. By the looks of it, it's not a number at all, it's a random number generator. Perhaps try RandomNumberGenerator
Rename h.
Add code to deal with what happens when the .text is nil, or what happens when it contains a string that isn't a UInt32 (thus causing the UInt32 initializer to fail and return nil)

Swift: Mirror(reflecting: self) too slow?

I am trying to make a dictionary with the properties of a class of mine.
class SomeClass() {
var someString = "Hello, stackoverflow"
var someInt = 42 // The answer to life, the universe and everything
var someBool = true
func objectToDict() -> [String: String] {
var dict = [String: String]()
let reflection = Mirror(reflecting: self)
for child in reflection.children {
if let key = child.label {
dict[key] = child.value as? AnyObject
}
return dict
}
}
but objectToDict() is very slow. Is there a way to speed this up, or may be another approach to add the property values to a Dictionary?
I do not agree with most other users. Using reflection results less code, which means less time to develop, maintain, and test your product. With a well written library like EVReflection you don't need to worry about things behind the scene too much.
However, if performance is going to be a concern, do NOT use reflection based approaches at all. I'd say it's never really a problem in front-end development for me, especially in iOS, but it cannot be ignored in back-end development.
To see how slow it can be, I ran some test in Xcode. I'll write a blog about it, but generally speaking, getting Mirror is not the worst part (plus it may be possible to catch property list in memory), so replacing it with objc runtime wouldn't change the situation too much. In the other hand, setValue(_, forKey) is surprisingly slow. Considering that in real life you also need to perform tasks like checking dynamicType and so on, using the dynamic approach surely will make it 100+ times slower, which won't be acceptable for server development.
- Looping 1,000,000 times for a class with 1 `Int` property
- Getting mirror: 1.52
- Looping throw mirror and set `child.value`: 3.3
- Looping throw mirror and set `42`: 3.27
- Setting value `42`: 0.05
Again, for iOS I'll keep using it to save my time. Hopefully end customers won't care about whether it's 0.005 seconds or 0.0005 seconds.
Not only is that slow, it's also not a good idea: mirroring is for debug introspection only. You should instead construct the dictionary yourself. This ensures that you have the flexibility to store all the data in exactly the right way, and also decouples your Swift property names from the keys of the dictionary you're generating.
class SomeClass {
var someString = "Hello, stackoverflow"
var someInt = 42 // The answer to life, the universe and everything
var someBool = true
func objectToDict() -> [String: AnyObject] {
return ["someString": someString, "someInt": someInt, "someBool": someBool]
}
}

In Swift, how do I set every element inside an array to nil?

var roomsLiveStates = [Firebase?]()
for ref in roomsLiveStates {
if ref != nil {
ref = nil
}
}
}
This doesn't seem to work.
You can just set each to nil:
for index in 0 ..< roomsLiveStates.count {
roomsLiveStates[index] = nil
}
As The Swift Programming Language says in its Control Flow discussion of for syntax:
This example prints the first few entries in the five-times-table:
for index in 1...5 {
println("\(index) times 5 is \(index * 5)")
}
... In the example above, index is a constant whose value is automatically set at the start of each iteration of the loop. As such, it does not have to be declared before it is used. It is implicitly declared simply by its inclusion in the loop declaration, without the need for a let declaration keyword.
As this says, the index is a constant. As such, you can not change its value.
You can also use a map:
roomsLiveStates = roomsLiveStates.map { _ in nil }
This is less code and a good habit to get into for other cases where you may want to create a processed copy of an array without mutating the original.
if you want to set each element in array to a numberic value(int, float, double ...), you can try vDSP framework.
Please check this:
https://developer.apple.com/documentation/accelerate/vdsp/vector_clear_and_fill_functions
You can also just reassign the whole array to one that only contains nil like:
roomsLiveStates = [Firebase?](count: roomsLiveStates.count, repeatedValue: nil)
Although now that I think about it, this doesn't seem so good, because (probably?) new memory gets allocated which is not fast at all
EDIT: I just checked and found that using .map is a lot slower in Debug builds. However on Release builds, .map is about 20% faster. So I suggest using the .map version (which also is quiet a bit prettier ;)):
array = array.map { _ in nil }
Iterating the array to map it, will result in poor performance (iteration + new allocation).
Allocating the array all over (init(repeating...) is better, but still allocates a new array, very costly.
The best way would be to zero out the data without allocating it again, and as every C programmer knows, that's why we have bzero and memset for.
It won't matter much for small arrays in a non repeating action, and as long as you remember it - using the smallest code possible could make sense, so sometimes using map or init makes sense.
Final note on the test - map and init use multiple allocations of the same size in this test, which makes allocation allot faster than in real world usage.
In general, memory allocation is not your friend, it is a time consuming process that may result in having to defragment the heap, calling kernel code to allocate new virtual memory, and also must use locks and/or memory barriers.
import Foundation
guard CommandLine.argc == 2, let size = Int(CommandLine.arguments[1]),size > 0 else {
fatalError("Usage: \(CommandLine.arguments[0]) {size}\nWhere size > 0")
}
var vector = [Int].init(repeating: 2, count: size)
let ITERATIONS = 1000
var start:Date
var end:Date
start = Date()
for _ in 0..<ITERATIONS {
vector = vector.map({ _ in return 0 })
}
end = Date()
print("Map test: \(end.timeIntervalSince(start)) seconds")
start = Date()
for _ in 0..<ITERATIONS {
vector = [Int].init(repeating: 0, count: size)
}
end = Date()
print("init(repeating:,count:) test: \(end.timeIntervalSince(start)) seconds")
start = Date()
for _ in 0..<ITERATIONS {
let size = MemoryLayout.size(ofValue: vector[0]) * vector.count // could optimize by moving out the loop, but that would miss the purpose
vector.withUnsafeMutableBytes { ptr in
let ptr = ptr.baseAddress
bzero(ptr, size)
}
}
end = Date()
print("bzero test: \(end.timeIntervalSince(start)) seconds")
Results when running with an array of 5,000,000 items size (M1 Mac), compiled with optimizations:
Map test: 5.986680030822754 seconds
init(repeating:,count:) test: 2.291425108909607 seconds
bzero test: 0.6462910175323486 seconds
Edit:
Just realized it's also to initialize the memory using the initializeMemory(...) method.
Something like:
_ = vector.withUnsafeMutableBytes { ptr in
ptr.initializeMemory(as: Int.self, repeating: 0)
}
The performance is virtually the same as with bzero, and it is pure swift and shorter.

Swift convert object that is NSNumber to Double

I have this code in Swift and it works, but I would think there is a better way to get my object from NSNumber and convert it to a Double:
var rating: NSNumber
var ratingDouble: Double
rating = self.prodResult?.prodsInfo.prodList[indexPath.row].avgRating as NSNumber!!
ratingDouble = Double(rating.doubleValue)
Update
Swift's behavior here has changed quite a bit since 1.0. Not that it was that easy before, but Swift has made it harder to convert between number types because it wants you to be explicit about what to do with precision loss. Your new choices now look like this:
var rating: NSNumber
var ratingDouble: Double
ratingDouble = rating as! Double // 1
ratingDouble = Double(exactly: rating)! // 2
ratingDouble = Double(truncating: rating) // 3
ratingDouble = rating.doubleValue // 4
if let x = rating as? Double { // 5
ratingDouble = x
}
if let x = Double(exactly: rating) { // 6
ratingDouble = x
}
This calls Double._forceBridgeFromObjectiveC which calls Double(exactly:) with Double, Int64, or UInt64 based on the stored type in rating. It will fail and crash the app if the number isn't exactly representable as a Double. E.g. UInt64.max has more digits than Double can store, so it'll crash.
This is exactly the same as 1 except that it may also crash on NaN since that check isn't included.
This function always returns a Double but will lose precision in cases where 1 and 2 would crash. This literally just calls doubleValue when passing in an NSNumber.
Same as 3.
This is like 1 except that instead of crashing the app, it'll return nil and the inside of the statement won't be evaluated.
Same as 5, but like 2 will return nil if the value is NaN.
If you know your data source is dealing in doubles, 1-4 will probably all serve you about the same. 3 and 4 would be my first choices though.
Old Answer for Swift 1 and 2
There are several things you can do:
var rating: NSNumber
var ratingDouble: Double
ratingDouble = rating as Double // 1
ratingDouble = Double(rating) // 2
ratingDouble = rating.doubleValue // 3
The first item takes advantage of Objective-Cbridging which allows AnyObject and NSNumber to be cast as Double|Float|Int|UInt|Bool.
The second item presumably goes through a constructor with the signature init(_ number: NSNumber). I couldn't find it in the module or docs but passing AnyObject in generated an error that it cannot be implicitly downcast to NSNumber so it must be there and not just bridging.
The third item doesn't employ language features in the same way. It just takes advantage of the fact that doubleValue returns a Double.
One benefit of 1 is that it also works for AnyObject so your code could be:
let ratingDouble = self.prodResult!.prodsInfo.prodList[indexPath.row].avgRating! as Double
Note that I removed the ? from your function and moved the ! in. Whenever you use ! you are eschewing the safety of ? so there's no reason to do both together.

Resources