Swift getter code repetition - caching

I'm trying to have variables in swift that are critical app-wide user settings so they must be persisted to disk after every change. There is a small amount of these variables and I'm content with the first read happening from disk after the app starts.
I have code that looks similar to this:
var _myEnumMember:MyEnum?
var myEnumMember:MyEnum {
get {
if let value = _myEnumMember { // in memory
return value
}
var c:Cache = Cache()
var storedValue:MyEnum? = c.get("SomeStorageKey");
if let value = storedValue { // exists on disk
self.myEnumMember = value // call setter to persist
return self.myEnumMember // call getter again with value set
}
self.myEnumMember = .DefaultValue // assign via setter
return self.rankingDuration // call getter after `set`
}
set (newValue){
self._myEnumMember = newValue // assign to memory
var c:Cache = Cache()
c.put("SomeStorageKey", data: ser) // store in disk
}
I have about 5-6 properties that need to do this - I don't want to repeat myself over and over - is there any way to DRY this code up so I won't have to repeat this logic in several places?
(Note: Asking here and not CR.SE because I'd like answers to explain how to DRY getters/setters in these situations rather than receive critique on a particular piece of code)

I was working on something similar recently - this may be your best bet. I used this as a nested struct, but it doesn't strictly need to be nested.
First, define a LocalCache type that will handle the persistence of your properties:
struct LocalCache {
// set up keys as constants
// these could live in your class instead
static let EnumKey = "EnumKey"
static let IntKey = "IntKey"
static let StringKey = "StringKey"
// use a single cache variable, hopefully?
var cache = Cache()
// in-memory values go in a Dictionary
var localValues: [String: Any] = [:]
// fetch from local storage or from disk
// note that the default value also sets the return type
mutating func fetch<T>(key: String, defaultValue: T) -> T {
if localValues[key] == nil {
localValues[key] = cache.get(key) ?? defaultValue
}
return localValues[key]! as T
}
// save in both local storage and to disk
mutating func store(key: String, _ value: Any) {
localValues[key] = value
cache.put(key, data: value)
}
}
Then add a LocalCache instance to your class, and you can have much simpler getter/setters.
class Test {
// instance of the local cache
var localCache = LocalCache()
var enumPropery: MyEnum {
get { return localCache.fetch(LocalCache.EnumKey, defaultValue: MyEnum.DefaultValue) }
set { localCache.store(LocalCache.EnumKey, newValue) }
}
var intProperty: Int {
get { return localCache.fetch(LocalCache.IntKey, defaultValue: 0) }
set { localCache.store(LocalCache.IntKey, newValue) }
}
var stringProperty: String {
get { return localCache.fetch(LocalCache.StringKey, defaultValue: "---") }
set { localCache.store(LocalCache.StringKey, newValue) }
}
}

If you're using swift in an iOS or OS X context then NSUserDefaults are ABSOLUTELY the right way to do this.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/index.html
http://www.codingexplorer.com/nsuserdefaults-a-swift-introduction/

Related

How Can I Tell If My NSManagedObject Resides in a Read Only NSPersistentStore?

I would like to add read-only example/tutorial data to my Core Data based macOS app.
I will include an SQL file in my application bundle containing the example data. My NSPersistentContainer will have 2 NSPersistentStores, one writable and one read only. I will only have a default configuration for my model since both stores will have the same model.
My UI will need to know if the data displayed is read only or not, for example, to stop this data being draggable.
I know that NSManagedObject does not support a readonly state, see and : Is it possible to return NSManagedObjects as read-only in Core Data? ...and the docs.
I think the best approach would be to add a readonly property to my NSManagedObject derived class that can be queried where necessary. However, I can't see how I could easily set this property! I can't find a direct link to an NSPersistentStore from an NSManagedObject.
I could set up an NSFetchRequest and specify the read only store and see if the NSManagedObject is in it, but that seems a little ridiculous.
Am I missing something more obvious here please?
With thanks to pbasdf for his suggestion...
I could find no straight-forward way to achieve this. I had to move away from using NSPersistentContainer to simplify my Core Data stack. However, I think this is a fairly elegant solution if you need a small subset of your graph to be readonly.
I subclassed NSPersistentStoreCoordinator to cache the NSManagedObjectIDs of any readonly store added to it:
class GraphStoreCoordinator: NSPersistentStoreCoordinator
{
override init(managedObjectModel model: NSManagedObjectModel)
{
readOnlyTestContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
super.init(managedObjectModel: model)
readOnlyTestContext.persistentStoreCoordinator = self
NotificationCenter.default
.addObserver(forName: .NSPersistentStoreCoordinatorStoresDidChange,
object: self, queue: nil) { [unowned self] notification in
// userInfo will be in this form for add/remove keys - not supporting migration here
guard let userInfo = notification.userInfo as? [String: [NSPersistentStore]] else {
unhandledError("Invalid userInfo for NSPersistentStoreCoordinatorStoresDidChange.") }
userInfo[NSAddedPersistentStoresKey]?.forEach { self.didAddStore($0) }
userInfo[NSRemovedPersistentStoresKey]?.forEach { self.didRemoveStore($0) }
}
}
deinit {
NotificationCenter.default
.removeObserver(self, name: .NSPersistentStoreCoordinatorStoresDidChange, object: self)
}
private func didAddStore(_ store: NSPersistentStore) {
guard store.isReadOnly else { return }
var addedObjects = Set<NSManagedObjectID>()
baseEntityNames.forEach { entityName in
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: entityName)
fetchRequest.affectedStores = [store]
do {
let addedEntityObjects = try readOnlyTestContext.fetch(fetchRequest)
addedObjects = addedObjects.union(addedEntityObjects.map { $0.objectID })
} catch {
unhandledError("Failed to fetch all \(entityName) for read only check: \(error)") }
}
readOnlyObjects[store.identifier] = addedObjects
}
private func didRemoveStore(_ store: NSPersistentStore) {
guard store.isReadOnly else { return }
readOnlyObjects.removeValue(forKey: store.identifier)
}
/// Returns the minimum set of entities that can be fetched for readonly checking
private lazy var baseEntityNames: [String] = {
return managedObjectModel.entitiesByName.compactMap { $1.superentity == nil ? $0 : nil }
}()
private var readOnlyTestContext: NSManagedObjectContext
/// Readonly objectIDs keyed per persistent store
private var readOnlyObjects = [String : Set<NSManagedObjectID>]()
internal func isObjectReadOnly(_ objectID: NSManagedObjectID) -> Bool {
return readOnlyObjects.contains(where: { $1.contains(objectID) } )
}
}
I then added an extension to NSManagedObject to that queries its NSPersistentStoreCoordinator for read-only status:
public extension NSManagedObject
{
/// Does this managed object reside in a read-only persistent store?
var isReadOnly: Bool {
guard let coordinator = managedObjectContext?
.persistentStoreCoordinator as? GraphStoreCoordinator else {
unhandledError("Should only check readonly status in a GraphStoreCoordinator") }
return coordinator.isObjectReadOnly(objectID)
}
}

How to set a bool value to drive<Bool>

var readIndicatorNeedsDisplay: Driver<Bool> = .empty()
public func bindcellEvents(readNotificationID: String) {
if let unreadNotificationIDs = UserDefaults.main?.unreadNotificationIDs, unreadNotificationIDs.contains(readNotificationID) {
readIndicatorNeedsDisplay = true
} else {
UserDefaults.main?.unreadNotificationIDs.append(readNotificationID)
readIndicatorNeedsDisplay = false
// Cannot assign value of type 'Bool' to type 'Driver<Bool>' (aka 'SharedSequence<DriverSharingStrategy, Bool>')
}
}
when i assign bool to driver Giving error: Cannot assign value of type 'Bool' to type 'Driver' (aka 'SharedSequence')
You're not supposed to assign values directly to a Driver, and attempting to do so shows your fundamental misunderstanding of RxSwift. You should probably step back, and learn the basics, then come back to this problem.
However, if you want to feed values to a stream, you could use a PublishRelay:
var readIndicatorNeedsDisplay = PublishRelay<Bool>()
public func bindcellEvents(readNotificationID: String) {
if let unreadNotificationIDs = UserDefaults.main?.unreadNotificationIDs, unreadNotificationIDs.contains(readNotificationID) {
readIndicatorNeedsDisplay.accept(true)
} else {
UserDefaults.main?.unreadNotificationIDs.append(readNotificationID)
readIndicatorNeedsDisplay.accept(false)
}
}

Convert PublishRelay to BehaviorRelay of optional Element

I have the following code in RxSwift 4.0-based project:
private var _myRelay = PublishRelay<MyData>()
var myRelay: Observable<MyData> {
return _myRelay.asObservable()
}
Now I need to keep the last value (if any) in _myRelay, so I decided to convert it to BehaviorRelay:
private var _myRelay = BehaviorRelay<MyData?>(value: nil)
I want to keep public interface intact
var myRelay: Observable<MyData>
I mean I do not want to convert it to
var myRelay: Observable<MyData?>
The idea is not to "publish" initial value == nil to subscribers of myRelay, and start publishing only after some data appears. How can I do it? I am completely beginner in Rx, but I am sure there should be some elegant solution.
The simplest solution is to filter out the nils. If you were using 5.0 then you would do that with compactMap but since you said 4.0 that means you will need a filter and map:
var myRelay: Observable<MyData> {
return _myRelay.filter { $0 != nil }.map { $0! }.asObservable()
}
but you might actually be better off using a ReplaySubject instead of a Relay.
private let _myRelay = ReplaySubject<MyData>.create(bufferSize: 1)
var myRelay: Observable<MyData> {
return _myRelay.asObservable()
}
That way, you don't have to deal with nils at all. (Also note, _myRelay should be a let not a var.)
Doing the above will also allow you to emit a completed event when the observable goes out of scope (a relay doesn't allow that.)
private let _myRelay = ReplaySubject<MyData>.create(bufferSize: 1)
var myRelay: Observable<MyData> {
return _myRelay.asObservable()
}
deinit {
_myRelay.onCompleted()
}

Adding a property to an existing enum in Kotlin

Given the following enum defined in an external api.
public enum Status {
COMPLETE,
RUNNING,
WAITING
}
I would like a way to add a int flag to each enum value. I know that I can extend the enum:
fun Status.flag(): Int {
when(this) {
RUNNING -> return 1;
WAITING -> return 2;
else -> return 0;
}
}
However I would like to define those int flag values as constants. Maybe a companion object, but I don't think I can extend an existing enum and add a companion object.
Any ideas?
Unless you are using a field that already exists in the original enum (like ordinal), you won't be able to do what you're asking without wrapping the external enum in your own enum.
Sure you could use ordinal, but a newer version of the external API may change the order of the items in the enum, so I wouldn't recommend it. But, if you REALLY want to, you could do something like this (again, this is NOT recommended):
val Status.flag: Int
get() = this.ordinal
But I'd definitely recommend wrapping it. That way you guarantee that the flag integers you define won't change.
enum class MyStatus(val status: Status, val flag: Int) {
COMPLETE(Status.COMPLETE, 0),
RUNNING(Status.RUNNING, 1),
WAITING(Status.WAITING, 2);
companion object {
private val STATUS_TO_MYSTATUS = values().associateBy { it.status }
fun fromStatus(status: Status): MyStatus {
return STATUS_TO_MYSTATUS[status] ?: throw Exception("No MyStatus found for status ${status.name}")
}
}
}
You can then convert Status to MyStatus by using MyStatus.fromStatus(...). Or you can add an extension function to Status to easily convert to MyStatus.
fun Status.toMyStatus() = MyStatus.fromStatus(this)
You can add extension properties/methods to the companion object of enum/class/etc. if one exists:
val Status.Companion.COMPLETE_INT = 0
val Status.Companion.RUNNING_INT = 1
but indeed you can't currently "create" the companion object if it doesn't. So just put the constants into your own non-companion object:
object StatusFlags {
const val COMPLETE_INT = 0
const val RUNNING_INT = 1
const val WAITING_INT = 2
}
fun Status.flag(): Int {
when(this) {
RUNNING -> return StatusFlags.RUNNING_INT
...
}
}

are needsDisplayForKey/actionForKey overrides working correctly?

I'm trying to convert some code that works in Objective-C to Swift. The problem I'm running into is that needsDisplayForKey/actionForKey aren't getting called the same way. As far as I can tell, the custom key values aren't getting passed in correctly. Here is what I get when I debug it:
default value:
(String!) event = {
core = {
_baseAddress = Builtin.RawPointer = 0x00feee51 "onOrderIn"
_countAndFlags = 1073741833
_owner = Some {
Some = (instance_type = Builtin.RawPointer = 0x01026348 #"onOrderIn")
}
}
}
custom value (empty string passed in):
(String!) event = {
core = {
_baseAddress = Builtin.RawPointer = 0x0b418f79
_countAndFlags = 1073741833
_owner = Some {
Some = (instance_type = Builtin.RawPointer = 0x0b418f70 -> 0x006e38f0 (void *)0x006e38c8: __NSCFString)
}
}
}
I'm not sure what the relevant code might be. I'll just ask - has anyone else was able to define a custom implicit animation in Swift? Is there anything I need to keep in mind when moving over from Objective C?
override class func needsDisplayForKey(key: String!) -> Bool{
if key == "angleFrom" || key == "angleTo" {
return true;
}
return super.needsDisplayForKey(key)
}
override func actionForKey(event: String!) -> CAAction!{
if event == "angleFrom" || event == "angleTo" {
return self.makeAnimationForKey(event)
}
return super.actionForKey(event)
}
I got this working in Swift by using #NSManaged attribute in front of the variable declaration (where you would use the the #dynamic attribute in Objective-C)

Resources