7

我正在处理我的文凭项目,其中包括一个带有 Core Data 数据库的 iOS 客户端和一个 Ruby on Rails 服务器。我正在使用 RestKit 进行它们之间的通信。目前我在让整个系统正常工作时遇到了一个大问题:当我尝试将响应映射到来自服务器的对象时,出现以下异常

2013-02-08 22:40:43.947 App[66735:5903] *** Assertion failure in -[RKManagedObjectResponseMapperOperation performMappingWithObject:error:], ~/Repositories/App/RestKit/Code/Network/RKResponseMapperOperation.m:358
2013-02-08 23:04:30.562 App[66735:5903] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = http://localhost:3000/contacts?auth_token=s78UFMq8mCQrr12GZcyx)'
*** First throw call stack:
(0x1de9012 0x1c0ee7e 0x1de8e78 0x16a4f35 0x8f56e 0x8d520 0x1647d23 0x1647a34 0x16d4301 0x23a253f 0x23b4014 0x23a52e8 0x23a5450 0x90ac6e12 0x90aaecca)
libc++abi.dylib: terminate called throwing an exception

我正在尝试从服务器加载联系人列表(数组),该列表应保存为 Core Data 中的“用户”。

我在数据模型类中构建了所有核心数据代码,就像我在这个视频中看到的那样:http: //nsscreencast.com/episodes/11-core-data-basics。这里是:

头文件:

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface AppDataModel : NSObject

+ (id)sharedDataModel;

@property (nonatomic, readonly) NSManagedObjectContext *mainContext;
@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSString *)modelName;
- (NSString *)pathToModel;
- (NSString *)storeFilename;
- (NSString *)pathToLocalStore;

@end

Implementation file:

#import "AppDataModel.h"

@interface AppDataModel ()

- (NSString *)documentsDirectory;

@end

@implementation AppDataModel

@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
@synthesize mainContext = _mainContext;

+ (id)sharedDataModel {
    static AppDataModel *__instance = nil;
    if (__instance == nil) {
        __instance = [[AppDataModel alloc] init];
    }

    return __instance;
}

- (NSString *)modelName {
    return @"AppModels";
}

- (NSString *)pathToModel {
    return [[NSBundle mainBundle] pathForResource:[self modelName]
                                           ofType:@"momd"];
}

- (NSString *)storeFilename {
    return [[self modelName] stringByAppendingPathExtension:@"sqlite"];
}

- (NSString *)pathToLocalStore {
    return [[self documentsDirectory] stringByAppendingPathComponent:[self storeFilename]];
}

- (NSString *)documentsDirectory {
    NSString *documentsDirectory = nil;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    documentsDirectory = [paths objectAtIndex:0];
    return documentsDirectory;
}

- (NSManagedObjectContext *)mainContext {
    if (_mainContext == nil) {
        _mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        _mainContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
    }

    return _mainContext;
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel == nil) {
        NSURL *storeURL = [NSURL fileURLWithPath:[self pathToModel]];
        _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:storeURL];
    }

    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator == nil) {
        NSLog(@"SQLITE STORE PATH: %@", [self pathToLocalStore]);
        NSURL *storeURL = [NSURL fileURLWithPath:[self pathToLocalStore]];
        NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
                                             initWithManagedObjectModel:[self managedObjectModel]];
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
        NSError *e = nil;
        if (![psc addPersistentStoreWithType:NSSQLiteStoreType
                               configuration:nil
                                         URL:storeURL
                                     options:options
                                       error:&e]) {
            NSDictionary *userInfo = [NSDictionary dictionaryWithObject:e forKey:NSUnderlyingErrorKey];
            NSString *reason = @"Could not create persistent store.";
            NSException *exc = [NSException exceptionWithName:NSInternalInconsistencyException
                                                       reason:reason
                                                     userInfo:userInfo];
            @throw exc;
        }

        _persistentStoreCoordinator = psc;
    }

    return _persistentStoreCoordinator;
}
@end

The User class is pretty straightforward, auto-generated with xCode.

Header file:

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>


@interface User : NSManagedObject

@property (nonatomic, retain) NSString * email;
@property (nonatomic, retain) NSString * firstName;
@property (nonatomic, retain) NSString * lastName;
@property (nonatomic, retain) NSNumber * userID;

@end

Implementation file:

#import "User.h"

@implementation User

@dynamic email;
@dynamic firstName;
@dynamic lastName;
@dynamic userID;

@end

Just like the data model class, I have a server manager class which I use for communication:

Header file:

#import <Foundation/Foundation.h>
#import <RestKit/RestKit.h>
#import "AppServerProtocol.h"
#import "AppDataModel.h"


@interface AppServer : NSObject <AppServerDelegate>

+ (id)sharedInstance;

@property (strong, nonatomic) RKObjectManager *objectManager;
@property (strong, nonatomic) RKEntityMapping *userMapping;

@end

And implementation file:

#import "AppServer.h"
#import "User.h"
#import "Device.h"
#import "Ping.h"
#import "AppAppDelegate.h"

@interface AppServer ()

@property BOOL initialized;

@end

@implementation AppServer

+ (id)sharedInstance {
    static AppServer *__instance = nil;
    if (__instance == nil) {
        __instance = [[AppServer alloc] init];
        __instance.initialized = NO;
    }

    if (![__instance initialized]) {
        [__instance initServer];
    }

    return __instance;
}

- (void)initServer {
    // initialize RestKit
    NSURL *baseURL = [NSURL URLWithString:@"http://localhost:3000"];
    _objectManager = [RKObjectManager managerWithBaseURL:baseURL];

    // enable activity indicator spinner
    [AFNetworkActivityIndicatorManager sharedManager].enabled = YES;

    // initialize managed object store
    _objectManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[[AppDataModel sharedDataModel] managedObjectModel]];

    _userMapping = [RKEntityMapping mappingForEntityForName:@"User" inManagedObjectStore:_objectManager.managedObjectStore];
    [_userMapping addAttributeMappingsFromDictionary:@{
     @"email" : @"email",
     @"firstName" : @"first_name",
     @"lastName" : @"last_name"
     }];
    [_userMapping setIdentificationAttributes: @[@"userID"]];

    RKResponseDescriptor *contactsResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:_userMapping pathPattern:@"/contacts" keyPath:nil statusCodes:nil];
    [_objectManager addResponseDescriptor:contactsResponseDescriptor];

    _initialized = YES;
}

// contacts
- (void)getContactsForCurrentUser {
    NSString *authToken = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppAuthenticationToken"];

    [_objectManager getObjectsAtPath:@"/contacts" parameters:@{@"auth_token": authToken} success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        RKLogInfo(@"Load collection of contacts: %@", mappingResult.array);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        RKLogError(@"Operation failed with error: %@", error);
    }];

}

@end

So when I open the Contacts Table View, which is set up correctly to use a fetched results controller (successfully pulling entities out of the DB), I have a dangerous refresh button, which calls the method you've just read above:

- (void)downloadContacts {
    [[AppServer sharedInstance] getContactsForCurrentUser];
}

Here is the format of the response:

[
    {
        "created_at":"2013-01-11T14:03:57Z",
        "email":"john@example.com",
        "first_name":"John",
        "id":2,
        "last_name":"Doe",
        "updated_at":"2013-02-07T10:57:16Z"
    },
    {
        "created_at":"2013-01-11T14:03:57Z",
        "email":"jane@example.com",
        "first_name":"Jane",
        "id":3,
        "last_name":"Doe",
        "updated_at":"2013-02-07T10:57:16Z"
}
]

And before the exception the console states the following:

2013-02-08 22:40:36.892 App[66735:c07] I restkit:RKLog.m:34 RestKit logging initialized...
2013-02-08 22:40:36.994 App[66735:c07] SQLITE STORE PATH: ~/Library/Application Support/iPhone Simulator/6.0/Applications/D735548F-DF42-4E13-A7EF-53DF0C5D8F3B/Documents/AppModels.sqlite
2013-02-08 22:40:37.001 App[66735:c07] Context is ready!
2013-02-08 22:40:43.920 App[66735:c07] I restkit.network:RKHTTPRequestOperation.m:154 GET 'http://localhost:3000/contacts?auth_token=s78UFMq8mCQrr12GZcyx'
2013-02-08 22:40:43.945 App[66735:c07] I restkit.network:RKHTTPRequestOperation.m:181

The line of the RestKit library, that fails before the whole exception is thrown is:

NSAssert(self.managedObjectContext, @"Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = %@)", self.response.URL);

I have followed that back to the initServer method in the AppServer.m file, in which, before the method returns, the properties of the RKObjectManager class are like this: http://imgur.com/LM5ZU9m

As I have debugged, I've traced that the problem is not with the server side or the communication of the app - I can see the JSON received and deserialized into an array, but the moment it's passed to the next method which is supposed to save it to Core Data, the whole app goes kaboom because of the NSAssert of the managed object context.

Any help is greatly appreciated!

4

2 回答 2

4

After a few days of debugging, I finally found out what went wrong: it looks like I also had to set the path to my local persistence store and generate managed object contexts for the managed object store myself.

Here's where I found the solution: https://github.com/RestKit/RestKit/issues/1221#issuecomment-13327693

I've just added a few lines in my server init method:

NSError *error = nil;
NSString *pathToPSC = [[AppDataModel sharedDataModel] pathToLocalStore];

_objectManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[[AppDataModel sharedDataModel] managedObjectModel]];
[_objectManager.managedObjectStore addSQLitePersistentStoreAtPath:pathToPSC fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];

if (error != nil) {
    NSLog(@"\nSerious object store error!\n");
    return;
} else {
    [_objectManager.managedObjectStore createManagedObjectContexts];
}
于 2013-02-13T15:14:22.340 回答
2

I managed to do it using this function RKApplicationDataDirectory() to get the application directory and set my database path.

// Initialize HTTPClient
NSURL *baseURL = [NSURL URLWithString:@"http://myapiaddress.com"];
AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
//we want to work with JSON-Data
[client setDefaultHeader:@"Accept" value:RKMIMETypeJSON];

// Initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"NameOfMyCoreDataModel" ofType:@"momd"]];

//Iniitalize CoreData with RestKit
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSError *error = nil;

NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"nameOfDB.sqlite"];

objectManager.managedObjectStore = managedObjectStore;

[objectManager.managedObjectStore addSQLitePersistentStoreAtPath:path fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];

[objectManager.managedObjectStore createManagedObjectContexts];
于 2013-02-14T18:47:14.413 回答