如何将数千个对象保存到Parse.com?

时间:2014-05-24 03:25:24

标签: ios objective-c parse-platform

在我使用Parse的iOS应用程序中,一些用户需要在一个操作中保存数千个对象。我已经尝试迭代数据数组并逐个创建/保存对象,但这会导致对象保存到我的数据浏览器的速度非常慢。每个对象只需要包含几个字符串,所以我不明白为什么它需要这么长时间来保存这些对象。

是否有更快的方法将数千个对象保存到Parse?

5 个答案:

答案 0 :(得分:8)

编辑:

我一直使用[PFObject saveAllInBackground:数组块:^(BOOL成功,NSError *错误){}];但....我刚刚尝试半成功的另一种方法是上传一个Json字符串作为PFFile(没有128k限制),然后使用云代码解析它并创建必要的PFObjects。我能够以小批量工作,但不幸的是,当使用大量数据时,云代码超时。我改为选择使用后台作业来执行解析。这在数据完全可用之前需要相当长的时间,但可以处理大量数据。上传时间本身要快得多。当使用具有3个字符串的1000个对象时,每个上载大约为0.8秒,而在后台执行全部保存则为23秒,5000个对象3个字符串,每个上传时间仅为2.5秒。除了更快的时间,您还可以获得进度更新。根据使用情况,如果立即快速上传很重要,那么利用这种替代方案可能效果最好,而不是立即提供数据。

IOS代码:

  NSMutableArray *array = [NSMutableArray array];
    for (int i = 0; i<5; i++) {
        //subclass of PFObject
        Employee *employee = [Employee object];
        employee.firstName = @"FName";
        employee.lastName = @"LName";
        employee.employeeID = @"fid54";
        [array addObject:[employee dictionaryWithValuesForKeys:employee.allKeys]];
    }
    //Seperate class only to store the PFFiles
    PFObject *testObject = [PFObject objectWithClassName:@"fileTestSave"];
    testObject[@"testFile"] = [PFFile fileWithData:[NSJSONSerialization dataWithJSONObject:array options:0 error:nil]];

    NSLog(@"started");
    //**notice I am only saving the test object with the NSData from the JSONString**
    [testObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (!error && succeeded) NSLog(@"succeeded");
        else NSLog(@"error");
    }];

已编辑:以下后台作业可以随时运行,而不是保存在beforeSave或afterSave Cloud Code中,这可能导致超时问题。它抓取&#34; fileTestSave&#34;中的所有行。 table,解析这些行中的JSON字符串,并将它们添加到&#34; Person&#34;表。一旦完成,它将从表中行。所有异步!

var _ = require('underscore.js');
Parse.Cloud.job("userMigration", function(request, status) 
{

    // Set up to modify user data
    Parse.Cloud.useMasterKey();
    //Table called fileTestSave stores a PFFile called "testFile" which we will use an HTTPRequest to get the data. Is there a better way to get the data?
    //This PFFile stores a json string which contains relavent data to add to the "Person" table
    var testFileSave = Parse.Object.extend("fileTestSave");
    var query = new Parse.Query(testFileSave);
    query.find().then(function(results) 
    {
        //Generate an array of promises
        var promises = [];

        _.each(results, function(testFileSaveInstance){
            //add promise to array
            promises.push(saveJsonPerson(testFileSaveInstance));
        });
        //only continue when all promises are complete
        return Parse.Promise.when(promises);
    }).then(function() 
    {

    // Set the job's success status
        console.log("Migration Completed NOW");
        status.success("Migration completed");
    }, function(error) {
    // Set the job's error status
        status.error("Uh oh, something went wrong.");
    });
});

function saveJsonPerson(fileTestSave)
{
    //Get the pffile testfile
    var testFile = fileTestSave.get("testFile");
    //get the fileURL from the PFFile to generate the http request
    var fileURL = testFile["url"]();
    //return the promise from the httpRequest
    return Parse.Cloud.httpRequest({
        method:"GET",
        url: fileURL
    }).then(function(httpResponse){
            //return the promise from the parsing
            return parsehttpResponse(httpResponse,fileTestSave);
        },
        function(error){
            console.log("http response error");
        }
    );
}

function parsehttpResponse(httpResponse,fileTestSave)
{   
    var jsonArray = eval( '(' + httpResponse.text + ')' );
    var saveArray =[];

    //parse each person in the json string, and add them to the saveArray for bulk saving later.
    for (i in jsonArray) 
    {
        var personExtend = Parse.Object.extend("Person");
        var person = new personExtend();
        person.set("classDiscriminator",jsonArray[i]["classDiscriminator"]);
        person.set("lastName",jsonArray[i]["lastName"]);
        person.set("firstName",jsonArray[i]["firstName"]);
        person.set("employeeID",jsonArray[i]["employeeID"]);
        saveArray.push(person);
    };
    //return the promise from the saveAll(bulk save)
    return Parse.Object.saveAll(
            saveArray
        ).then(function(){
                //return the promise from the destory
                return fileTestSave.destroy(

                    ).then(function(){

                    },function(error){
                            console.log("error destroying");
                        }
                );
        },function(error){
                console.log("Error Saving");
            }
    );
}

作为参考超时的旧Cloud Code:

Parse.Cloud.afterSave("fileTestSave", function(request) {

    //When accessing PFFiles you don't get the actual data, there may be an easier way, but I just utitlized an HTTPRequest to get the data, and then continued parsing.
    var file = request.object.get("testFile");
    var fileURL = file["url"]();
    console.log("URL:"+fileURL);
    Parse.Cloud.httpRequest({
            method:"GET",
            url: fileURL,
            success: function(httpResponse) 
            {
                var jsonArray = eval( '(' + httpResponse.text + ')' );
                var saveArray =[];
                for (i in jsonArray) 
                {
                    var personExtend = Parse.Object.extend("Person");
                    var person = new personExtend();
                                    //May be a better way to parse JSON by using each key automatically, but I'm still new to JS, and Parse so I set each individually.
                    person.set("classDiscriminator",array[i]["classDiscriminator"]);
                    person.set("lastName",array[i]["lastName"]);
                    person.set("firstName",array[i]["firstName"]);
                    person.set("employeeID",array[i]["employeeID"]);
                    saveArray.push(person);
                };
                Parse.Object.saveAll(saveArray,
                {
                    success: function(list) {
                      // All the objects were saved.
                    },
                    error: function(error) {
                      // An error occurred while saving one of the objects.
                    },
                });
            },
            error: function(httpResponse) {
            console.log("http response error");
            }
    });
});

答案 1 :(得分:2)

另一种在后台上传数千个对象的方法,这需要一些时间,但可以调整大小以避免超时,因为数组以递归方式保存在块中。我保存10k +物品没问题。作为一个类别实现,只需要一次输入您想要保存的对象数量,它会将它们串行保存在后台,并递归保存,直到保存所有对象,它还通过单独的块进行更新。

//  PFObject+addOns.h
#import <Parse/Parse.h>

@interface PFObject (addOns)
+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block progressBlock:(PFProgressBlock)progressBlock;
@end


#import "PFObject+addOns.h"

@interface PFObject (addOns_internal)
+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block trigger:(void(^)())trigger;
@end

@implementation PFObject (addOns)

+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block progressBlock:(PFProgressBlock)progressBlock
{
    unsigned long numberOfCyclesRequired = array.count/chunkSize;
    __block unsigned long count = 0;
    [PFObject saveAllInBackground:array chunkSize:chunkSize block:block trigger:^() {
        count++;
        progressBlock((int)(100.0*count/numberOfCyclesRequired));
    }];
}

+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block trigger:(void(^)())trigger
{

    NSRange range = NSMakeRange(0, array.count <= chunkSize ? array.count:chunkSize);
    NSArray *saveArray = [array subarrayWithRange:range];
    NSArray *nextArray = nil;
    if (range.length<array.count) nextArray = [array subarrayWithRange:NSMakeRange(range.length, array.count-range.length)];
    [PFObject saveAllInBackground:saveArray block:^(BOOL succeeded, NSError *error) {
        if(!error && succeeded && nextArray){
            trigger(true);
            [PFObject saveAllInBackground:nextArray chunkSize:chunkSize block:block trigger:trigger];
        }
        else
        {
            trigger(true);
            block(succeeded,error);
        }
    }];
}

@end

答案 2 :(得分:1)

我认为你应该能够将数量为5的保存过程发送到后台,所以说吧,#34;线程&#34;它,正如苹果所指的那样。

这是指向apple ios线程指南的链接。 我还没有使用它,但我很快就会需要它,因为我正在开发一个庞大的数据库应用程序。

这里是链接

https://developer.apple.com/library/mac/Documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html

答案 3 :(得分:1)

如果您有一组对象,则可以使用saveAllInBackgroundWithBlock。此方法将PFObjects数组作为其参数:

https://parse.com/docs/ios/api/Classes/PFObject.html#//api/name/saveAllInBackground:block

答案 4 :(得分:0)

为了加快处理速度,你可以使用解析云代码,这只是一个javascript。您可以创建一个函数,该函数将数据数组作为参数,然后在函数中,您可以保存对象。 解析云代码具有比原生代码更好的处理速度。

如需使用,您可以参考:

https://parse.com/docs/cloud_code_guide https://parse.com/docs/js_guide