Core Data entries not updating correctly in swift3 - core-data

I have a core data entries with certain keys and values stored in the database fetched from API . Now i want to update some core data entries with the updated values So for this i am using the code as below:
func updateAllRecords(responseArray: [Dictionary<String, String>]) {
for settingsObject in responseArray {
if #available(iOS 10.0, *) {
let keys = settingsObject.flatMap() { $0.0 as? String }
let values = settingsObject.flatMap(){ $0.1 as? String}
let request = NSFetchRequest<ApplicationSettings>(entityName: "ApplicationSettings")
do {
let context = persistentContainer.viewContext
let searchResults = try context.fetch(request)
for settingsKeys in searchResults {
if keys.contains(settingsKeys.key!) {
settingsKeys.value = values[1]
try context.save()
}
}
} catch {
print ("There was an error")
}
} else {
}
}
}
}
I am calling this function from viewcontroller like this:
var dicArray = [Dictionary<String, String>]()
let dic1 = ["IS_CIVIL_ID_MANDATORY": "Smith", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert"]
dicArray.append(dic1)
CoreDataHandler.sharedInstance.updateAllRecords(responseArray: dicArray)
But the entries in the table is not updating correctly for the keys. It is storing the same value for both the keys. See below:
Where i am wrong in this code? Why same values are stored for the keys?

You made mistake here
settingsKeys.value = values[1] here 1 is static
I think you don't require values array separate
just replace this code with my code
for settingsKeys in searchResults {
if keys.contains(settingsKeys.key!)
{
settingsKeys.updateValue(settingObject[settingsKeys.key!], forKey:settingsKeys.key!)
try context.save()
}
}
}
For Demo Example
func updateAllRecords(responseArray: [Dictionary<String, String>])
{
for settingsObject in responseArray
{
var dic2 = ["IS_CIVIL_ID_MANDATORY": "xyz", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "xyz"]
let keys = settingsObject.flatMap() {$0.0}
let values = settingsObject.flatMap(){$0.1}
let settingKeys = dic2.flatMap() {$0.0}
for settingsKey in settingKeys
{
dic2.updateValue(settingsObject[settingsKey]!, forKey: settingsKey)
}
print(keys)
print(dic2)
}
}
var dicArray = [Dictionary<String, String>]()
let dic1 = ["IS_CIVIL_ID_MANDATORY": "Smith", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert"]
dicArray.append(dic1)
updateAllRecords(responseArray: dicArray)
Output
["TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert", "IS_CIVIL_ID_MANDATORY": "Smith"]

Related

Publish background context Core Data changes in a SwiftUI view without blocking the UI

After running a background-context core data task, Xcode displays the following purple runtime warning when the updates are published in a SwiftUI view:
"[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates."
Besides the ContentView.swift code below, I also added container.viewContext.automaticallyMergesChangesFromParent = true to init in the default Persistence.swift code.
How can I publish the background changes on the main thread to fix the warning? (iOS 14, Swift 5)
Edit: I've changed the code below, in response to the first answer, to clarify that I'm looking for a solution that doesn't block the UI when a lot of changes are saved.
struct PersistenceHelper {
private let context: NSManagedObjectContext
init(context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
self.context = context
}
public func fetchItem() -> [Item] {
do {
let request: NSFetchRequest<Item> = Item.fetchRequest()
var items = try self.context.fetch(request)
if items.isEmpty { // Create items if none exist
for _ in 0 ..< 250_000 {
let item = Item(context: context)
item.timestamp = Date()
item.data = "a"
}
try! context.save()
items = try self.context.fetch(request)
}
return items
} catch { assert(false) }
}
public func updateItemTimestamp(completionHandler: #escaping () -> ()) {
PersistenceController.shared.container.performBackgroundTask({ backgroundContext in
let start = Date(), request: NSFetchRequest<Item> = Item.fetchRequest()
do {
let items = try backgroundContext.fetch(request)
for item in items {
item.timestamp = Date()
item.data = item.data == "a" ? "b" : "a"
}
try backgroundContext.save() // Purple warning appears here
let interval = Double(Date().timeIntervalSince(start) * 1000) // Artificial two-second delay so cover view has time to appear
if interval < 2000 { sleep(UInt32((2000 - interval) / 1000)) }
completionHandler()
} catch { assert(false) }
})
}
}
// A cover view with an animation that shouldn't be blocked when saving the background context changes
struct CoverView: View {
#State private var toggle = true
var body: some View {
Circle()
.offset(x: toggle ? -15 : 15, y: 0)
.frame(width: 10, height: 10)
.animation(Animation.easeInOut(duration: 0.25).repeatForever(autoreverses: true))
.onAppear { toggle.toggle() }
}
}
struct ContentView: View {
#State private var items: [Item] = []
#State private var showingCoverView = false
#State private var refresh = UUID()
let persistence = PersistenceHelper()
let formatter = DateFormatter()
var didSave = NotificationCenter.default
.publisher(for: .NSManagedObjectContextDidSave)
// .receive(on: DispatchQuene.main) // Doesn't help
var body: some View {
ScrollView {
LazyVStack {
Button("Update Timestamp") {
showingCoverView = true
persistence.updateItemTimestamp(completionHandler: { showingCoverView = false })
}
ForEach(items, id: \.self) { item in
Text(formatter.string(from: item.timestamp!) + " " + (item.data ?? ""))
}
}
}
.id(refresh)
.onAppear {
formatter.dateFormat = "HH:mm:ss"
items = persistence.fetchItem()
}
.onReceive(didSave) { _ in
items = persistence.fetchItem()
}
.fullScreenCover(isPresented: $showingCoverView) {
CoverView().onDisappear { refresh = UUID() }
}
}
}
Since you are performing a background task, you are on a background thread - rather than the main thread.
To switch to the main thread, change the line producing the runtime warning to the following:
DispatchQueue.main.async {
try backgroundContext.save()
}
You should use Combine and observe changes to your background context and update State values for your UI to react.
#State private var coreDataAttribute = ""
var body: some View {
Text(coreDataAttribute)
.onReceive(
CoreDataManager.shared.moc.publisher(for: \.hasChanges)
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.global())
.map{_ in CoreDataManager.shared.fetchCoreDataValue()}
.filter{$0 != coreDataAttribute}
.receive(on: DispatchQueue.main))
{ value in
coreDataAttribute = value
}
}

Nesting URLSession.shared.dataTask in Swift 4

I am trying to fetch data from an api where the JSON returned has URLs to other pieces of information that I need, such as
"value1" : "data",
"value2": {
"url": "https://example.com/stuff",
}
My logic is as follows:
func(completion: #escaping ([Data]) -> ()) {
var classArray = [myClass]()
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
guard let resultArray = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
let myObject = myClass(value1: resultArray["value1"]! as! String)
guard let valueUrl = URL(string: resultArray["value2"]! as! String) else { return }
URLSession.shared.dataTask(with: valueUrl) { (data, _, _) in
myObject.value2 = data
classArray.append(myObject)
}.resume()
} catch let error {
print("Failed to create json with error: ", error.localizedDescription)
}
completion(classArray)
}.resume()
}
}
Is this a valid approach or are there better implementations? Trying to avoid a future Pyramid of Doom situation. I have tried putting the inner URLSession call in a separate private function but still receive an empty classArray in the end.

I want to acess the value of this json object without using the key name

{
"callExpDateMap": {
"2020-03-06:2": {
"305.0": [
{
"putCall": "CALL,
}
]
}
}
}
So this is my json object.And As you can see in "callExpDateMap",there is a date("2020-03-06:2" and then there is a value("305.0").The date("2020-03-06:2") and price("305.0") values will not be the same in every response.So If I want to access the value of "putCall" (Please remember that I can't use the key of the date and the price because it keeps changing in every response), How can I do this?...I'm using nodejs.
You need to pass this JSON to a function and call that function recursively with each inner object (or value of JSON key) and when value is string just print it or use in your way.
forEach(Object.keys(jsonobj))
function myfunc(key){
if((typeof jsonobj[key])==object){
forEach(Object.keys(jsonobj[key]));
}else{
console.log(jsonobj[key])
}
}
}
You could loop over the object and get the value,
Example:
let test = {
callExpDateMap: {
"2020-03-06:2": {
"305.0": [
{
putCall: "CALL"
}
]
}
}
};
let callExpDateMap = test.callExpDateMap;
for(const dateValue in callExpDateMap) {
for(const put in callExpDateMap[dateValue]) {
console.log(callExpDateMap[dateValue][put][0]['putCall']);
}
}
or you could use Object.keys to get the value. it's simpler then the previous approach;
let obj = {
callExpDateMap: {
"2020-03-06:2": {
"305.0": [
{
putCall: "CALL"
}
]
}
}
};
let callExpDateMap = obj.callExpDateMap;
let dateKey = Object.keys(callExpDateMap);
let price = Object.keys(callExpDateMap[dateKey]);
let putCall = callExpDateMap[dateKey][price];
console.log(putCall);

Deeply nested data objects in multidimensional object

I have a multidimensional object and using Vue, I am trying to make the inner object reactive.
My object looks like this:
data() {
return {
myObject: {}
}
}
And the filled data looks like this:
myObject: {
1: { // (client)
0: "X", // (index) : (value)
1: "Y"
},
2: {
0: "A",
2: "B"
}
}
If I try using:
let value = "X";
let client = 1;
let index = 1;
let obj = {};
obj[client][index] = value;
this.myObject = Object.assign({}, this.myObject, obj);
It throws an error:
TypeError: Cannot set property '0' of undefined
And if I try below, it overwrites the initial values as it is initially setting the object to {}
let obj = {};
obj[index] = value;
let parentObj = {};
parentObj[client] = obj;
this.myObject = Object.assign({}, this.myObject, parentObj);
What is the proper way of adding the values to the multidimensional object?
In javascript, dim2Thing[1][1] = ... expressions require dim2Thing[1] to exist. This is why you get the error you mentioned. So you can do two expressions, which should work fine:
dim2Thing[1] = dim2Thing[1] || {}
dim2Thing[1][1] = otherThing
For the last block, you mention that it "overwrites the initial values"
I think what's actually happening here is just that Object.assign is not recursive. It only merges top-level keys. So if parentObj has a key that over-laps with this.myObj, then sub-keys will be lost.
Object.assign({ a: { b: 2} }, { a: { c: 3 } }) // returns { a: { c: 3 } }
This is what I interpret your code as trying to do - though I am unfamiliar with vue.js at this time, so I cannot assure it will have the desired result to your webpage:
let value = "X";
let client = 1;
let index = 1;
const newObj = Object.assign({}, this.myObject);
// if you have lodash _.set is handy
newObj[client] = newObj[client] || {}; // whatever was there, or a new object
newObj[client][index] = value
this.myObject = newObj
Just use an array, thats reactive by design.
If you need to get elements from the array in your template or anywhere just add a find method
// temp
late
<div v-for="(value, idx) in myArray">{{find(obj => obj.id === idx)}}</div>
methods: {
find (searchFunction) {
return this.myArray.find(searchFunction)
}
}

(Swift.LazyMapCollection<Swift.Dictionary<Swift.String, Swift.String>(_base:[ ]

The code says that it is perfectly fine and that there are no errors, but when I go to run the simulator, the words will include:
(Swift.LazyMapCollection < Swift.Dictionary < Swift.String, Swift.String > (_base:[ ]
I am trying to create a quote app that displays a quote.
Here is the code for the Import of the Plist:
import Foundation
struct ImportList {
let path: String
init(FileName: String) {
self.path = NSBundle.mainBundle().pathForResource("\(FileName)", ofType:"plist")!
}
var dict: Dictionary<String, String> {
return NSDictionary(contentsOfFile: path)! as! Dictionary
}
var array: Array<AnyObject> {
return [String](arrayLiteral: String(dict.keys) { $0 as String})
}
func count() -> Int {
return array.count
}
}
Thank you.
Don't use arrayLiteral in this case, just use Array():
var array: Array<AnyObject> {
return Array(dict.keys)
}
It safely converts the lazy collection to an actual array.

Resources