How do u save Array of object to Realm, Swift? - swift

I'm new to Swift Realm. Here's my method to save single object.
let realm = try! Realm()
func savePerson(_ person: Person){
do{
try realm.write {
realm.add(person)
}
}catch{
print(error.localizedDescription)
}
}
Is there any method to save Array?
func savePersonList(_ personList: []){
do{
try realm.write{
// HOW??
}
}catch{
}
}

Related

Thread 1: Exception: "Realm accessed from incorrect thread."

I have two views. The first one shows a list of the custom objects made of downloaded data, the second shows a list of objects that are the basis for objects from the first list.
If I choose an object in the second view to save in Realm and go back to the first one, the data to make a list of custom objects is downloaded from database. If I want to delete that object the app crash and this message appears:
Thread 1: Exception: "Realm accessed from incorrect thread."
The same situation is when I delete one, two or more objects in the first screen, go to another one, choose one, two, or more to save in the database and go back to the first one, where data is downloaded from database. App is crashing and same message.
I know it's all about threads, but I don't know how to resolve that.
I tried resolving that by DispatchQueue, but it doesn't work, or i'm doing it wrong. How to resolve this thread problem in my case?
These database functions are using in the first view:
func deleteAddItem(addItem: AddItem) throws {
do {
let realm = try! Realm()
try! realm.write {
if let itemToDelete = realm.object(ofType: AddItem.self, forPrimaryKey: addItem.id) {
realm.delete(itemToDelete)
realm.refresh()
}
}
}
}
}
func fetchStations() throws -> Results<Station> {
do {
realm = try Realm()
return realm!.objects(Station.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
func fetchSensors() throws -> Results<Sensor> {
do {
realm = try Realm()
return realm!.objects(Sensor.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
func fetchAddItems() throws -> Results<AddItem> {
do {
realm = try Realm()
return realm!.objects(AddItem.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
func fetchData() throws -> Results<Data> {
do {
realm = try Realm()
return realm!.objects(Data.self)
}
catch {
throw RuntimeError.NoRealmSet
}
}
If you want more code or information, please let me know.
It appears you have two different Realm threads going
func deleteAddItem(addItem: AddItem) throws {
do {
let realm = try! Realm() <- One realm thread, a local 'let'
and then
func fetchAddItems() throws -> Results<AddItem> {
do {
realm = try Realm() <- A different realm thread, a class var?
that can probably be fixed by using the same realm when deleting
func deleteAddItem(addItem: AddItem) throws {
do {
realm = try! Realm() <- references the same realm as in delete
There are few options to prevent this. Once is simply get the realm, every time you want to use it
let realm = try! Realm()
realm.read or realm.write etc
or create a singleton function (or a RealmService class) that can be accessed throughout the app. In a separate file (or whever you want to put it and this is just a quick example)
import RealmSwift
func gGetRealm() -> Realm? {
do {
let realm = try Realm()
return realm
} catch let error as NSError { //lots of error handling!
print("Error!")
print(" " + error.localizedDescription)
let err = error.code
print(err)
let t = type(of: err)
print(t)
return nil
}
}
Then to use it
if let realm = gGetRealm() {
realm.read, realm.write etc
}
Also, I noticed you appear to be getting an item from realm to just then delete it. That's not necessary.
If the item is already managed by Realm, it can just be deleted directly. Here's an updated delete function
func deleteItem(whichItem: theItemToDelete) {
if let realm = gGetRealm() {
try! realm.write {
realm.delete(theItemToDelete)
}
}
}

Writing data to a file with path using NSKeyedArchiver archivedData throws Unrecognized Selector for Swift 4.2

I am attempting to use the NSKeyedArchiver to write a Codable to disk.
All the questions I could find on the subject using deprecated methods. I can't find any SO questions or tutorials using the Swift 4 syntax.
I am getting the error
-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance
Which I am guessing is the try writeData.write(to: fullPath) line in my UsersSession class.
What is the proper way to write data to a file in Swift 4.2?
struct UserObject {
var name : String?
}
extension UserObject : Codable {
enum CodingKeys : String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
}
}
UserSession.swift
class UserSession {
static let shared = UserSession()
let fileName = "userdata.dat"
var user : UserObject?
lazy var fullPath : URL = {
return getDocumentsDirectory().appendingPathComponent(fileName)
}()
private init(){
print("FullPath: \(fullPath)")
user = UserObject()
load()
}
func save(){
guard let u = user else { print("invalid user data to save"); return}
do {
let writeData = try NSKeyedArchiver.archivedData(withRootObject: u, requiringSecureCoding: false)
try writeData.write(to: fullPath)
} catch {
print("Couldn't write user data file")
}
}
func load() {
guard let data = try? Data(contentsOf: fullPath, options: []) else {
print("No data found at location")
save()
return
}
guard let loadedUser = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UserObject else {
print("Couldn't read user data file.");
return
}
user = loadedUser
}
func getDocumentsDirectory() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
}
Since you are using Codable, you should first encode to Data and then archivedData. Here is the code:
func save(){
guard let u = user else { print("invalid user data to save"); return}
do {
// Encode to Data
let jsonData = try JSONEncoder().encode(u)
let writeData = try NSKeyedArchiver.archivedData(withRootObject: jsonData, requiringSecureCoding: false)
try writeData.write(to: fullPath)
} catch {
print("Couldn't write user data file")
}
}
func load() {
guard let data = try? Data(contentsOf: fullPath, options: []) else {
print("No data found at location")
save()
return
}
guard let loadedUserData = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Data else {
print("Couldn't read user data file.");
return
}
// Decode Data
user = try? JSONDecoder().decode(UserObject.self, from: loadedUserData)
}

Is there a way to use my array of type Music, in another scope?

I'm attempting to print/dump and array of type Music outside of a function it's created in. I can successfully dump the musicItems array inside of the getMusicData function but when I set the musicItems array outside of the scope, it won't print anything. What am I doing wrong with the scope here? I have a feeling it's super simple but I just can't figure it out. Thanks in advance for taking the time to read this.
edit: It's giving me "0 elements" in the console when I attempt to dump the musicItems array in the ViewController class. Well, the function is in the same class as well so I guess I don't know what to call the first array. The parent array?
struct MusicResults: Decodable {
let results: [Music]?
}
struct Music: Decodable {
let trackName: String?
let collectionName: String?
let artworkUrl30: String?
}
class ViewController: UITableViewController, UISearchBarDelegate {
var musicItems: [Music] = []
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData()
dump(musicItems)
}
Here is the function.
func getMusicData() {
var musicItems: [Music] = []
guard let searchTerm = searchString else {return}
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
// print(results.trackName!)
musicItems.append(results)
}
//dump(musicItems)
self.musicItems = musicItems
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
Fixed Code
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData {
music in
dump(music)
}
function:
func getMusicData(completionHandler: #escaping (_ music: [Music]) -> ()) {
...
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
musicItems.append(results)
}
completionHandler(musicItems)
...
Your 'getMusicData' function is asynchronous which means that when it executes, it queues data task in a background queue and proceeds the execution and since there are no more institutions it simply returns control to its calling site - 'musicButton()' action, which in its turn executes the next instruction - prints the 'musicItems' array which might (and most likely, is) still not populated as the network call haven’t yet completed. One of the options that you have here is to pass a completion block to your 'getMusicData' function, that runs it after data task gets the results.
Another option is to use Property Observers
var musicItems: [Music] = [] {
didSet {
dump(self.musicItems)
/// This is where I would do the...
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
and then
func getMusicData() {
guard let searchTerm = searchString else { print("no search"); return }
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { print("url error"); return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { print(err ?? "unknown"); return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
if let results = music.results {
self.musicItems.append(contentsOf: results)
}
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}

Deleting from Core Data Swift 4

I have a Favorites button that when clicked will add the image of the particular character to CoreData.
#IBAction func favButtonClicked(_ sender: UIButton) {
if sender.isSelected != true {
saveFav()
}
}
func saveFav() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newFav = NSEntityDescription.insertNewObject(forEntityName: "Favorite", into: context)
newFav.setValue((figure?.image)!, forKey: "favFig")
do {
try context.save()
print("Saved!")
} catch {
//Process Error
}
}
My question is how do I remove that image from CoreData when clicking the button again?
In coredata every object should have Id if you want to delete it , like this
let fetchRequest: NSFetchRequest<Favorite> = Favorite.fetchRequest()
fetchRequest.predicate = Predicate.init(format: "FavoriteID==\(ID)")
do {
let objects = try context.fetch(fetchRequest)
for object in objects {
context.delete(object)
}
try context.save()
} catch _ {
// error handling
}

Swift realm return zero objects

I have opened my realm database in Realm browser, i can see that there is are actual data (10 entities).
But when i call print("realm objects \(self.realm.objects(CharacterModel.self))")
Result is empty:
realm objects Results<CharacterModel> <0x7f8d8f204a30> (
)
When i put breakpoint and check data base state at this moment data exist. Why is that happening?
realm is declared like that:
static func realm() -> Realm{
do {
let realm = try Realm()
return realm
} catch let error as NSError {
fatalError("Error opening realm: \(error)")
}
}
The answer may reveal itself if we eliminate some variables:
The following code works with a Realm that contains Person() objects
func doPrintData() {
do {
let realm = try Realm()
print("realm objects \(realm.objects(Person.self))")
} catch let error as NSError {
print(error.localizedDescription)
}
}
the following also works
func realm() -> Realm{
do {
let realm = try Realm()
return realm
} catch let error as NSError {
fatalError("Error opening realm: \(error)")
}
}
func doPrintData() {
do {
let realm = self.realm()
print("realm objects \(realm.objects(Person.self))")
} catch let error as NSError {
print(error.localizedDescription)
}
}
There's probably more code involved but try one of the above solutions and see if it makes a difference.