Storing OpaquePointer type in UserDefaults - ios

I am working on Xcode 12 and Swift 5 environment to build an iOS application.
I need to store an OpaquePointer type variable("self.loggers" in the code below) before the view disappears(or before the app closes) and retrieve it when the view appears( when the app runs).
I tried to use UserDefault as below,
// Storing(In viewWillDisappear)
do {
encodedData = try NSKeyedArchiver.archivedData(withRootObject: self.loggers, requiringSecureCoding: false)
UserDefaults.standard.set(encodedData, forKey: "accelLoggerID")
} catch {
print("archiving error")
}
...
// Retrieving(In viewDidLoad)
if let decodedData = UserDefaults.standard.object(forKey: "acceleration") as? Data {
do {
self.loggers = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decodedData) as? [String:OpaquePointer] ?? [:]} catch {
print("unarchiving error")
}
} else {
self.loggers = [:]
print("No value in Userdefault. [viewDidLoad]")
}
However, NSKeyedArchiver failed to encode such type. After that, I made a class that wraps the variable.
class LoggerWrapper {
var loggers: [String : OpaquePointer]
init(loggerDic: [String : OpaquePointer]) {
loggers = loggerDic
}
}
And changed my code like
self.loggers = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decodedData) as? LoggerWrapper ?? LoggerWrapper(loggerDic: [:])} catch {
print("unarchiving error")
}
However, this gives SIGABRT when NSKeyedArchiver.archivedData is called.
Is there any way to store Opaquepointer type in UserDefaults? If not, can using core data solve this problem?
Thank you.

You probably need to convert the pointers to Int bitPatterns. There is an initializer on Int for that: let intBitPattern = Int(bitPattern: opaquePointer).
There is also an initializer on OpaquePointer that takes an Int bitPattern: let opaquePointer = OpaquePointer(bitPattern: intBitPattern).
So you would need to convert the values in your dictionary (e.g. using compactMapValues) before storing and after reading it.
With a Dictionary<String, Int>, you should be able to store it directly in UserDefaults, without the need for NSKeyedArchiver and NSKeyedUnarchiver.
Sidenote:
Pointers aren't "long-lived". So storing it in UserDefaults is only valid during the lifetime of your app. Once your app restarts, the pointers might no longer be valid.

Related

Update Plist data without erasing old data

I'm trying to make a file downloader application using swift and cocoa. I am using plist for the download history. Reading the data works however, writing the data will erase the previous data and replace it for the new data.
Here is the code
let newdownloaditem = downloadList(root: [downloadListt(downloadURL: response.url!.absoluteString, fileName: response.suggestedFilename!)])
// This is a codeable method
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let pListFilURL = uniqueDataDir()?.appendingPathComponent("downloads.plist")
do {
let data = try encoder.encode(newdownloaditem)
try data.write(to: pListFilURL!)
// Here is the problem
} catch {
print(error)
}
// Here is the codeable
public struct downloadList: Codable {
let root: [downloadListt]
}
public struct downloadListt: Codable {
let downloadURL: String
let fileName: String
}
Here is an image of what happens
The contents got erased
Thanks!
You are indeed replacing the previous data with your new one.
You need to retrieve the previous data.
Append your new data to it.
Save that combination
let newItem = downloadListt(downloadURL: response.url!.absoluteString,
fileName: response.suggestedFilename!)
var allItems: [downloadListt] = []
allItems.append(contentsOf: previousList.root)
allitems.append(newItem)
let newList = downloadList(root: allItems)
...
let data = try encoder.encode(newList)
try data.write(to: pListFilURL!)
Unrelated but recommended (it's convention):
You should starting naming your struct/classes with an uppercase: downloadList => DownloadList
I would avoid naming downloadListt, it's unredable it's hard at first look to make a difference between downloadListt and downloadList. Instead, name it maybe DownloadItem. More readable.

How to find out if the "NSUbiquitousKeyValueStore.default.set" result was successful

I am writing data from my application to iCloud as follows. How to determine whether this process was successful?
NSUbiquitousKeyValueStore.default.set("data", forKey: "key")
NSUbiquitousKeyValueStore.default.synchronize()
Reference: https://developer.apple.com/documentation/foundation/nsubiquitouskeyvaluestore
You should be able to get the value after setting it to see if it was set correctly.
I think it is important to note that your data is actually a string so you would use the string() function of NSUbiquitousKeyValueStore.default to get the value. The function required to get the stored value will change depending on the value's variable type.
// Set value
let data: String = "data"
NSUbiquitousKeyValueStore.default.set(data, forKey: "key")
NSUbiquitousKeyValueStore.default.synchronize()
// Try to get value
if let dataString = NSUbiquitousKeyValueStore.default.string(forKey: "key") {
if dataString == data {
// The data stored correctly
} else {
// Handle condition if data is not stored
}
}

How to get ObjectID and search for specific ObjectID in CoreData in Swift 5?

I am currently working on a project with a multi user system. The user is able to create new profiles which are saved persistently using CoreData.
My problem is: Only one profile can be the active one at a single time, so I would like to get the ObjectID of the created profile and save it to UserDefaults.
Further I was thinking that as soon as I need the data of the active profile, I can simply get the ObjectID from UserDefaults and execute a READ - Request which only gives me back the result with that specific ObjectID.
My code so far for SAVING THE DATA:
// 1. Create new profile entry to the context.
let newProfile = Profiles(context: context)
newProfile.idProfileImage = idProfileImage
newProfile.timeCreated = Date()
newProfile.gender = gender
newProfile.name = name
newProfile.age = age
newProfile.weight = weight
// 2. Save the Object ID to User Defaults for "activeUser".
// ???????????????????
// ???????????????????
// 3. Try to save the new profile by saving the context to the persistent container.
do {
try context.save()
} catch {
print("Error saving context \(error)")
}
My code so far for READING THE DATA
// 1. Creates an request that is just pulling all the data.
let request: NSFetchRequest<Profiles> = Profiles.fetchRequest()
// 2. Try to fetch the request, can throw an error.
do {
let result = try context.fetch(request)
} catch {
print("Error reading data \(error)")
}
As you can see, I haven't been able to implement Part 2 of the first code block. The new profile gets saved but the ObjectID isn't saved to UserDefaults.
Also Party 1 of the second code block is not the final goal. The request just gives you back all the data of that entity, not only the one with the ObjectID I stored in User Defaults.
I hope you guys have an idea on how to solve this problem.
Thanks for your help in advance guys!
Since NSManagedObjectID does not conform to one of the types handled by UserDefaults, you'll have to use another way to represent the object id. Luckily, NSManagedObjectID has a uriRepresentation() that returns a URL, which can be stored in UserDefaults.
Assuming you are using a NSPersistentContainer, here's an extension that will handle the storage and retrieval of a active user Profile:
extension NSPersistentContainer {
private var managedObjectIDKey: String {
return "ActiveUserObjectID"
}
var activeUser: Profile? {
get {
guard let url = UserDefaults.standard.url(forKey: managedObjectIDKey) else {
return nil
}
guard let managedObjectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: url) else {
return nil
}
return viewContext.object(with: managedObjectID) as? Profile
}
set {
guard let newValue = newValue else {
UserDefaults.standard.removeObject(forKey: managedObjectIDKey)
return
}
UserDefaults.standard.set(newValue.objectID.uriRepresentation(), forKey: managedObjectIDKey)
}
}
}
This uses a method on NSPersistentStoreCoordinator to construct a NSManagedObjectID from a URI representation.

Mock UserDefaults Object In Unit Test Returning _ArrayBuffer

I'm trying to remove dependencies to OS objects like URLSessions and UserDefaults in my unit tests. I am stuck trying to mock pre-cached data into my mock UserDefaults object that I made for testing purposes.
I made a test class that has an encode and decode function and stores mock data in a member variable which is a [String: AnyObject] dictionary. In my app, on launch it will check the cache for data and if it finds any, a network call is skipped.
All I've been able to get are nil's or this one persistent error:
fatal error: NSArray element failed to match the Swift Array Element
type
Looking at the debugger, the decoder should have return an array of custom type "Question". Instead I get an _ArrayBuffer object.
What's also weird is if my app loads data into my mock userdefaults object, it works fine, but when I hardcode objects into it, I get this error.
Here is my code for the mock UserDefaults object:
class MockUserSettings: DataArchive {
private var archive: [String: AnyObject] = [:]
func decode<T>(key: String, returnClass: T.Type, callback: (([T]?) -> Void)) {
print("attempting payload from mockusersettings with key: \(key)")
if let data = archive[key] {
callback(data as! [T])
} else {
print("Found nothing for: \(key)")
callback(nil)
}
}
public func encode<T>(key: String, payload: [T]) {
print("Adding payload to mockusersettings with key: \(key)")
archive[key] = payload as AnyObject
}
}
and the test I'm trying to pass:
func testInitStorageWithCachedQuestions() {
let expect = XCTestExpectation(description: "After init with cached questions, initStorage() should return a cached question.")
let mockUserSettings = MockUserSettings()
var questionsArray: [Question] = []
for mockQuestion in mockResponse {
if let question = Question(fromDict: mockQuestion) {
questionsArray.append(question)
}
}
mockUserSettings.encode(key: "questions", payload: questionsArray)
mockUserSettings.encode(key: "currentIndex", payload: [0])
mockUserSettings.encode(key: "nextFetchDate", payload: [Date.init().addingTimeInterval(+60)])
let questionStore = QuestionStore(dateGenerator: Date.init, userSettings: mockUserSettings)
questionStore.initStore() { (question) in
let mockQuestionOne = Question(fromDict: self.mockResponse[0])
XCTAssertTrue(question == mockQuestionOne)
XCTAssert(self.numberOfNetworkCalls == 0)
expect.fulfill()
}
wait(for: [expect], timeout: 1.0)
}
If someone could help me wrap my head around what I''m doing wrong it would be much appreciated. Am I storing my mock objects properly? What is this ArrayBuffer and ArrayBridgeStorage thing??
I solved my problem. My custom class was targeting both my app and tests. In the unit test, I was using the test target's version of my class constructor instead of the one for my main app.
So lesson to take away from this is just use #testable import and not to have your app classes target tests.

Checking if NSData exist or not in swift

I have an NSData object stored in my core data. I want to be able to check if it exist or not using an if statement. I can't seem to work it out. I have the variable set up as:
var coreImage : NSData?
and I have tried using:
if (coreImage != nil) {
println("Use core image")
}else {
println("Do something else")
}
I know I have NSData stored in core data but it never runs the if statement as I want it too so I must be doing something wrong?
Can anyone help?
Thanks.
Check if NSData's length is > 0, that's it.
Example:
if (coreImage.length > 0) {
//we're cool
}
I am newer to Swift than you, but I think you need to do some special work to access and utilize items from Core Data. Try something like this:
let fetchRequest = NSFetchRequest(entityName: "coreImage")
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSData] {
println("Use core image")
} else {
println("Do something else")
}

Resources