使用 Parse 同步数据,而不是下载数据

Posted

技术标签:

【中文标题】使用 Parse 同步数据,而不是下载数据【英文标题】:Syncing Data with Parse, not downloading data 【发布时间】:2013-08-27 02:50:54 【问题描述】:

我一直在关注 RayWenderlinch.com 的 How To Synchronize Core Data with a Web Service – Part 1 我正在尝试将其自定义为我的应用程序,但在使用 AFNetworking 从 Parse 实际下载数据时遇到问题

应用程序应该连接解析,查看两个类“Club”和“IronSet”检查是否有新记录(或在初始运行时,抓取所有内容)并仅下载新添加的内容。

然后它将这些记录保存到核心数据中,然后从 Cache/JSONRecords/Club(或 IronSet)中删除文件。似乎我从来没有真正从 Parse 中获取数据,尽管它连接成功,并且在从缓存中删除文件之前不会引发错误。

我收到“所有操作已完成”,表明 SyncEngine 应该已完成,并在 downloadDataForRegisteredObjects 中下载

错误

2013-08-26 19:39:03.981 WGT Golf Calculator[3287:c07] All operations completed
2013-08-26 19:39:03.991 WGT Golf Calculator[3287:c07] Unable to delete JSON records at Club -- file://localhost/Users/**/Library/Application%20Support/iPhone%20Simulator/6.1/Applications/4B72F57E-264D-44F7-981D-3D921B0CC2A4/Library/Caches/JSONRecords/, reason Error Domain=NSCocoaErrorDomain Code=4 "The operation couldn’t be completed. (Cocoa error 4.)" UserInfo=0x75610f0 NSUnderlyingError=0x75615e0 "The operation couldn’t be completed. No such file or directory", NSFilePath=/Users/**/Library/Application Support/iPhone Simulator/6.1/Applications/4B72F57E-264D-44F7-981D-3D921B0CC2A4/Library/Caches/JSONRecords/Club, NSUserStringVariant=(
    Remove
)

MLVAppDelegate.m

#import "MLVAppDelegate.h"
#import "MLVSyncEngine.h"
#import "Club.h"
#import "IronSet.h"

@implementation MLVAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

    [[MLVSyncEngine sharedEngine] registerNSManagedObjectClassToSync:[Club class]];
    [[MLVSyncEngine sharedEngine] registerNSManagedObjectClassToSync:[IronSet class]];

    return YES;
    

MLVAFParseAPIClient.h

#import "AFHTTPClient.h"

@interface MLVAFParseAPIClient : AFHTTPClient

+ (MLVAFParseAPIClient *)sharedClient;

- (NSMutableURLRequest *)GETRequestForClass:(NSMutableString *)className parameters:(NSDictionary *)parameters;

- (NSMutableURLRequest *)GETRequestForAllRecordsOfClass:(NSString *)className updatedAfterDate:(NSDate *)updatedDate;

@end

MLVAFParseAPIClient.m

#import "MLVAFParseAPIClient.h"
#import "AFJSONRequestOperation.h"

static NSString * const kSDFParseAPIBaseURLString = @"https://api.parse.com/1/";
static NSString * const kSDFParseAPIApplicationId = @"APP ID REMOVED";
static NSString * const kSDFParseAPIKey = @"API KEY REMOVED";

@implementation MLVAFParseAPIClient

+ (MLVAFParseAPIClient *)sharedClient

    static MLVAFParseAPIClient *sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^sharedClient = [[MLVAFParseAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kSDFParseAPIBaseURLString]];
    );

    return sharedClient;


- (id)initWithBaseURL:(NSURL *)url 
    self = [super initWithBaseURL:url];
    if (self) 
        [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
        [self setParameterEncoding:AFJSONParameterEncoding];
        [self setDefaultHeader:@"X-Parse-Application-Id" value:kSDFParseAPIApplicationId];
        [self setDefaultHeader:@"X-Parse-REST-API-Key" value:kSDFParseAPIKey];
    

    return self;


- (NSMutableURLRequest *)GETRequestForClass:(NSMutableString *)className parameters:(NSDictionary *)parameters

    NSMutableURLRequest *request = nil;
    request = [self requestWithMethod:@"GET" path:[NSString stringWithFormat:@"classes/%@", className] parameters:parameters ];
    return request;


- (NSMutableURLRequest *)GETRequestForAllRecordsOfClass:(NSString *)className updatedAfterDate:(NSDate *)updatedDate

    NSMutableURLRequest *request = nil;
    NSDictionary *parameters = nil;

    if (updatedDate) 
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.'999Z'"];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];

        NSString *jsonString = [NSString stringWithFormat:@"\"updatedAt\":\"$gte\":\"__type\":\"Date\",\"iso\":\"%@\"", [dateFormatter stringFromDate:updatedDate]];

        parameters = [NSDictionary dictionaryWithObject:jsonString forKey:@"where"];
    

    request = [self GETRequestForClass:className parameters:parameters];
    return request;



@end

MLVCoreDataController.m

#import "MLVCoreDataController.h"

@interface MLVCoreDataController ()

@property (strong, nonatomic) NSManagedObjectContext *masterManagedObjectContext;
@property (strong, nonatomic) NSManagedObjectContext *backgroundManagedObjectContext;
@property (strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@end

@implementation MLVCoreDataController

@synthesize masterManagedObjectContext = _masterManagedObjectContext;
@synthesize backgroundManagedObjectContext = _backgroundManagedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

+ (id)sharedInstance 
    static dispatch_once_t once;
    static MLVCoreDataController *sharedInstance;
    dispatch_once(&once, ^
        sharedInstance = [[self alloc] init];
    );

    return sharedInstance;


#pragma mark - Core Data stack

// Used to propegate saves to the persistent store (disk) without blocking the UI
- (NSManagedObjectContext *)masterManagedObjectContext 
    if (_masterManagedObjectContext != nil) 
        return _masterManagedObjectContext;
    

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) 
        _masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_masterManagedObjectContext performBlockAndWait:^
            [_masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
        ];

    
    return _masterManagedObjectContext;


// Return the NSManagedObjectContext to be used in the background during sync
- (NSManagedObjectContext *)backgroundManagedObjectContext 
    if (_backgroundManagedObjectContext != nil) 
        return _backgroundManagedObjectContext;
    

    NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
    if (masterContext != nil) 
        _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_backgroundManagedObjectContext performBlockAndWait:^
            [_backgroundManagedObjectContext setParentContext:masterContext];
        ];
    

    return _backgroundManagedObjectContext;


// Return the NSManagedObjectContext to be used in the background during sync
- (NSManagedObjectContext *)newManagedObjectContext 
    NSManagedObjectContext *newContext = nil;
    NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
    if (masterContext != nil) 
        newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [newContext performBlockAndWait:^
            [newContext setParentContext:masterContext];
        ];
    

    return newContext;


- (void)saveMasterContext 
    [self.masterManagedObjectContext performBlockAndWait:^
        NSError *error = nil;
        BOOL saved = [self.masterManagedObjectContext save:&error];
        if (!saved) 
            // do some real error handling
            NSLog(@"Could not save master context due to %@", error);
        
    ];


- (void)saveBackgroundContext 
    [self.backgroundManagedObjectContext performBlockAndWait:^
        NSError *error = nil;
        BOOL saved = [self.backgroundManagedObjectContext save:&error];
        if (!saved) 
            // do some real error handling
            NSLog(@"Could not save background context due to %@", error);
        
    ];


// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel

    if (_managedObjectModel != nil) 
        return _managedObjectModel;
    
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"WGTCalculator" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;


// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator

    if (_persistentStoreCoordinator != nil) 
        return _persistentStoreCoordinator;
    

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"WGTCalcul.sqlite"];

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) 

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    

    return _persistentStoreCoordinator;


#pragma mark - Application's Documents directory

// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory

    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];



@end

MLVSyncEngine.h

#import <Foundation/Foundation.h>

typedef enum 
    MLVObjectSynced = 0,
    MLVObjectCreated,
    MLVObjectDeleted,
 MLVObjectSyncStatus;

@interface MLVSyncEngine : NSObject
@property (atomic, readonly) BOOL syncInProgress;


+ (MLVSyncEngine *)sharedEngine;

- (void)registerNSManagedObjectClassToSync:(Class)aClass;
- (void)startSync;

@end

MLVSyncEngine.m

#import "MLVSyncEngine.h"

#import "MLVCoreDataController.h"
#import "MLVAFParseAPIClient.h"
#import "AFHTTPRequestOperation.h"
#import "AFJSONRequestOperation.h"

NSString * const kMLVSyncEngineInitialCompleteKey = @"MLVSyncEngineInitialSyncCompleted";
NSString * const kMLVSyncEngineSyncCompletedNotificationName = @"MLVSyncEngineSyncCompleted";

@interface MLVSyncEngine ()
@property (nonatomic, strong) NSMutableArray *registeredClassesToSync;
@property (nonatomic, strong) NSDateFormatter *dateFormatter;

@end

@implementation MLVSyncEngine

@synthesize registeredClassesToSync = _registeredClassesToSync;
@synthesize syncInProgress = _syncInProgress;
@synthesize dateFormatter = _dateFormatter;


+ (MLVSyncEngine *)sharedEngine

    static MLVSyncEngine *sharedEngine = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        sharedEngine = [[MLVSyncEngine alloc] init];
    );

    return sharedEngine;


- (void)registerNSManagedObjectClassToSync:(Class)aClass

    if (!self.registeredClassesToSync) 
        self.registeredClassesToSync = [NSMutableArray array];
    

    if ([aClass isSubclassOfClass:[NSManagedObject class]]) 
        if (![self.registeredClassesToSync containsObject:NSStringFromClass(aClass)]) 
            [self.registeredClassesToSync addObject:NSStringFromClass(aClass)];
         else 
            NSLog(@"Unable to register %@ as it is already registered", NSStringFromClass(aClass));
        
     else 
        NSLog(@"Unable to reguster %@ as it is not a subclass of NSManagedObject", NSStringFromClass(aClass));
    


- (BOOL)initialSyncComplete
    return [[[NSUserDefaults standardUserDefaults] valueForKey:kMLVSyncEngineInitialCompleteKey] boolValue];


- (void)setInitialSyncCompleted
    [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES] forKey:kMLVSyncEngineInitialCompleteKey];
    [[NSUserDefaults standardUserDefaults] synchronize];


- (void)executeSyncCompletedOperations 
    dispatch_async(dispatch_get_main_queue(), ^
        [self setInitialSyncCompleted];

        NSError *error = nil;
        [[MLVCoreDataController sharedInstance] saveBackgroundContext];
        if (error) 
            NSLog(@"Error saving background context after creating objects on server: %@", error);
        

        [[MLVCoreDataController sharedInstance] saveMasterContext];

        [[NSNotificationCenter defaultCenter]
         postNotificationName:kMLVSyncEngineSyncCompletedNotificationName
         object:nil];
        [self willChangeValueForKey:@"syncInProgress"];
        _syncInProgress = NO;
        [self didChangeValueForKey:@"syncInProgress"];
    );


- (void)startSync

    if (!self.syncInProgress) 
        [self willChangeValueForKey:@"syncInProgress"];
        _syncInProgress = YES;
        [self didChangeValueForKey:@"syncInProgress"];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^[self downloadDataForRegisteredObjects:YES];
        );
    



- (NSDate *)mostRecentUpdatedAtDateForEntityWithName:(NSString *)entityName 
    __block NSDate *date = nil;
    //
    // Create a new fetch request for the specified entity
    //
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
    //
    // Set the sort descriptors on the request to sort by updatedAt in descending order
    //
    [request setSortDescriptors:[NSArray arrayWithObject:
                                 [NSSortDescriptor sortDescriptorWithKey:@"updatedAt" ascending:NO]]];
    //
    // You are only interested in 1 result so limit the request to 1
    //
    [request setFetchLimit:1];
    [[[MLVCoreDataController sharedInstance] backgroundManagedObjectContext] performBlockAndWait:^
        NSError *error = nil;
        NSArray *results = [[[MLVCoreDataController sharedInstance] backgroundManagedObjectContext] executeFetchRequest:request error:&error];
        if ([results lastObject])   
            //
            // Set date to the fetched result
            //
            date = [[results lastObject] valueForKey:@"updatedAt"];
        
    ];

    return date;


- (void)newManagedObjectWithClassName:(NSString *)className forRecord:(NSDictionary *)record 
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:className inManagedObjectContext:[[MLVCoreDataController sharedInstance] backgroundManagedObjectContext]];
    [record enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) 
        [self setValue:obj forKey:key forManagedObject:newManagedObject];
    ];
    [record setValue:[NSNumber numberWithInt:MLVObjectSynced] forKey:@"syncStatus"];


- (void)updateManagedObject:(NSManagedObject *)managedObject withRecord:(NSDictionary *)record 
    [record enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) 
        [self setValue:obj forKey:key forManagedObject:managedObject];
    ];



- (void)setValue:(id)value forKey:(NSString *)key forManagedObject:(NSManagedObject *)managedObject 
    if ([key isEqualToString:@"createdAt"] || [key isEqualToString:@"updatedAt"]) 
        NSDate *date = [self dateUsingStringFromAPI:value];
        [managedObject setValue:date forKey:key];
     else if ([value isKindOfClass:[NSDictionary class]]) 
        if ([value objectForKey:@"__type"]) 
            NSString *dataType = [value objectForKey:@"__type"];
            if ([dataType isEqualToString:@"Date"]) 
                NSString *dateString = [value objectForKey:@"iso"];
                NSDate *date = [self dateUsingStringFromAPI:dateString];
                [managedObject setValue:date forKey:key];
             else if ([dataType isEqualToString:@"File"]) 
                NSString *urlString = [value objectForKey:@"url"];
                NSURL *url = [NSURL URLWithString:urlString];
                NSURLRequest *request = [NSURLRequest requestWithURL:url];
                NSURLResponse *response = nil;
                NSError *error = nil;
                NSData *dataResponse = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
                [managedObject setValue:dataResponse forKey:key];
             else 
                NSLog(@"Unknown Data Type Received");
                [managedObject setValue:nil forKey:key];
            
        
     else 
        [managedObject setValue:value forKey:key];
    


- (NSArray *)managedObjectsForClass:(NSString *)className withSyncStatus:(MLVObjectSyncStatus)syncStatus 
    __block NSArray *results = nil;
    NSManagedObjectContext *managedObjectContext = [[MLVCoreDataController sharedInstance] backgroundManagedObjectContext];
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"syncStatus = %d", syncStatus];
    [fetchRequest setPredicate:predicate];
    [managedObjectContext performBlockAndWait:^
        NSError *error = nil;
        results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    ];

    return results;


- (NSArray *)managedObjectsForClass:(NSString *)className sortedByKey:(NSString *)key usingArrayOfIds:(NSArray *)idArray inArrayOfIds:(BOOL)inIds 
    __block NSArray *results = nil;
    NSManagedObjectContext *managedObjectContext = [[MLVCoreDataController sharedInstance] backgroundManagedObjectContext];
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className];
    NSPredicate *predicate;
    if (inIds) 
        predicate = [NSPredicate predicateWithFormat:@"objectId IN %@", idArray];
     else 
        predicate = [NSPredicate predicateWithFormat:@"NOT (objectId IN %@)", idArray];
    

    [fetchRequest setPredicate:predicate];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:
                                      [NSSortDescriptor sortDescriptorWithKey:@"objectId" ascending:YES]]];
    [managedObjectContext performBlockAndWait:^
        NSError *error = nil;
        results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    ];

    return results;



- (void)downloadDataForRegisteredObjects:(BOOL)useUpdatedAtDate 
    NSMutableArray *operations = [NSMutableArray array];

    for (NSString *className in self.registeredClassesToSync) 
        NSDate *mostRecentUpdatedDate = nil;
        if (useUpdatedAtDate) 
            mostRecentUpdatedDate = [self mostRecentUpdatedAtDateForEntityWithName:className];
        
        NSMutableURLRequest *request = [[MLVAFParseAPIClient sharedClient]
                                        GETRequestForAllRecordsOfClass:className
                                        updatedAfterDate:mostRecentUpdatedDate];
        AFHTTPRequestOperation *operation = [[MLVAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) 
            if ([responseObject isKindOfClass:[NSDictionary class]]) 
            [self writeJSONResponse:responseObject toDiskForClassWithName:className];
                NSLog(@"Response for %@: %@", className, responseObject);

            
         failure:^(AFHTTPRequestOperation *operation, NSError *error) 
            NSLog(@"Request for class %@ failed with error: %@", className, error);
        ];

        [operations addObject:operation];
    

    [[MLVAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) 

     completionBlock:^(NSArray *operations) 
        NSLog(@"All operations completed");

[self processJSONDataRecordsIntoCoreData];
    ];


- (void)processJSONDataRecordsIntoCoreData 
    NSManagedObjectContext *managedObjectContext = [[MLVCoreDataController sharedInstance] backgroundManagedObjectContext];
    //
    // Iterate over all registered classes to sync
    //
    for (NSString *className in self.registeredClassesToSync) 
        if (![self initialSyncComplete]) 
            NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
            NSArray *records = [JSONDictionary objectForKey:@"results"];
            for (NSDictionary *record in records) 
                [self newManagedObjectWithClassName:className forRecord:record];
            
         else 

            NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:@"objectId"];
            if ([downloadedRecords lastObject]) 

                NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:@"objectId" usingArrayOfIds:[downloadedRecords valueForKey:@"objectId"] inArrayOfIds:YES];
                int currentIndex = 0;

                for (NSDictionary *record in downloadedRecords) 
                    NSManagedObject *storedManagedObject = nil;

                    if ([storedRecords count] > currentIndex) 
                        storedManagedObject = [storedRecords objectAtIndex:currentIndex];
                    

                    if ([[storedManagedObject valueForKey:@"objectId"] isEqualToString:[record valueForKey:@"objectId"]]) 

                        [self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
                     else 

                        [self newManagedObjectWithClassName:className forRecord:record];
                    
                    currentIndex++;
                
            
        

        [managedObjectContext performBlockAndWait:^
            NSError *error = nil;
            if (![managedObjectContext save:&error]) 
                NSLog(@"Unable to save context for class %@", className);
            
        ];


        [self deleteJSONDataRecordsForClassWithName:className];
        [self executeSyncCompletedOperations];
    




- (void)initializeDateFormatter 
    if (!self.dateFormatter) 
        self.dateFormatter = [[NSDateFormatter alloc] init];
        [self.dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
        [self.dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
    


- (NSDate *)dateUsingStringFromAPI:(NSString *)dateString 
    [self initializeDateFormatter];
    dateString = [dateString substringWithRange:NSMakeRange(0, [dateString length]-5)];

    return [self.dateFormatter dateFromString:dateString];


- (NSString *)dateStringForAPIUsingDate:(NSDate *)date 
    [self initializeDateFormatter];
    NSString *dateString = [self.dateFormatter stringFromDate:date];
    dateString = [dateString substringWithRange:NSMakeRange(0, [dateString length]-1)];
    dateString = [dateString stringByAppendingFormat:@".000Z"];

    return dateString;


#pragma mark - File Management

- (NSURL *)applicationCacheDirectory

    return [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];


- (NSURL *)JSONDataRecordsDirectory

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *url = [NSURL URLWithString:@"JSONRecords/" relativeToURL:[self applicationCacheDirectory]];
    NSError *error = nil;
    if (![fileManager fileExistsAtPath:[url path]]) 
        [fileManager createDirectoryAtPath:[url path] withIntermediateDirectories:YES attributes:nil error:&error];
    
    return url;




-(void)writeJSONResponse:(id)response toDiskForClassWithName:(NSString *)className
    NSURL *fileURL = [NSURL URLWithString:className relativeToURL:[self JSONDataRecordsDirectory]] ;
    if (![(NSDictionary *)response writeToFile:[fileURL path] atomically:YES]) 
        NSLog(@"Error saving response to disk, will attempt to remove NSNull values and try again.");
        //remove NSNulls and try again...
        NSArray *records = [response objectForKey:@"results"];
        NSMutableArray *nullFreeRecords = [NSMutableArray array];
        for (NSDictionary *record in records) 
            NSMutableDictionary *nullFreeRecord = [NSMutableDictionary dictionaryWithDictionary:record];
            [record enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) 
                if ([obj isKindOfClass:[NSNull class]]) 
                    [nullFreeRecord setValue:nil forKey:key];
                
            ];
            [nullFreeRecords addObject:nullFreeRecord];
        

        NSDictionary *nullFreeDictionary = [NSDictionary dictionaryWithObject:nullFreeRecords forKey:@"results"];

        if (![nullFreeDictionary writeToFile:[fileURL path] atomically:YES]) 
            NSLog(@"Failed all attempts to save response to disk: %@", response);
        
    


- (void)deleteJSONDataRecordsForClassWithName:(NSString *)className 
    NSURL *url = [NSURL URLWithString:className relativeToURL:[self JSONDataRecordsDirectory]];

                  NSError *error = nil;
                  BOOL deleted = [[NSFileManager defaultManager] removeItemAtURL:url error:&error];
                  if (!deleted) 
                      NSLog(@"Unable to delete JSON records at %@, reason %@", url, error);
                  



- (NSDictionary *)JSONDictionaryForClassWithName:(NSString *)className 
    NSURL *fileURL = [NSURL URLWithString:className relativeToURL:[self JSONDataRecordsDirectory]];

    return [NSDictionary dictionaryWithContentsOfURL:fileURL];


- (NSArray *)JSONDataRecordsForClass:(NSString *)className sortedByKey:(NSString *)key 
    NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
    NSArray *records = [JSONDictionary objectForKey:@"results"];
    return [records sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:key ascending:YES]]];

@end

【问题讨论】:

我已经删除了 MLVDataController.h 和 CoreData 模型文件,因为它超过了字符数限制。我知道粘贴网站的格式不好,但它们看起来像 codetidy.com/6540 只是好奇,为什么不直接使用 ios 框架,并为 PFObjects 发出查询? 在执行 saveJSONtoDisk 方法之前,您是否看到 parse.com HTTP 响应的 NSLog() 结果?如果在 saveJSONResponse 方法中设置断点会发生什么?为什么目录不存在,除非它们从未被创建? 【参考方案1】:

老实说,如果您的数据模型如此简单,并且您在每次加载时都删除本地持久存储/缓存,那么使用 Core Data 可能会好得多。

通过根据需要加载数据来保持简单。使用NSCoding 保存一个缓存,最初作为占位符加载,同时应用等待下载新信息。

【讨论】:

这只是数据模型的起点。一旦用户从 Core Data 中选择了项目,它们就会被缓存。使用 Parse 是为了能够根据需要加载新项目。 PS。我刚刚在 NSCoding 上搜索了 NSCoding,看到了你的名字并意识到我刚刚在 Helios 上收听了你的播客。好东西!【参考方案2】:

事实证明答案非常简单,本教程使用的是旧版本的 AFNetworking,其中 AFHTTPRequestOperation 可以下载数据但不将其视为 JSON。

AFHTTPRequestOperation *operation = [[MLVAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) 
            if ([responseObject isKindOfClass:[NSDictionary class]]) 
            [self writeJSONResponse:responseObject toDiskForClassWithName:className];
                NSLog(@"Response for %@: %@", className, responseObject);
            
         

需要用 AFJSONRequestOperation 更新

  AFJSONRequestOperation *operation =
        [AFJSONRequestOperation JSONRequestOperationWithRequest: request
                                                        success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
         
             NSDictionary * responseObject = (NSDictionary * )JSON;
             if ([responseObject isKindOfClass:[NSDictionary class]]) 
                 NSLog(@"Response for %@: %@", className, responseObject);
                 [self writeJSONResponse:responseObject toDiskForClassWithName:className];

【讨论】:

以上是关于使用 Parse 同步数据,而不是下载数据的主要内容,如果未能解决你的问题,请参考以下文章

如何将Parse通道与我们的API同步以正确提供推送通知

Android:如何使用 Parse.com 的 Bolts 同步查询?

如何正确同步解析与本地数据存储?

解析本地数据存储 + 网络同步

为啥使用 HttpClient 而不是 HttpWebRequest 进行同步请求

Parse:如何重置本地数据存储?