What is Good Practice 'Error Handling' for Swift 4? - swift

Please could someone guide me on a good practice using 'Error Handling' approaches with my 'working' (Swift 4) code below... (eg: 'Guards', 'Do', 'Catch', Throw', 'if', 'else')...
//// Import ////
import UIKit
import CoreData
//// Custom Function ////
func insertCoreData(){
if let coreData = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext {
let data = MyEntity(entity: MyEntity.entity(), insertInto: coreData)
data.name = "Joe Blogs"
data.country = "United Kingdom"
try? coreData.save()
print("Data Saved...")
}
}
//// Output Function ///
insertCoreData() // Working and Gives

You're heading for a lot of trouble if you're going to use ´try?´ for error handling, especially in a situation as crucial as saving your data.
Do yourself a big favour and use a proper do/try/catch (or re-throwing) as your standard way of handling error and only use try? or try! in very specific situations when you are aware of the consequences. It's more code to write but as soon as you have some kind of issue you'll appreciate the extra effort.
do {
try coreData.save()
} catch {
print("Unable to save Managed Object Context")
print("\(error), \(error.localizedDescription)")
}

Do,Try, Catch is a good start and it is the default implementation that Xcode loads for you during the CoreData setup.
At the same time it recommends you implement proper error handling for your app. Here are some of the reasons. By default it just prints the error message out.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
There is a good video on Do, Try Catch by Sean Allen, showing an example of how to handle an error. Check it out.
youtube - do, try, catch

Related

Problem with Firebase Storage reference in Swift program

I am an iOS App developer using Swift. I opened Firebase Storage today and proceeded step by step in accordance with the help document. I can already view the txt file I uploaded on web browser with the url: "https://storage.googleapis.com/my_bucket_name/story_001.txt". But I encountered difficulty in accessing the txt file in the App. I think the problem lies in the generation of the reference. Here is my code:
let ref = Storage.storage().reference();
let txt_ref = ref.child("gs://my_bucket_name").child("story_001.txt");
print(txt_ref.fullPath);
txt_ref.getMetadata{ metadata, error in
if let err = error { print("Failed to get metadata."); }
else{ print("Metadata: \(metadata)"); }
}
I get the following output from console:
gs:/my_bucket_name/story_001.txt
Failed to get metadata.
The two slashes after "gs:" become one slash. I think the problem should come from here.
How can I solve it? Thanks in advance.
You may able to use the reference withPath: API like
let ref = Storage.storage().reference(withPath:"my_bucket_name")
let txt_ref = ref.child("story_001.txt")
See additional examples in these integration tests.

NSPersistenceContainer returns nil?

When setting up a CoreData stack, I noticed that NSPersistenceContainer may return "nil" if the name of the model is spelled wrong.
...
let container = NSPersistentContainer(name: name)
guard container != nil else { // warning here ... see explanation below
promise(.failure(.objectModelNotFound))
return
}
In the code fragment above (which is part of utility function) the compiler complains about the guard statement with a warning: "Comparing non-optional value of type 'NSPersistentContainer' to 'nil' always returns true".
The init for the NSPersistenceContainer is declared as:
convenience init(name: String)
To summarise: If the name of the model is wrong, the code in the guard's else statement is reached, and a message in the console window says: "CoreData: error: Failed to load model named XYZ"
The question then is, is it okay for NSPersistentContainer to do so? Or maybe it was stated incorrectly in the documentation?
And if not, what is the correct way to handle the situation in code without getting a warning?
Just creating an NSPersistenceContainer does not set up the Core Data Stack. You still need to call loadPersistentStores on this container. If you look at the default stack that Xcode can create for you you can see that the default implementation allows you to check that the container is set up correctly:
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
And in this block you can deal with the error.
Passing in an Incorrect name comes under the category of a developer error. You should know the name of your model, that exists in your bundle, and if it doesn't exist then you have a big problem.

iOS11 Swift 4: Storing access rights for URLs retrieved from DocumentBrowserViewController

When using DocumentBrowserViewController to access files from other applications, is it possible to store the access rights to those files?
The concrete issue
Currently, for proof of concept purposes, I printed the url for a file, selected via DocumentBrowserViewController, to the console, hardcoded that url somewhere in my code and try to open that file again. That doesn't work because of: {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
Now, is there a way to store the access rights?
In a blog post I found something using:
do {
let data = try sourceURL.bookmarkData(
options: URL.BookmarkCreationOptions.withSecurityScope,
includingResourceValuesForKeys: nil,
relativeTo: nil)
print(data)
} catch {
print(error)
}
But withSecurityScope is "unavailable". (To be precise: 'withSecurityScope' has been explicitly marked unavailable here (Foundation.NSURL))
Is there a way to do this kind of stuff?
Regards and thanks in advance :)
Edit
So maybe I was a little hasty with the question, the above code just needs to be adjusted into:
do {
let data = try sourceURL.bookmarkData(
options: URL.BookmarkCreationOptions.minimalBookmark,
includingResourceValuesForKeys: nil,
relativeTo: nil)
print(data)
} catch {
print(error)
}
And this data can then be used somehwat like that:
let url = try URL.init(resolvingBookmarkData: data, bookmarkDataIsStale: &stale)!
:)
You need to create a bookmark from the URL and store that. This is explained towards the 40' mark of this WWDC session: https://developer.apple.com/videos/play/wwdc2018/216 and is also in the Particles sample code published last week.
Essentially, the snippet above is correct, except that the withSecurityScope flag is useless on iOS (this is automatic). When resolving the URL on launch, you need to startAccessing or use UIDocument, which does this for you.

Best practice for savings cloud kit record to core data, getting sporadic runtime error

I have the below code, in both cases of having or not having the comments removed and also having unowned self changed to weak self 8 times out of 10 i have no issues getting the record id to post to core data, however, in other cases i get a run time error and it appears it is on the try.self.moContext.save() line. the error happens on different threads, and in the debug is shows abortretainunowned...
CKContainer.defaultContainer().publicCloudDatabase.saveRecord(postRecord, completionHandler: { [unowned self] (record, error) -> Void in
// dispatch_async(dispatch_get_main_queue()) {
if error != nil {
print("Error Saving to Cloud \(error)")
} else {
photoData.cloudKitID = record!.recordID.recordName as String
//Save to CoreData
do {
try self.moContext.save()
print("Saved Posted ReocrdID Name to CoreData \(record!.recordID.recordName)")
self.moContext.refreshAllObjects()
} catch let error {
//Check for Error
print("Error is \(error)")
}
}
// }
})
UPDATE
I think I found a solution, if anyone could validate if this is best practice or not would be appreciative.
Since I have to create postRecord by initializing as a CKRecord postRecord actually already has the cloud kit record name postRecord.recordID.recordName already before it is posted. So i moved my core data save to be infant of the CloudKit save operation. Essentially saving before the saverecord occurs. So far so good. This works assuming that the CkRecord Name of postRecord.recordID.recordName will always match the returned CloudKit results.recordID.recordName. Is this a right assumption?
Thanks
Remember, CloudKit operation are asynchronous and there are many reason they could fail, so is not good idea to save any cache or data before you know the result (unless you mark the cache as "unsaved" or something like that, for retry purpose).
What you need to do is to use [weak self] in your block/closure and then, check if self is not nil to continue.
Also, all your call to self need and "?" or "!"
try self!.moContext.save()
I use a singleton for my NSOperationQueue and an observer for the operation (in case of the app go background, the observer give me more seconds in the background to finish the operation). Look for a video of the last WWDC about operations.

executeFetchRequest:error: A fetch request must have an entity

I had a project that was working fine. It had "Core Data" checked, and had the data model all set up. I only started adding a couple entities and properties tonight. When I try to run my app now, it won't even load. It just crashes on me.
Here's the error:
'NSInvalidArgumentException', reason: 'executeFetchRequest:error: A fetch request must have an entity.'
I'm really scared because I don't have a backup of this and if I can't get it working I don't know what I'll do. :)
Thanks in advance!
EDIT:
I got fed up with my data, so I just copied a new blank xcdatamodel to my project and I'm going to start fresh. Thanks for the help!
My issue is I didn't use the same name for Entity and Class. Trivial solution to fix it is by giving them the same name.
If you are using MagicalRecored with Swift:
Make sure you use #objc directive in the Swift NSManagedObject subclass to make the class accessible to Objective-C code from the MagicalRecord library
#objc(MyEntity)
class MyEntity: NSManagedObject {
#NSManaged var myAttribute: Int16
}
After searching all over for a solution, what fixed it for me was doing a Clean/Build in Xcode.
Product->Clean, Product->Build, then try running it.
It seemed as if my data got corrupted, so I deleted my data model and the database in the iPhone simulator, and started fresh.
I had the same error.
For me, it is because I have added a new Model Version, but I did not set it as "Current Version". Careless me! To fix, select the xcdatamodel, click Design > Data Model > Set Current Version. The xcdatamodel file will then have a green tick.
Hopes that helps.
Also, make sure that your .xcdatamodeld file is in the "Copy Bundle Resources" phase of your Build Phases.
Here's what fixed it for me:
As I was converting to Swift 3, Xcode was giving me an error when declaring a new NSFetchRequest, saying that it needed a type. After adding the type, I did what anyone else would have assumed; if the request is typed, why specify an entity name? So, I removed it.
It actually was my mistake.
Swift 2.2:
let request = NSFetchRequest(entityName: "MyEntity")
When I first converted to Swift 3:
let request = NSFetchRequest<MyEntity>()
That was giving me an error. I ended up with this:
let request = NSFetchRequest<MyEntity>(entityName: "MyEntity")
And everything works fine. Personally, I'm not sure why it needs to have an entity name specified, if you're typing the request. Maybe they'll update that at some point (I hope)
i found this solution in the apple develper forum and it was exactly my problem!
the solutions is that the context must be defined inside the struct App.
not in the environment parameter
import SwiftUI
#main
struct CoreDataDemoApp: App {
private let context = CoreDataStack.context.
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, context)
}
}
}
Check if,
the entity is present in the xcdatamodel file.
entity name used are same.
If you are using Swift 3 and Core Data's new stack syntax:
var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyAppModel")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
} else {
print(storeDescription)
}
})
return container
}()
Then you should be using this fetch syntax:
let request: NSFetchRequest<Client> = Client.fetchRequest()
I had this error on the first fetch after app launches when using different variations:
let request: NSFetchRequest<NSFetchRequestResult> = Client.fetchRequest()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Client")
I did stumble across the same precise error upon taking my first steps into Core Data (and iOS 11 and Swift 4). I started off a book (sixth edition meant to target Swift 4 but presumably including some legacy stuff).
As suggested in the book my code was:
let fetchRequest = NSFetchRequest<ReminderData>()
let entity = ReminderData.entity()
fetchRequest.entity = entity
do {
let rows = try managedObjectContext.fetch(fetchRequest)
} catch {
fatalError("Unresolved error")
}
It turned out that all I got from ReminderData.entity() is nil. Not sure if I did something wrong when setting up the data model or ... Apple's docs say that NSManagedObject.entity() must not be overwritten?
Long story short, the Codegen file ReminderData+CoreDataProperties.swift did include the solution:
#nonobjc public class func fetchRequest() -> NSFetchRequest<ReminderData> {
return NSFetchRequest<ReminderData>(entityName: "ReminderDB")
}
which was all I had to use to end-up with a proper NSFetchRequest, no fiddling with the NSEntityDescription, problem gone!
let fetchRequest = NSFetchRequest<ReminderData>(entityName: "ReminderDB")
do {
let rows = try managedObjectContext.fetch(fetchRequest)
} catch {
fatalError("Unresolved error")
}
I built clean, and that didn't fix it. Then I deleted the app, and that didn't fix it. Then I built clean and deleted the app AT THE SAME TIME, and that fixed it.
Just add the same problem. I copied all my entities. Deleted the data model, recreated an empty one and pasted the entities back into the new data model. Solved my issue.
First I downloaded the app's data through the Organizer (to see what was happening) and noticed that it offered me to save it under a previous project name. This puzzled me. So I exited XCode 4.6.1, deleted the app (and its data) from my iPhone, and came back.
This time I got an error saying Cannot create an NSPersistentStoreCoordinator with a nil model. So I looked into the AppDelegate.m file and changed the URLForResource in the - (NSPersistentStoreCoordinator *) persistentStoreCoodinator method. It was set to the name of my app, and I changed it to 'Model' so as to match the name of my Model.xcdatamodeld.
It's working now.
This happened to me when I was fetching from the wrong database. My application has 3 sqlite databases, and of course 3 ManagedObjectContext instances. Well I was submitting the wrong ManagedObjectContext to a method asking it to query a table that didn't exist in the ManagedObjectContext I submitted. After using the correct ManagedObjectContext, all was good.
I think the original question/problem, and also the issue that most of these answers fixes (just in different ways) is just a real simple one:
Anytime you modify your core data (like adding an entity as you mention), you either have to delete all existing data (if you haven't published your app yet), or add a new version to your model.
Just thought I would post this answer, even though this is an older question, because this answer seems pretty obvious and so far hasn't been discussed in any of the questions or comments I read above.
You can also use setter method from CoraData ... Just do something like this...
On your CustomCoreDataManager.m
import "ObjectiveRecord.h"
call init method like this
(instancetype)init {
self = [super init];
if (self) {
[[CoreDataManager sharedManager] setModelName:#"YourModelName"];
}
return self; }
Hope this helps to someone...
Maybe you are trying to load a Database from a different/the wrong bundle?
For instance from or within a Framework?
I had this issue and solved it by loading the DB from the bundle of the related Framework. And then it all worked fine!!
Swift 4 + MagicalRecord:
let frameworkBundle = Bundle(for: AClassFromTheFramework.self)
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [frameworkBundle])
MagicalRecord.setShouldAutoCreateManagedObjectModel(false)
NSManagedObjectModel.mr_setDefaultManagedObjectModel(managedObjectModel)
MagicalRecord.setupCoreDataStack(withAutoMigratingSqliteStoreNamed: "db.sqlite")
And voila !
I faced same issue, actually i was calling MyEnty instead of MyEntity so please re-check what names you have given to your entities and call the same and also check whether you are calling same attributes that you have defined like name
In my case, it was because this dropdown was not set to "Current Product Module" in the Data Model Inspector in Xcode (13.4.1):
Once I set that, it stopped crashing.
Hope this helps!