iOS: +[NSString stringWithUTF8String] producing unpredictable results? - ios

I'm retrieving some UTF8-encoded HTML from a server. (In this particular case, I can tell that every time I make a request, I'm getting 763 bytes back.)
I then pass these bytes to +[NSString stringWithUTF8String], and about 90% of the time, I get a resulting string that looks exactly like the HTML in my file on the server. 10% of the time, I get nil.
I have utterly no reason why this would be inconsistent. The bytes being read from the server by NSURLConnection are identical every time.
Anyone have any ideas? Here is the code:
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:url] queue:customTableOperationQueue
completionHandler: ^(NSURLResponse* response, NSData* data, NSError* error)
{
if (! error)
{
NSString* newTableHTML = [NSString stringWithUTF8String:data.bytes];
NSLog(#"data.length = %d, newTableHTML.length = %d", data.length, newTableHTML.length);
self.tableHTML = newTableHTML;
}
else
{
self.tableHTML = nil;
self.errorMessage = error.localizedDescription;
}
}
];

Related

backslashes "\" added in JSON string for web service in swift

In my project I need to send JSON object in web service API call. I have converted JSON from array.
do {
let theJSONData = try NSJSONSerialization.dataWithJSONObject(
param ,
options: NSJSONWritingOptions(rawValue: 0))
var theJSONText : String = String(data: theJSONData,
encoding: NSASCIIStringEncoding)!
print(theJSONText)
theJSONText = theJSONText.stringByReplacingOccurrencesOfString("\\", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
print(theJSONText)
let newParam = ["ESignData":theJSONText]
} catch let error as NSError {
print(error)
}
it print string correctly as
{"EntNum":"47","JobNo":"1737753","ClientID":"100","HospNo":"1","QAReason":"","DoctorNo":"1694","Action":"Sign"}
{"EntNum":"47","JobNo":"1737753","ClientID":"100","HospNo":"1","QAReason":"","DoctorNo":"1694","Action":"Sign"}
Now When I try to send this newParam dictionary in API call, it contains "\" in string parameters of JSON string.
WebService.PostURL(mainLink, methodname: ESIGNTHISDOC, param: newParam, userName: AUTH_USERNAME, password: AUTH_PWD, CompletionHandler: { (success, response) in
})
And in that web service method I have print param.
Param = {
ESignData = "{\"EntNum\":\"47\",\"JobNo\":\"1737753\",\"ClientID\":\"100\",\"HospNo\":\"1\",\"QAReason\":\"\",\"DoctorNo\":\"1694\",\"Action\":\"Sign\"}";
}
Now in this I know it is obvious in iOS because of " in string. Now the problem is that there are lots of APIs working in android app, and the API developer doesn't want to update his code according to us.
I know this problem happens because of adding JSON string in dictionary as parameter. But I have not proper justification for that so if any proof will be also helpful for me to convince him.
Any solution to convert the JSON string without backslash in iOS? I need to fix from my side if possible. Any help will be appreciate.
EDIT :
On server side it needs like
ESignData = {"EntNum":"47","JobNo":"1737753","ClientID":"100","HospNo":"1","QAReason":"","DoctorNo":"1694","Action":"Sign"}
If I pass this as parameter in POSTMAN than it gives success message. But not with our object with "\" in it.
EDIT 2:
Now printing the newParam dictionary:
print(newParam)
print("-------------------------")
print(newParam["ESignData"])
And logs :
["ESignData": "{\"EntNum\":\"47\",\"JobNo\":\"1737754\",\"ClientID\":\"100\",\"HospNo\":\"1\",\"QAReason\":\"\",\"DoctorNo\":\"1694\",\"Action\":\"Sign\"}"]
-------------------------
Optional("{\"EntNum\":\"47\",\"JobNo\":\"1737754\",\"ClientID\":\"100\",\"HospNo\":\"1\",\"QAReason\":\"\",\"DoctorNo\":\"1694\",\"Action\":\"Sign\"}")
And by debug :
Printing description of newParam:
▿ 1 elements
▿ [0] : 2 elements
- .0 : "ESignData"
- .1 : "{\"EntNum\":\"47\",\"JobNo\":\"1737754\",\"ClientID\":\"100\",\"HospNo\":\"1\",\"QAReason\":\"\",\"DoctorNo\":\"1694\",\"Action\":\"Sign\"}"
So it shows that it is in our dictionary. All the " are joined by \.
I ran into this exact issue today. For me it appears that the default encoding for any NSURLRequest is a string. So, somewhere between my creating the dictionary request and the server parsing it, the backslashes would appear and the server had problems with my payload.
I solved the issue by explicitly stating that my payload was JSON by setting the content type header.
[authRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
Now when I create JSON data from a dictionary, the backslashes don't appear and the server is able to parse everything correctly.
Code snippet below for completeness:
NSMutableURLRequest *authRequest = [[[NSURLRequest alloc] initWithURL:authURL] mutableCopy];
[authRequest setHTTPMethod:#"POST"];
NSURLSession *session = [NSURLSession sharedSession];
[authRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSDictionary *bodyDictionary = #{#"User_Name": user, #"Password_Hash": password};
if ([NSJSONSerialization isValidJSONObject:bodyDictionary]) {
NSError *error;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:bodyDictionary options:0 error:&error];
if (!error) {
[authRequest setHTTPBody:bodyData];
} else {
NSLog(#"Unable to convert to JSON DATA %#", error.localizedDescription);
}
}

Azure DocumentDB Intermittent 401 error when querying REST API via Obj-c

I've been charged with implementing an objective-c based iOS query of the Azure DocumentDB system using the REST API scheme. Utilizing the code found on github, specifically https://github.com/Azure/azure-storage-ios I was able to generate a request that appropriately authenticates and returns the appropriate data.... sometimes.
The problem: I receive a 401 (authentication failure) error response from the server intermittently. Making the same request via Node.js does not encounter this behavior, so I believe this to be an issue with my objective-c implementation.
- (NSMutableURLRequest *) RequestWithQuery:(NSString*)query Parameters:(NSArray*)parameters {
NSError* error;
NSDictionary* dictionaryOfBodyContents = #{#"query":query,
#"parameters":parameters};
NSData* body = [NSJSONSerialization dataWithJSONObject:dictionaryOfBodyContents
options:NSJSONWritingPrettyPrinted
error:&error];
if(error != nil) {
NSLog(#"AzureRequestWithQueryParameters error generating the body: %#",error);
return nil;
}
char buffer[30];
struct tm * timeptr;
time_t time = (time_t) [[NSDate date] timeIntervalSince1970];
timeptr = gmtime(&time);
if (!strftime_l(buffer, 30, [#"%a, %d %b %Y %T GMT" UTF8String], timeptr, NULL))
{
NSException* myException = [NSException
exceptionWithName:#"Error in date/time format"
reason:#"Unknown"
userInfo:nil];
#throw myException;
}
NSString* date = [NSString stringWithUTF8String:buffer];
// generate auth token
NSString* authorizationToken = [self AuthorizationTokenForTableQueryWithDate:date];
// generate header contents
NSDictionary* dictionaryOfHeaderContents = #{#"authorization":authorizationToken,
#"connection":AZURE_CONNECTION_HEADER_CONNECTION,
#"content-type":AZURE_CONNECTION_HEADER_CONTENTTYPE,
#"content-length":[NSString stringWithFormat:#"%lu",(unsigned long)[body length]],
#"x-ms-version":AZURE_CONNECTION_APIVERSION,
#"x-ms-documentdb-isquery":#"true",
#"x-ms-date":date.lowercaseString,
#"cache-control":#"no-cache",
#"user-agent":AZURE_CONNECTION_HEADER_USERAGENT,
#"accept":#"application/json"};
// generate url contents
NSString* urlString = [NSString stringWithFormat:#"https://%#:%#/%#", AZURE_URL_HOST, AZURE_URL_PORT, AZURE_URL_DOCUMENTS];
NSURL* url = [NSURL URLWithString:urlString];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:AZURE_CONNECTION_METHOD];
[request setAllHTTPHeaderFields:dictionaryOfHeaderContents];
[request setHTTPBody:body];
return request;
}
- (NSString*) AuthorizationTokenForTableQueryWithDate:(NSString*)date {
//
// Based on https://msdn.microsoft.com/en-us/library/azure/dd179428.aspx under "Table Service (Shared Key Authentication)"
//
// generating a authentication token is a Hash-based Message Authentication Code (HMAC) constructed from the request
// and computed by using the SHA256 algorithm, and then encoded by using Base64 encoding.
//
// StringToSign = VERB + "\n" +
// Content-MD5 + "\n" +
// Content-Type + "\n" +
// Date + "\n" +
// CanonicalizedHeaders +
// CanonicalizedResource;
//
NSString* StringToSign = [NSString stringWithFormat:#"%#\n%#\n%#\n%#\n\n",
AZURE_CONNECTION_METHOD.lowercaseString?:#"",
AZURE_RESOURCE_TYPE.lowercaseString?:#"",
AZURE_URL_COLLECTIONS.lowercaseString?:#"",
date.lowercaseString?:#""];
// Generate Key/Message pair
NSData* keyData = [[NSData alloc] initWithBase64EncodedString:AZURE_AUTH_KEY options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSData* messageData = [StringToSign dataUsingEncoding:NSUTF8StringEncoding];
// Encrypt your Key/Message using HMAC SHA256
NSMutableData* HMACData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, keyData.bytes, keyData.length, messageData.bytes, messageData.length, HMACData.mutableBytes);
// Take your encrypted data, and generate a token that Azure likes.
NSString* signature = [HMACData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSString* unencodedToken = [NSString stringWithFormat:#"type=master&ver=1.0&sig=%#",signature];
NSString* authorizationToken = [unencodedToken stringByReplacingOccurrencesOfString:#"&" withString:#"%26"];
authorizationToken = [authorizationToken stringByReplacingOccurrencesOfString:#"=" withString:#"%3D"];
return authorizationToken;
}
If anyone has encountered a similar intermittent 401 and was able to resolve any help would be appreciated. Or suggestions for debugging steps for the above code bearing in mind, I have attempted decrementing the timestamp by a few seconds, similar intermittent failures.
Although simply retrying a few times upon a failure while decrementing the seconds results in a 200 response in 1-2 retries, I don't feel it is an ideal solution by any means.
Thank you for your time.
Update: Please see Andrew Liu's explanation below for the reason for this failure. I have flagged his response as the answer, below is the updated snippet of code.
NSString* unencodedToken = [NSString stringWithFormat:#"type=master&ver=1.0&sig=%#",signature];
// NSString* authorizationToken = [unencodedToken stringByReplacingOccurrencesOfString:#"&" withString:#"%26"];
// authorizationToken = [authorizationToken stringByReplacingOccurrencesOfString:#"=" withString:#"%3D"];
NSString* authorizationToken = [unencodedToken stringByAddingPercentEncodingWithAllowedCharacters:[[NSCharacterSet characterSetWithCharactersInString:#"&+="] invertedSet]];
return authorizationToken;
401 (auth failure) usually indicates that something is wrong with the auth token.
It's important to note that the auth token is a Base64-encoded string - meaning it can contain the + character.
The db server expects + characters in the auth token to be url encoded (%2B)... some but not all HTTP clients will automatically encode HTTP headers for you.
I suspect url-encoding or converting + to %2B for the following variable will fix your intermittent 401 issue:
NSString* signature = [HMACData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
I've seen this issue before and usually it has to do with the final 2 steps of the protocol, i.e. either the base64 encoding is whacky, or the URI encoding. One way to debug this would be to print the auth token that you had sent, in case of failure, and see if there are any strange characters which are possibly not being transmitted correctly. You can post the buggy token here and I can take a look.

CJSONDeserialize error

I am working on a JSON parsing application. I am using touchjson for parsing the json url which is --
https://dl.dropboxusercontent.com/u/746330/facts.json
{
"title":"About Canada",
"rows":[
{
"title":"Beavers",
"description":"Beavers are second only to humans in their ability to manipulate and change their environment. They can measure up to 1.3 metres long. A group of beavers is called a colony",
"imageHref":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/American_Beaver.jpg/220px-American_Beaver.jpg"
},
{
"title":"Flag",
"description":null,
"imageHref":"http://images.findicons.com/files/icons/662/world_flag/128/flag_of_canada.png"
},
{
"title":"Transportation",
"description":"It is a well known fact that polar bears are the main mode of transportation in Canada. They consume far less gas and have the added benefit of being difficult to steal.",
"imageHref":"http://1.bp.blogspot.com/_VZVOmYVm68Q/SMkzZzkGXKI/AAAAAAAAADQ/U89miaCkcyo/s400/the_golden_compass_still.jpg"
},
{
"title":"Hockey Night in Canada",
"description":"These Saturday night CBC broadcasts originally aired on radio in 1931. In 1952 they debuted on television and continue to unite (and divide) the nation each week.",
"imageHref":"http://fyimusic.ca/wp-content/uploads/2008/06/hockey-night-in-canada.thumbnail.jpg"
},
{
"title":"Eh",
"description":"A chiefly Canadian interrogative utterance, usually expressing surprise or doubt or seeking confirmation.",
"imageHref":null
},
{
"title":"Housing",
"description":"Warmer than you might think.",
"imageHref":"http://icons.iconarchive.com/icons/iconshock/alaska/256/Igloo-icon.png"
},
{
"title":"Public Shame",
"description":" Sadly it's true.",
"imageHref":"http://static.guim.co.uk/sys-images/Music/Pix/site_furniture/2007/04/19/avril_lavigne.jpg"
},
{
"title":null,
"description":null,
"imageHref":null
},
{
"title":"Space Program",
"description":"Canada hopes to soon launch a man to the moon.",
"imageHref":"http://files.turbosquid.com/Preview/Content_2009_07_14__10_25_15/trebucheta.jpgdf3f3bf4-935d-40ff-84b2-6ce718a327a9Larger.jpg"
},
{
"title":"Meese",
"description":"A moose is a common sight in Canada. Tall and majestic, they represent many of the values which Canadians imagine that they possess. They grow up to 2.7 metres long and can weigh over 700 kg. They swim at 10 km/h. Moose antlers weigh roughly 20 kg. The plural of moose is actually 'meese', despite what most dictionaries, encyclopedias, and experts will tell you.",
"imageHref":"http://caroldeckerwildlifeartstudio.net/wp-content/uploads/2011/04/IMG_2418%20majestic%20moose%201%20copy%20(Small)-96x96.jpg"
},
{
"title":"Geography",
"description":"It's really big.",
"imageHref":null
},
{
"title":"Kittens...",
"description":"Éare illegal. Cats are fine.",
"imageHref":"http://www.donegalhimalayans.com/images/That%20fish%20was%20this%20big.jpg"
},
{
"title":"Mounties",
"description":"They are the law. They are also Canada's foreign espionage service. Subtle.",
"imageHref":"http://3.bp.blogspot.com/__mokxbTmuJM/RnWuJ6cE9cI/AAAAAAAAATw/6z3m3w9JDiU/s400/019843_31.jpg"
},
{
"title":"Language",
"description":"Nous parlons tous les langues importants.",
"imageHref":null
}
]
}
For some reason i am getting the error when i use CJONDeserializer ---
Error Domain=CJSONDeserializerErrorDomain Code=-104 "Could not scan dictionary. Failed to scan a value." UserInfo=0x7feb38d99ed0 {NSLocalizedDescription=Could not scan dictionary. Failed to scan a value., line=0, character=0, location=0, snippet=!HERE>!{
"title":"About Can}
and if I use NSJSONSerializer it gives me the following error ---
"Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Unable to convert data to string around character 2643.) UserInfo=0x7f8559c56700 {NSDebugDescription=Unable to convert data to string around character 2643.}”
For the code I am using, it doesn't give me any error if I use any other json url.
Here is the code that I am using --
- (void)viewDidLoad {
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
if (connection) {
self.receivedData = [[NSMutableData alloc] init];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
NSError *error = nil;
NSDictionary *receivedDataDictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:self.receivedData error:&error];
NSArray *arrayFromJson = [receivedDataDictionary objectForKey:#"title"];
for (NSDictionary *tempDict in arrayFromJson) {
NSLog(#"blah %#", tempDict);
}
}
Any help will be much appreciated. Thanks in advance.
First thing from your JSON : "title" doesnt contain array, array is in key "rows".
Second : It looks like Problem is in your JSON. Your JSON contains "É" in third last object. CJSONDeserializer is not able to decode this kind of chars. Use NSJSONSerialization. Try following code.
NSError *error;
NSString *string = [NSString stringWithContentsOfURL:[NSURL URLWithString: urlString] encoding:NSISOLatin1StringEncoding error:&error];
NSData *resData = [string dataUsingEncoding:NSUTF8StringEncoding];
id jsonObject = [NSJSONSerialization JSONObjectWithData:resData options:kNilOptions error:&error];
if (error) {
//Error handling
} else {
//use your json object
NSArray *arrayFromJson = [jsonObject objectForKey:#"rows"];
for (NSDictionary *tempDict in arrayFromJson) {
NSLog(#"blah %#", tempDict);
}
}

How one tests http requests in iOS 8?

In ruby I used to test http requests with vcr gem which recorded the request so the tests didn't send request to real host. Is there anything like this in iOS8 world?
The requests I want to test really need to be recorded since those requests may be outdated in some time and will return some other response
P.S. It would be great if it was some default Apple/iOS approach/library like XCTest for testing in general
What you want is something like OHHTTPStubs or Nocilla or AMY server. All of them essentially use NSURLProtocol to intercept your request and allow you to designate a response. We used OHHTTPStubs but pick the one with the feature set closest to your use case.
Here's an example of an OHHTTPStubs implementation in a unit test for a service that talks to a single REST endpoint:
NSString *loadRoomJSON = #{ #"key" : #"value" }; /* some JSON */
NSNumber identifier = #1;
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSString *url = [NSString stringWithFormat:#"v1/user/%#/room", identifier];
XCTAssert([request.URL.relativePath containsString:url], #"Expected certain URL");
return YES;
} andRespond:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [OHHTTPStubsResponse responseWithJSONObject:loadRoomJSON statusCode:200 headers:nil];
}];
XCTestExpectation *loadPromise = [self expectation:#"Room loaded"];
[service loadRoomOnSucceed:^(Room *room) {
// Do your asserts here. For us, the JSON is mapped to an object
// so for example you could assert that the object is mapped correctly
[loadPromise fulfill];
} onFail:^(NSError *error) {
expect(error).to.beNil();
}];
[self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) {
expect(error).to.beNil();
}];
In reality our tests are shorter since we write wrapper/helpers to make it read better so this is an exploded-out version. Should give you the general idea. OHHTTPStubs (if you use it) has helper functions to load responses directly from files as well.
Im not sure if I understood you correct. But if I understand you right, you should be able to use XCTest to test your request and response.
class Tests:XCTestCase{
func testing(){
var expectation = self.expectationWithDescription("Your request")
var url = NSURL(string: "http://yourUrl.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
if let httpRes = response as? NSHTTPURLResponse {
println("status code=",httpRes.statusCode)
//200 means OK
if httpRes.statusCode == 200 {
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
}else{
println("error \(error)")
}
}
}
}

How do I save thousands of objects to Parse.com? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
In my iOS app that uses Parse, some users will need to save thousand of objects in one action. I've tried iterating through the array of data and creating/saving the objects one by one, but this results in the objects saving to my data browser pretty slowly. Each object only needs to contain a few strings, so I don't understand why its taking so long to save these objects.
Is there a faster way to save thousands of objects to Parse?
Edited:
I've always used [PFObject saveAllInBackground:array block:^(BOOL succeeded, NSError *error) {}]; but....another method I have just attempted semi-successfully, was to upload a Json string as a PFFile(no 128k limit), and then use cloud code to parse it and create the necessary PFObjects. I was able to get this to work with small quantities, but unfortunately the cloud code timed out when using a large quantity. I instead opted to utilize a background job to perform the parsing. This takes a considerable amount of time before the data is completely available, but can handle the large amount of data. The upload time itself was much quicker. When using 1000 objects with 3 strings each upload was roughly .8 seconds, vs 23 seconds doing a save all in background, 5000 objects 3 strings each was only 2.5 seconds upload time. In addition to the quicker time you also get progress updates. Depending on the use-case, utilizing this alternative may work best if immediate and quick upload is important, vs making the data immediately available.
IOS Code:
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");
}];
Edited: Instead of saving in the beforeSave or afterSave Cloud Code which can cause timeout issues, the background job below can be run anytime. It grab all rows in the "fileTestSave" table, parses the JSON strings in those rows, and adds them to the "Person" table. Once completed it will rows from the table. All asynchronously!
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");
}
);
}
Old Cloud Code that timed out as reference:
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");
}
});
});
Another method for uploading thousands of objects in the background, again this takes some time but can be sized to avoid timing out, as arrays are saved in chunks recursively. I've had no problem saving 10k+ items. Implemented as a category, just enter how many objects at a time you want save at a time, it will save them in the background serially, and recursively until all objects are saved, it also features progress updating via a separate block.
// 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
I think you should be able to do this with sending the save process in quantities of five into the background, so to speak fork it, "thread" it, as apple would refer to it.
here is the link to apples ios threading guides.
I have not used it yet, but I will soon need it as well, as I'm working on a massive database app.
here's the link
https://developer.apple.com/library/mac/Documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html
If you have an array of objects, you can use saveAllInBackgroundWithBlock. This method takes an array of PFObjects as its argument:
https://parse.com/docs/ios/api/Classes/PFObject.html#//api/name/saveAllInBackground:block:
For the faster processing, you can use the parse cloud code, whihc is simply a javascript. You can create a function which takes the array of the data as argument and then in function, you can save the objects.
Parse cloud code has better processing speed than the native one.
For its use, you can refer :
https://parse.com/docs/cloud_code_guide
https://parse.com/docs/js_guide

Resources