I am using CoreData with MagicalRecord.
I'd like to insert Data in following code, but to insert data become an error with a message Cocoa error 133000.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"class_schedule.sqlite"];
return YES;
}
ViewController.m
- (void)saveData
{
Data *data = [Data MR_createEntity];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Data *localData = [data MR_inContext:localContext];
localData.title = textField.text;
} completion:^(BOOL success, NSError *error) {
}];
}
Data.h
#interface Data : NSManagedObject
#property (nonatomic, retain) NSNumber * id;
#property (nonatomic, retain) NSString * title;
#end
Can you tell me how to insert record with Magical Record?
Error:
Cocoa error 133000 is:
NSManagedObjectReferentialIntegrityError = 133000, // attempt to fire a fault pointing to an object that does not exist (we can see the store, we can't see the object)
(Taken from this SO question). Basically you are doing something with a NSManagedObject that doesn't exist.
Inserting data:
In terms of how to insert data using magical record take a look at this tutorial which will probably explain it much better than I can.
My advice:
Use Core Data straight up. It's quite a steep learning curve, but very quickly becomes intuitive and easy to use. It will also stand you in good stead if you know how it all works rather than relying on a third party.
If you're interested in how it works at a more fundamental level, take a look at SQLite. I wouldn't necessarily recommend using it as it is a C library but it will help you get a deeper understanding.
You get error 133000 when you try to access object that is not existing. "But hey", you might say, "what do you mean not existing? I'm creating it right there!".
When you are creating NSManagedObject like you do, that is with MR_createEntity, under the hood it calls
NSManagedObject *newEntity = [self MR_createEntityInContext:[NSManagedObjectContext MR_contextForCurrentThread]]
This context is not saved in any way by doing that, and created entity is not persisted. Then by calling
Data *localData = [data MR_inContext:localContext];
You are actually making this under the hood:
BOOL success = [[self managedObjectContext] obtainPermanentIDsForObjects:#[self] error:&error];
The problem is that if NSManagedObject isn't persisted, you won't get persistent ID that is next used in
NSManagedObject *inContext = [otherContext existingObjectWithID:[self objectID] error:&error];
Above method fails to retrieve existing object because it's not existing in store yet (remember, context for current thread in which created entity exists is not saved at any point).
But worry not, fix for this is pretty simple. Don't create new entity like that. Instead do it like this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Data *localData = [data MR_createEntityInContext:localContext];
localData.title = textField.text;
} completion:^(BOOL success, NSError *error) {
}];
That way create entity and modify in context that is going to be saved immediately. This is the correct way to create entities in MagicalRecord.
Related
Update
After posting this as an issue to the AFNetworking repo, turns out this is in fact a usage issue on my part. Per the response to my issue:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
So, long story short: If you are using AHTTPSessionManager in the manner described below, make sure to call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate
Original Question
I have a subclassed AFHTTPSessionManager called GTAPIClient that I am using to connect to my REST API. I realize the docs state to use as a singleton but there are a few cases where I need to gin up a new instance. However, it seems that whenever I do so, the object is never deallocated. Currently, GTAPIClient literally does nothing except NSLog itself when deallocated.
Here's some sample code that demonstrates the behavior
GTAPIClient.m
#implementation GTAPIClient
- (void)dealloc
{
NSLog(#"Dealloc: %#", self);
}
#end
GTViewController.m
#import "GTBaseEntityViewController.h"
//Models
#import "GTBaseEntity.h"
//Clients
#import "GTAPIClient.h"
#interface GTBaseEntityViewController ()
#property (nonatomic, weak) GTAPIClient *client;
#property (nonatomic, weak) GTBaseEntity *weakEntity;
#end
#implementation GTBaseEntityViewController
- (IBAction)makeClient:(id)sender {
self.client = [[GTAPIClient alloc] init];
NSLog(#"I just made an API client %#", self.client);
//Another random object assigned to a similar property, just to see what happens.
self.weakEntity = [[GTBaseEntity alloc] init];
NSLog(#"I just made a random object %#", self.weakEntity);
}
- (IBAction)checkClient:(id)sender {
NSLog(#"Client: %#", self.client);
NSLog(#"Entity: %#", self.weakEntity);
}
#end
NSLog output
Fire makeClient:
//It seems to me that both NSLog's should return (null) as they are assigning to a weak property
2014-06-22 16:41:39.143 I just made an API client <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:41:39.144 I just made a random object (null)
Fire checkClient
//Again, both NSLog's should return null for the two objects. However...client is still around. Also, it's overridden dealloc method never fired.
2014-06-22 16:44:43.722 Client: <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:44:43.723 Entity: (null)
For reference, I am using v2.3.1 of AFNetworking. Compiler is warning me that assigning retained object to weak property will release after assignment - which is correct, and functions as expects with my random object. There is nothing else going on in the app. No other view controllers, no other methods on the GTAPIClient, all singleton functionality is removed. Any thoughts on what I am doing wrong here?
Posting the response from Mattt Thompson here to assist future readers:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
If, like many apps, your app uses a singleton Session Manager and one URL Session for your entire app, then you don't need to worry about this.
Replicating your scenario and running it through Instruments shows that AFURLSessionManagers are retained by the NSURLSessions they create, as AFURLSessionManager acts as the delegate for every NSURLSession created. This creates a retain cycle and thus the AFHTTPSessionManager cannot be released. Whether this is a bug in either library or not a bug at all, I'm not sure. You may want to report it on the AFNetworking GitHub page (https://github.com/AFNetworking/AFNetworking).
__weak typeof(manager) weak_manager = manager;
[manager requestWithMethod:method URLString: uri parameters:param
success:^(NSURLSessionDataTask *task, id responseObject) {
if (completion) {
completion(YES, responseObject, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completion) {
completion(NO, error, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}];
I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)
I have an app which pretty much follows the method described here. The key code is as follows:
#import <Foundation/Foundation.h>
#import <sqlite3.h>
#interface FailedBankDatabase : NSObject {
sqlite3 *_database;
}
+ (FailedBankDatabase*)database;
- (NSArray *)failedBankInfos;
#end
#import "FailedBankDatabase.h"
#import "FailedBankInfo.h"
#implementation FailedBankDatabase
static FailedBankDatabase *_database;
+ (FailedBankDatabase*)database {
if (_database == nil) {
_database = [[FailedBankDatabase alloc] init];
}
return _database;
}
- (id)init {
if ((self = [super init])) {
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"banklist"
ofType:#"sqlite3"];
if (sqlite3_open([sqLiteDb UTF8String], &_database) != SQLITE_OK) {
NSLog(#"Failed to open database!");
}
}
return self;
}
- (void)dealloc {
sqlite3_close(_database);
[super dealloc];
}
Now, the app works with one database as expected. But, I want to be able to switch to a different database when the user touches a button. I have the button handler and logic OK, and I store the name of the database to be used and can retrieve it. But, no matter what I do, I always get the same (original) database being called. I fear that the handle associated with _database, a object of type sqlite3, in the example is not being changed properly, so I don't open the database properly. How should I go about changing this? You can't re-init a singleton, but I need to change what's stored in it, in this case _database. Thanks.
EDIT: I would add that if I ask for _database is a pointer. So I need to open a new database (and close the first I guess) and give the new database a new address in the process.
I had the same problem, but couldn't modify the database (they were used in other projects).
So, I created a method called useDatabase:, that close the previous connection, and open a new one.
The steps :
Your - (id)init remains the same
In FailedBankDatabase, you create a method that close and open the database with the name of the new database
-(void)useDatabase:(NSString*)database {
sqlite3_close(_database);
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:database
ofType:#"sqlite3"];
if (sqlite3_open([sqLiteDb UTF8String], &_database) != SQLITE_OK) {
NSLog(#"Failed to open database!");
}
}
At the very beggining (for example in appDidFinishLaunching), you call the singleton once
[FailedBankDatabase database];
, so that it is first initialised.
Then, when you want to change the .sqlite used, you can call :
[FailedBankDatabase useDatabase:#"anOtherDatabase"]
I think you can do this when you don't have to change the database very often. In my case, I use this once at the very first screen, with 3 buttons, where I will choose wich database will be used.
For more complicated cases, for exemple involving multithreading, you should not do that since it closes the connection for a little time, while it is used elsewhere.
Hope it helps,
Jery
After some additional study, I was unsuccessful in answering the question as asked. However, it looks like FMDB can probably handle the task, I just didn't want to add a large framework to my project. I solved my problem an entirely different way: I modified each database to give it an identifying column and then combined them, and modified the query I used to select only the original database chunk that was wanted. This approach will only work when the databases have the same structure of course.
This is my first question on Stack Overflow, so please excuse me if I'm breaking any etiquette. I'm also fairly new to Objective-C/app creation.
I have been following the CS193P Stanford course, in particular, the CoreData lectures/demos. In Paul Hegarty's Photomania app, he starts with a table view, and populates the data in the background, without any interruption to the UI flow. I have been creating an application which lists businesses in the local area (from an api that returns JSON data).
I have created the categories as per Paul's photo/photographer classes. The creation of the classes themselves is not an issue, it's where they are being created.
A simplified data structure:
- Section
- Sub-section
- business
- business
- business
- business
- business
- business
My application starts with a UIViewController with several buttons, each of which opens a tableview for the corresponding section (these all work fine, I'm trying to provide enough information so that my question makes sense). I call a helper method to create/open the URL for the UIManagedDocument, which was based on this question. This is called as soon as the application runs, and it loads up quickly.
I have a method very similar to Paul's fetchFlickrDataIntoDocument:
-(void)refreshBusinessesInDocument:(UIManagedDocument *)document
{
dispatch_queue_t refreshBusinessQ = dispatch_queue_create("Refresh Business Listing", NULL);
dispatch_async(refreshBusinessQ, ^{
// Get latest business listing
myFunctions *myFunctions = [[myFunctions alloc] init];
NSArray *businesses = [myFunctions arrayOfBusinesses];
// Run IN document's thread
[document.managedObjectContext performBlock:^{
// Loop through new businesses and insert
for (NSDictionary *businessData in businesses) {
[Business businessWithJSONInfo:businessData inManageObjectContext:document.managedObjectContext];
}
// Explicitly save the document.
[document saveToURL:document.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success){
if (!success) {
NSLog(#"Document save failed");
}
}];
NSLog(#"Inserted Businesses");
}];
});
dispatch_release(refreshBusinessQ);
}
[myFunctions arrayOfBusinesses] just parses the JSON data and returns an NSArray containing individual businessses.
I have run the code with an NSLog at the start and end of the business creation code. Each business is assigned a section, takes 0.006 seconds to create, and there are several hundred of these. The insert ends up taking about 2 seconds.
The Helper Method is here:
// The following typedef has been defined in the .h file
// typedef void (^completion_block_t)(UIManagedDocument *document);
#implementation ManagedDocumentHelper
+(void)openDocument:(NSString *)documentName UsingBlock:(completion_block_t)completionBlock
{
// Get URL for document -> "<Documents directory>/<documentName>"
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:documentName];
// Attempt retrieval of existing document
UIManagedDocument *doc = [managedDocumentDictionary objectForKey:documentName];
// If no UIManagedDocument, create
if (!doc)
{
// Create with document at URL
doc = [[UIManagedDocument alloc] initWithFileURL:url];
// Save in managedDocumentDictionary
[managedDocumentDictionary setObject:doc forKey:documentName];
}
// If the document exists on disk
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]])
{
[doc openWithCompletionHandler:^(BOOL success)
{
// Run completion block
completionBlock(doc);
} ];
}
else
{
// Save temporary document to documents directory
[doc saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success)
{
// Run compeltion block
completionBlock(doc);
}];
}
}
And is called in viewDidLoad:
if (!self.lgtbDatabase) {
[ManagedDocumentHelper openDocument:#"DefaultLGTBDatabase" UsingBlock:^(UIManagedDocument *document){
[self useDocument:document];
}];
}
useDocument just sets self.document to the provided document.
I would like to alter this code to so that the data is inserted in another thread, and the user can still click a button to view a section, without the data import hanging the UI.
Any help would be appreciated I have worked on this issue for a couple of days and not been able to solve it, even with the other similar questions on here. If there's any other information you require, please let me know!
Thank you
EDIT:
So far this question has received one down vote. If there is a way I could improve this question, or someone knows of a question I've not been able to find, could you please comment as to how or where? If there is another reason you are downvoting, please let me know, as I'm not able to understand the negativity, and would love to learn how to contribute better.
There are a couple of ways to this.
Since you are using UIManagedDocument you could take advantage of NSPrivateQueueConcurrencyType for initialize a new NSManagedObjectContext and use performBlock to do your stuff. For example:
// create a context with a private queue so access happens on a separate thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// insert this context into the current context hierarchy
context.parentContext = parentContext;
// execute the block on the queue of the context
context.performBlock:^{
// do your stuff (e.g. a long import operation)
// save the context here
// with parent/child contexts saving a context push the changes out of the current context
NSError* error = nil;
[context save:&error];
}];
When you save from the context, data of the private context are pushed to the current context. The saving is only visible in memory, so you need to access the main context (the one linked to the UIDocument) and do a save there (take a look at does-a-core-data-parent-managedobjectcontext-need-to-share-a-concurrency-type-wi).
The other way (my favourite one) is to create a NSOperation subclass and do stuff there. For example, declare a NSOperation subclass like the following:
//.h
#interface MyOperation : NSOperation
- (id)initWithDocument:(UIManagedDocument*)document;
#end
//.m
#interface MyOperation()
#property (nonatomic, weak) UIManagedDocument *document;
#end
- (id)initWithDocument:(UIManagedDocument*)doc;
{
if (!(self = [super init])) return nil;
[self setDocument:doc];
return self;
}
- (void)main
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setParentContext:[[self document] managedObjectContext]];
// do the long stuff here...
NSError *error = nil;
[moc save:&error];
NSManagedObjectContext *mainMOC = [[self document] managedObjectContext];
[mainMOC performBlock:^{
NSError *error = nil;
[mainMOC save:&error];
}];
// maybe you want to notify the main thread you have finished to import data, if you post a notification remember to deal with it in the main thread...
}
Now in the main thread you can provide that operation to a queue like the following:
MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]];
[[self someQueue] addOperation:op];
P.S. You cannot start an async operation in the main method of a NSOperation. When the main finishes, delegates linked with that operations will not be called. To say the the truth you can but this involves to deal with run loop or concurrent behaviour.
Hope that helps.
Initially I was just going to leave a comment, but I guess I don't have the privileges for it. I just wanted to point out the UIDocument, beyond the change count offers
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
Which shouldn't have the delay I've experienced with updating the change count as it waits for a "convenient moment".
First off, InAppSettingsKit is just what I was looking for. Once I figure out some things it will save me tons of time.
My question is: how do I create my own store by subclassing IASKAbstractSettingsStore? The home of IASK states:
The default behaviour of IASK is to store the settings in [NSUserDefaults standardUserDefaults]. However, it is possible to change this behaviour by setting the settingsStore property on an IASKAppSettingsViewController.
and
The easiest way to create your own store is to create a subclass of IASKAbstractSettingsStore.
I've spent a good deal of time combing through the code, and I think I understand the basic structure of it. However, I can't figure out how and what to set the settingsStore property to.
I can see the settingsStore defined and implemented in IASKAppSettingsViewController:
id<IASKSettingsStore> _settingsStore;
and
- (id<IASKSettingsStore>)settingsStore {
if (!_settingsStore) {
_settingsStore = [[IASKSettingsStoreUserDefaults alloc] init];
}
return _settingsStore;
}
I tried subclassing IASKAbstractSettingsStore:
#import <Foundation/Foundation.h>
#import "IASKSettingsStore.h"
#interface IASKSettingsStoreMeals : IASKAbstractSettingsStore {
NSString * _filePath;
NSMutableDictionary * _dict;
}
- (id)initWithPath:(NSString*)path;
#end
and then modified IASKAppSettingsViewController's settingsStore property to allocate and initialize my new class IASKSettingsStoreMeals instead of IASKSettingsStoreUserDefaults - the only way I can see to change the property:
- (id<IASKSettingsStore>)settingsStore {
if (!_settingsStore) {
_settingsStore = [[IASKSettingsStoreMeals alloc] init];
}
return _settingsStore;
}
When I build and run, I get the following message when I try the first control (the toggle switch), all other fields do not get saved:
attempt to insert nil value at objects[0] (key: toggleSwitch)
What am I doing wrong? In addition to the changes needed to "rejigger" the code to use IASKSettingsStoreFile (or a subclassed IASKAbstractSettingsStore), I also can't see where to set the file path change the location of where the settings are saved - or is that done behind the scenes. Looking forward to get past this learning curve and using this.
Found the answer.
My question reveals my inexperience with object orientated languages on the whole, and the concept of encapsulation and frameworks in particular. No changes needed to be made to the IASK framework code, all code was added on my root view controller.
I created another instance of IASKAppSettingsViewController, and added the following code to change the plist location:
// the path to write file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *settingsFile = [documentsDirectory stringByAppendingPathComponent:#"mySettings"];
IASKSettingsStoreFile *mySettingsBundle = [[IASKSettingsStoreFile alloc] initWithPath:settingsFile];
self.appSettingsViewController.settingsStore = mySettingsBundle;
UINavigationController *aNavController = [[UINavigationController alloc] initWithRootViewController:self.appSettingsViewController];
[mySettingsBundle release];
self.appSettingsViewController.settingsStore = mySettingsBundle;
//[viewController setShowCreditsFooter:NO]; // Uncomment to not display InAppSettingsKit credits for creators.
// But we encourage you not to uncomment. Thank you!
self.appSettingsViewController.showDoneButton = YES;
[self presentModalViewController:aNavController animated:YES];
[aNavController release];