NSSearchPathDirectory.DocumentDirectory returns bogus path - ios

I've been trying to unit test an iOS project which uses Core Data. I'm using the usual Core Data stack which is generated by Xcode. The line failing is the following:
lazy var applicationDocumentsDirectory: NSURL = {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1] as NSURL
}()
On my mac it returns: file:///Users/juliantejera/Library/Developer/CoreSimulator/Devices/{UNIQUE ID}/data/Containers/Data/Application/{UNIQUE ID}/Documents/
On travis: file:///var/empty/Documents/
Therefore my NSManagedObjectContext cannot be created and my app crashes. Any solutions?

After a week I was finally able to solve my problem by using a NSInMemoryStoreType for the NSPersistentStoreCoordinator whilst running unit tests.
var isRunningUnitTests = NSClassFromString("XCTest") != nil
var storeType = isRunningUnitTests ? NSInMemoryStoreType : NSSQLiteStoreType

In my case the problem was a combination of Kiwi and Realm:
I was initializing an object in the context block of the test spec, which in turn forced a Realm DB to be initialized. Not sure what exactly causes this weird action, but since then I take it as a rule of thumb to initialize variables in the beforeAll block.

Related

Define Bundle.main.url to use Core Data for iOS9 and above Swift 4

I'm just learning Core Data and I need to implement Core Datafor both iOS 9 and iOS 10 as my only iPad test device is an iPad3 running iOS 9.3.5. I'm trying to follow this solution https://charleswilson.blog/2016/09/09/out-of-context-using-core-data-on-ios-9-3-and-10-0/ ( not sure I could paste the whole code from the link ) as I failed with implementing other solutions from stack overflow. I'm not sure If I got this one thing right: Inside the lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator there is this let modelURL = Bundle.main.url(forResource: modelName, withExtension: modelExtension)! that I see in other solutions here in stack overflow and they're all declared as with different Stringvalues for forResource parameter, but all with the same "momd"value for withExtension: parameter. I actually thought the since I'm using an .xcdatamodeld I should put in my data model name for forResource parameter and "xcdatamodeld" for withExtension: parameter, resulting in my case as :
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let url = self.applicationDocumentsDirectory.appendingPathComponent("fix_it_shop").appendingPathExtension("xcdatamodeld")
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
} catch {
let dict : [String : Any] = [NSLocalizedDescriptionKey : "Failed to initialize the application's saved data" as NSString,
NSLocalizedFailureReasonErrorKey : "There was an error creating or loading the application's saved data." as NSString,
NSUnderlyingErrorKey : error as NSError]
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
fatalError("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
}
return coordinator
}()
Is it so or withExtension: parameter is unrelated to my xcdatamodeld file extension and I should use "momd" instead? Similar questions I found point me in both directions. Many thanks for any explanation you could give about it.
You should use “momd” as the file extension for the model. During the Xcode compilation process, your .xcdatamodeld file gets compiled into a .momd file, which is what actually gets included in the bundle.
However, the url variable in the persistentStoreCoordinator definition refers to the NSPersistentStore file, which for a sqlite store will have the extension “.sqlite”.

Use single realm instance/variable across the application

Goal: Reduce memory footprint
My approach is to create single realm instance in AppDelegate class & then access that instead of creating a new variable each time.
AppDelegate
lazy var realm: Realm = {
let realm = try! Realm()
// Get our Realm file's parent directory
if let folderPath = realm.configuration.fileURL?.URLByDeletingLastPathComponent?.path{
// Disable file protection for this directory
do {
try NSFileManager.defaultManager().setAttributes([NSFileProtectionKey: NSFileProtectionNone],ofItemAtPath: folderPath)
}catch {
printDebug(error)
}
}
return realm
}()
UIViewController
var realm = (UIApplication.sharedApplication().delegate as! AppDelegate).realm
// Access/Modify realm object
try! self.realm.write{
location.imageFile = fileName
}
Questions
1. Will this help reduce memory usage?
2. What are the drawbacks?
Interesting question
1.
In my opinion major drawback is that if you're going to use GCD with Realm. Remember that Realm is thread-safe so you can't use/modify Realm Object across threads/queues.
I handle Realm with Manager which is singleton. Maybe someone has better solution but this works just great.
class CouponManager: NSObject {
/// path for realm file
lazy private var realmURL: NSURL = {
let documentUrl = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask)[0]
let url = documentUrl.URLByAppendingPathComponent("coupons.realm")
return url
}()
lazy private var config:Realm.Configuration = {
return Realm.Configuration(
fileURL: self.realmURL,
inMemoryIdentifier: nil,
encryptionKey: "my65bitkey".dataUsingEncoding(NSUTF8StringEncoding),
readOnly: false,
schemaVersion: 1,
migrationBlock: nil,
deleteRealmIfMigrationNeeded: false,
objectTypes: nil)
}()
static let shared: CouponManager = CouponManager()
func save(coupons coupons:[Coupon]) {
let realm = try! Realm(configuration: config)
try! realm.write(){
realm.deleteAll()
realm.add(coupons)
}
}
func load() -> Results<Coupon> {
let realm = try! Realm(configuration: config)
return realm.objects(Coupon)
}
func deleteAll() {
let realm = try! Realm(configuration: config)
try! realm.write({
realm.deleteAll()
})
}
}
2.
You shouldn't worry about memory when use Realm. As TiM (he works in Realm) said in this answer and you should instantiate Realm every time you need to.
Questions
Will this help reduce memory usage?
From my experience, getting the shared instance of realm and setting the URL is not an heavy task. Yet opening and closing a transaction some times is (depends on how often you do that, but at the end of the day most clients do not write heavily enough).
So my answer for that is no, this will not help in memory usage.
And by the way, did you profile your app to check were does your memory usage go to? Thats a good place to start at.
What are the drawbacks?
I have been using realm for some time now, and I've written 2 apps in production with Realm . Now what I'm about to say is not about memory usage, but about design.
A. Realm as you know is a data base. And you should not just access it from any random place in the application (not from a viewController), especially without wapping it with some class of yours (UsersDataBase class for example), and I'll explain.
When objects in the DB start changing, you need to know who, were and from witch thread the writing is to DB.
One place were you can check and debug. And when I'ts all over your application I't is very hard to debug.
B. Realm is not Thread safe. That means that you really want to know on witch thread realm is getting written too. If not, you will end up getting thread executions, and believe be, this is not a fun thing to deal with. Especily when realm does not help with the stack trace some times.
C. You are accessing realm on AppDelegate with "UIApplication.sharedApplication()". This is not best practice since you can't access "UIApplication.sharedApplication()" from diffrent contexts, for example App Extensions. And when you will want to add App extensions to your app, you will end up reWriting any place your using it.

Sending core data to watchOS 2

I am trying to sync CoreData between iOS 9.0 and watchOS 2.0. My database is pretty small, so I wanted to just send the entire file over using the file transfer in watch connectivity. I know I need to find the URL of the CoreData stack and send that along with the metadata. I think I am getting the URL correctly, but I am getting a null error for the metadata. This is what I have tried.
let bundle = NSBundle.mainBundle()
let modelURL = bundle.URLForResource("Sunday", withExtension: "momd")!
do {
let myStore = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(nil, URL: modelURL)
let fileTransfer = WCSession.defaultSession().transferFile(modelURL, metadata:myStore)
catch {
}
The metadata argument is not required and can be nil, which in your case I think will be fine.
Give that a shot

How to access CoreData model in today extension (iOS)

Is it possible to work with my CoreData model in the today extension in swift like in the original app? If yes, how can I create the NSManagedObjectContext?
I really have no clue, beside the group-identifier, but unfortunatly I don't know how to get the context..
In the past I created apps with the check at the beginning that I want to use CoreData and then I got the managedObjectContext via my AppDelegate.. But how can I do somethink like that in an extension? Apple doesn't offer information about that..
I edited this line in AppDelegate:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"HTWcampus.sqlite"];
to this (after including the group to both targets):
NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.BenchR.TodayExtensionSharingDefaults"];
storeURL = [storeURL URLByAppendingPathComponent:#"HTWcampus.sqlite"];
NSLog(#"StoreURL2: %#", storeURL);
With that the existing database in my app was gone (what is great, because I think it worked to put the database in the shared segment).
But how can I create an instance of my context in the extension? And how can I access my NSManagedObject-subclasses?
In the extension I have this code so far:
var context: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
var storeURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.BenchR.TodayExtensionSharingDefaults")
storeURL = storeURL?.URLByAppendingPathComponent("HTWcampus.sqlite")
let modelURL = NSBundle.mainBundle().URLForResource("HTWcampus", withExtension: "momd")
let model = NSManagedObjectModel(contentsOfURL: modelURL)
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil, error: nil)
context = NSManagedObjectContext()
context.persistentStoreCoordinator = coordinator
}
Is this right? And if yes, how can I get my NSManagedObject-Subclasses in there? And do I have to add the momd-file to the extensions target? If yes, how can I do that?
What you really want is to access your persistent store (most likely a SQLite database).
In order to achieve that, you need to configure App Groups and make sure that your host app configures the Core Data stack using your shared container (so your store is accessible in extension as well).
Something like:
NSString *containerPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:YOUR_SECURITY_APP_GROUP].path;
NSString *sqlitePath = [NSString stringWithFormat:#"%#/%#", containerPath, #"database.sqlite"];
Then in your extension just create persistent store coordinator with managed object contexts using database in shared container.
You can share your model (.momd) and managed object subclasses with extension just by making sure they are included in extension target as well.
Edit:
To add your model and managed object subclasses:
Make sure you have your app and extension targets
Click on your model file, and select both targets under 'Target Membership' on right-hand panel
Repeat the same with all your managed object subclasses

Core Data Concurrency Debugging: False Positive

As mentioned in WWDC 2014 session 225 (Whatʼs New in Core Data), Core Data on iOS 8 and OS X Yosemite now support the command line argument -com.apple.CoreData.ConcurrencyDebug 1 to enable assertions that detect violations of Core Dataʼs concurrency contract.
In my experiments with this, I have found that it works under iOS 8 beta 1 (both on the device and in the simulator), but I seem to have found a false positive, i.e. the framework is throwing a multithreading violation exception where it should not do so. At least that's what I believe.
Question: is the code below correct or am I doing something that violates Core Dataʼs threading model?
What I do is set up a very simple Core Data stack (with an in-memory store, for simplicity's sake) with a managed object context named backgroundContext with private queue concurrency. I then invoke performBlockAndWait { } on that context and in the block I create a new managed object, insert it into the context, and save.
The save operation is where I get the multithreading violation exception from Core Data.
var backgroundContext: NSManagedObjectContext?
func setupCoreDataStackAndViolateThreadingContract()
{
let objectModelURL = NSBundle.mainBundle().URLForResource("CoreDataDebugging", withExtension: "momd")
let objectModel: NSManagedObjectModel? = NSManagedObjectModel(contentsOfURL: objectModelURL)
assert(objectModel)
// Set up a simple in-memory Store (without error handling)
let storeCoordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: objectModel)
assert(storeCoordinator)
let store: NSPersistentStore? = storeCoordinator!.addPersistentStoreWithType(NSInMemoryStoreType, configuration: nil, URL: nil, options: nil, error: nil)
assert(store)
// Set up a managed object context with private queue concurrency
backgroundContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
assert(backgroundContext)
backgroundContext!.persistentStoreCoordinator = storeCoordinator!
// Work on the background context by using performBlock:
// This should work but throws a multithreading violation exception on
// self.backgroundContext!.save(&potentialSaveError)
backgroundContext!.performBlockAndWait {
NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: self.backgroundContext!) as NSManagedObject
person.setValue("John Appleseed", forKey: "name")
var potentialSaveError: NSError?
// In the following line: EXC_BAD_INSTRUCTION in
// `+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__]:
let didSave = self.backgroundContext!.save(&potentialSaveError)
if (didSave) {
println("Saving successful")
} else {
let saveError = potentialSaveError!
println("Saving failed with error: \(saveError)")
}
}
}
I have tested essentially the same code in Objective-C and got the same result so I doubt it is a Swift problem.
Edit: If you want to run the code yourself, I have put a project on GitHub (requires Xcode 6/iOS 8 beta).
Apple confirmed this as a bug. It has been fixed with Xcode 6 beta 4.

Resources