I'm working on an iOS project in which huge amounts of data need to be downloaded from the internet and saved in a database. I'm using MagicalRecord to handle CoreData.
It works great for a while. Then, around the time when the sqlite database reaches around 3.6-4.0 MB it suddenly stops saving the information. That is in the same run with no apparent reason. The console printing just keeps going on as if there is no issues.
I'm new to MagicalRecord and CoreData in general. Not sure what the issue is. Thanks
This is the code that is responsible for the collection:
+ (void)downloadObjects {
NSString *urlString = [NSString stringWithFormat:#"http://url.to.backend/server?with=parameters"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Parse the XML into a dictionary
NSError *parseError = nil;
NSDictionary *xmlDictionary = [XMLReader dictionaryForXMLParser:(NSXMLParser *)responseObject error:&parseError];
__block NSArray *objectsArray = xmlDictionary[#"list"][#"object"];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSLog(#"PROCESSING: %d objects", [objectsArray count]);
if (objectsArray.count) {
for (NSDictionary *object in objectsArray) {
long long objectID = [object[#"objectid"] longLongValue];
__block NSString *latitude = object[#"latitude"];
__block NSString *longitude = object[#"longitude"];
__block NSString *speed = object[#"speed"];
__block NSString *distance = object[#"distance"];
__block NSDate *time = object[#"time"];
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
if (time) { //Check if valid object
Object *existingObject = [ObjectManager objectWithID:objectID];
if (!existingObject) { //Check if existing object
if (DEBUG_DATA_COLLECTION) NSLog(#"%lld: NEW OBJECT", objectID);
Object *anObject = [Object MR_createEntity];
anObject.time = time;
anObject.objectID = #(objectID);
if (distance) anObject.distance = #([distance floatValue]);
if (latitude) anObject.latitude = #([latitude floatValue]);
if (longitude) anObject.longitude = #([longitude floatValue]);
if (speed) anObject.speed = #([speed floatValue]);
}
else {
if (DEBUG_DATA_COLLECTION) NSLog(#"%lld: EXISTING OBJECT", objectID);
}
}
}];
}
}
[ObjectManager aggregatePerDay];
[ObjectManager aggregatePerMonth];
} completion:^(BOOL success, NSError *error) {
NSLog(#"COMPLETED: %d objects", [objectsArray count]);
NSLog(#"---------------------");
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
After 3 days of debugging and retracing my steps, I finally figured out what the issue is.
It had nothing to do with CoreData or MagicalRecord.
One of the objects I was getting from the server had speed = (null). This basically, stopped all entries in the database from that object and beyond instead of just failing and working for the rest. It might be because I was using saveWithBlockAndWait: instead of just saveWithBlock: but I'm not experienced enough to say for sure.
Related
I'm attempting to obtain elements extracted from a dictionary and convert them to doubles. The data is being pulled from JSON and seems to be extracted into a type of array (not sure which type). Is there a way to obtain the numbers listed below individually out of the array? Please let me know if you need more information.
NSDictionary *parameters = #{#"username":savedUser,#"password":savedPass};
NSURL *URL = [NSURL URLWithString:#"testwebsite"];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:URL.absoluteString parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject)
{
NSError *error = nil;
JSON = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:&error];
if (error) {
NSLog(#"Error serializing %#", error);
}
NSLog(#"%#",JSON);
NSString *price = [NSString stringWithFormat:#"%#",[JSON valueForKey:#"UnitPrice"]];
price= [price stringByReplacingOccurrencesOfString:#"\"" withString:#""];
NSLog(#"Price: %#",price);
[transactionTotals addObject:price];
[self createGraph:100];
}
failure:^(NSURLSessionTask *operation, NSError *error)
{
NSLog(#"Error1: %#", [error debugDescription]);
NSLog(#"Error2: %#", [error localizedDescription]);
}];
}
#catch (NSException *exception)
{
NSLog(#"%#",exception);
}
Log (UnitPrice values I need individually extracted):
Dictionary output:
2016-07-03 22:52:21.330 T2PApp[2272:658440] (
{
OrderDetailID = 3;
ProductName = Oranges;
UnitPrice = "399.99";
date = "2016-06-09T21:45:06";
},
{
OrderDetailID = 7;
ProductName = Oranges;
UnitPrice = 1000;
date = "2016-06-13T22:15:47.107";
}
)
Extracted UnitPrice output (still not completely extracted):
2016-07-03 22:52:21.330 T2PApp[2272:658440] Price: (
399.99,
1000
)
I think what you need is digging out the data from the objects in array. It not about the JSON.
There is many way to do it, but not one simply way to dig it out.
For example, create a new array, and traverse the target array, put the property you need into the new array.
It is basically like that.
In your code, maybe the code below will work.
NSLog(#"%#",JSON);
NSMutableArray *priceArr = [NSMutableArray array];
NSArray *arr = nil;
if ([JSON isKindOfClass:[NSArray class]]) {
arr = (NSArray *)JSON;
for (NSDictionary *dic in arr) {
NSString *price = [NSString stringWithFormat:#"%#",[dic valueForKey:#"UnitPrice"]];
price= [price stringByReplacingOccurrencesOfString:#"\"" withString:#""];
[priceArr addObject:price];
}
}
priceArr is what you need.
I am using AFNetworking to get a JSON response. I am getting is as a PhotoPXArray (model I created using mantle). The log output is exactly the data I want. My problem is using the data. How do I go about saving the response data as a variable that can be used elsewhere in my program.
Also, I am using Sculptor to help with serializing.
-(NSArray*) getPhotoForWord:(NSString*)word {
NSArray *results = nil;
NSString *requestString = BASE_URL;
requestString = [requestString stringByAppendingString:#"photos/search?term="];
requestString = [requestString stringByAppendingString:word];
requestString = [requestString stringByAppendingString:CONSUMER_KEY];
NSString *encoded = [requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [SCLMantleResponseSerializer serializerForModelClass:PhotoPXArray.class];
[manager GET:encoded
parameters:nil
//success:^(AFHTTPRequestOperation *operation, id responseObject) {
success:^(AFHTTPRequestOperation *operation, PhotoPXArray *responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return results;
}
#end
Read the Apple documentation regarding blocks and variables. Or you can view this question on SO that will probably also answer your question.
From the Apple docs:
__block variables live in storage that is shared between the lexical scope of the variable and all blocks and block copies declared or
created within the variable’s lexical scope. Thus, the storage will
survive the destruction of the stack frame if any copies of the blocks
declared within the frame survive beyond the end of the frame (for
example, by being enqueued somewhere for later execution). Multiple
blocks in a given lexical scope can simultaneously use a shared
variable.
Use a completion block to get your data out:
- (void)getPhotoForWord:(NSString *)word completionHandler:(void ^(PhotoPXArray *photoArray))completionHandler
{
NSString *requestString = BASE_URL;
requestString = [requestString stringByAppendingString:#"photos/search?term="];
requestString = [requestString stringByAppendingString:word];
requestString = [requestString stringByAppendingString:CONSUMER_KEY];
NSString *encoded = [requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [SCLMantleResponseSerializer serializerForModelClass:PhotoPXArray.class];
[manager GET:encoded
parameters:nil
success:^(AFHTTPRequestOperation *operation, PhotoPXArray *responseObject) {
NSLog(#"JSON: %#", responseObject);
if (completionHandler) {
completionHandler(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Then call it like this:
[object getPhotoForWord:#"word" completionHandler:^(PhotoPXArray *photoArray) {
// Do something with photo array.
}];
Note that this call is asynchronous and will complete at some unknown time in the future. Also, you should likely take an NSError argument in the completion block so you can see if you get an error from the request, but I'll leave that to you.
I am using AFNetworking to get a JSON response. I am getting is as a PhotoPXArray (model I created using mantle). The log output is exactly the data I want. My problem is using the data. When I set a break point and look at the responseObject, it is nil. I don't know why the log is pumping out data but the value is nil in the debugger.
What I am ultimately trying to do is save the response to use it later.
Also, I am using Sculptor to help with serializing.
-(NSArray*) getPhotoForWord:(NSString*)word {
NSArray *results = nil;
__block NSMutableDictionary *test = nil;
NSString *requestString = BASE_URL;
requestString = [requestString stringByAppendingString:#"photos/search?term="];
requestString = [requestString stringByAppendingString:word];
requestString = [requestString stringByAppendingString:CONSUMER_KEY];
NSString *encoded = [requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [SCLMantleResponseSerializer serializerForModelClass:PhotoPXArray.class];
[manager GET:encoded
parameters:nil
//success:^(AFHTTPRequestOperation *operation, id responseObject) {
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
test = responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return results;
}
You never set results variable, so of course that will return nil. Even if you used test (which you do set), that happens asynchronously, so when you immediately return, it will be nil, too, only getting the responseObject value later.
You might consider employing an asynchronous pattern, instead, supplying a completion handler parameter:
- (void)getPhotoForWord:(NSString*)word completionHandler:(void (^)(id responseObject, NSError *error))completionHandler{
NSString *requestString = BASE_URL;
requestString = [requestString stringByAppendingString:#"photos/search?term="];
requestString = [requestString stringByAppendingString:word];
requestString = [requestString stringByAppendingString:CONSUMER_KEY];
NSString *encoded = [requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [SCLMantleResponseSerializer serializerForModelClass:PhotoPXArray.class];
[manager GET:encoded parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(nil, error);
}
}];
}
You'd then call that like so:
[obj getPhotoForWord:word completionHandler:^(id responseObject, NSError *error) {
// use responseObject here
}];
// do not use it here because the above happens asynchronously (i.e. later)
It looks like you aren't assigning results to anything. The only 2 times it appears in your code is when you declared it:
NSArray *results = nil;
and when you return it:
return results;
What it appears you are missing is parsing your test dictionary and populating an array, then returning that?
But as gabbler said in the comments, the call is asynchronous so unless you set up a semaphore, notifications, or something along those lines to make it synchronous, the return has a chance to be nil anyways.
i have a request which returns information from a php web service. I'm having trouble adding this to a array which can be used in my UICollectionView. It seems like whatever i do i cant return the data. I think it is because i'm returning the array before i've added any objects. I've tried placing the NSLog several places, but without luck. What am i doing wrong?
When i place this NSLog(#"%d", imagesArray.count); beyond the request it returns 0.
ViewDidAppear:
-(void)viewDidAppear:(BOOL)animated {
imagesArray = [[NSMutableArray alloc] init];
[self getImages];
}
getImages method:
-(void)getImages {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:#"http://URL.COM" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseObject options:kNilOptions error:nil];
NSString *theTitle = [[json objectForKey:#"response"] valueForKey:#"title"];
NSString *theUrl = [[json objectForKey:#"response"] valueForKey:#"imageUrl"];
[imagesArray addObject:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
theTitle, #"title",
theUrl, #"url",
nil]];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
NSLog(#"%d", imagesArray.count);
}
AFNetworking is Asynchronous, you have to place the log inside the success block. Otherwise the array will always be empty.
One good solution would be to pass a block to your getImages function like that
-(void) getImages:(void (^)(BOOL result))callback {
// your code here then you call callback(YES or NO) inside your success or failure block.
}
[self getImages:^(BOOL result){
if(result)
//we got the images, we can now display them etc.
}];
My client webservice send me a result like this:
{"login":{"EMAIL":"none","ID":"none","NOME":"none"}}
So, in AFN doesn't work.
But, if have one more result works:
{"login":[{"EMAIL":"none","ID":"none","NOME":"none"},{"EMAIL":"none","ID":"none","NOME":"none"}]}
My code:
NSDictionary *paramLogin = [NSDictionary dictionaryWithObjectsAndKeys:_txtEmail.text, #"email",_txtSenha.text, #"senha", nil];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://webservice.info" parameters:paramLogin success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#" , responseObject );
for (NSDictionary *retLogin in [responseObject valueForKeyPath:#"login"]) {
nome = [retLogin objectForKey:#"nome"];
email = [retLogin objectForKey:#"email"];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
why it is like this? or what I've to do ?
Sometimes [responseObject valueForKeyPath:#"login"] returns and array, sometimes it returns a dictionary. You need to test for this.
id loginValue = [responseObject valueForKeyPath:#"login"];
if ([loginValue isKindOfClass:[NSDictionary class]]) {
nome = [loginValue objectForKey:#"nome"];
email = [loginValue objectForKey:#"email"];
} else if ([loginValue isKindOfClass:[NSArray class]]) {
for (NSDictionary *retLogin in [responseObject valueForKeyPath:#"login"]) {
nome = [retLogin objectForKey:#"nome"];
email = [retLogin objectForKey:#"email"];
}
} else {
// ERROR: Unexpected value
}
When you have 1 value, then loginValue is an NSDictionary. It contains {"EMAIL":"none","ID":"none","NOME":"none"}.
When you have more than 1 value, then loginValue is an NSArray. The array contains [<NSDictionary>, <NSDictionary>]. Each of of these dictionaries contains {"EMAIL":"none","ID":"none","NOME":"none"}.
Problem is with your json data structure. It's not consistent.
{"login":{"EMAIL":"none","ID":"none","NOME":"none"}}
Here [responseObject valueForKeyPath:#"login"] is a single NSDictionary object.
But here,
{"login":[{"EMAIL":"none","ID":"none","NOME":"none"},{"EMAIL":"none","ID":"none","NOME":"none"}]}
Here [responseObject valueForKeyPath:#"login"] is an NSArray. So your fast enumeration works.
Best solution is to ask your webservice developer to send an array all the time, even 'login' has a single object. so it should look like this,
{"login": [{"EMAIL":"none","ID":"none","NOME":"none"}]} //notice square brackets
Otherwise you have to modify your code to check for an NSDictionary instead of array when there's only one object.
I suspect the issue is that you aren't retaining the AFHTTPRequestOperationManager object.
Assuming this code is in something like viewDidAppear:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:...];
Then manager will be destroyed before it has a chance to complete.
Instead add a property and store the manager object in that:
#interface MyViewController ()
#property (nonatomic) AFHTTPRequestOperationManager *manager;
#end
and use:
self.manager = [AFHTTPRequestOperationManager manager];
[self.manager GET:...];
if you are getting response like that than use below code
NSMutableArray *category = [[NSMutableArray alloc]init];
category = [responseObject objectForKey:#"login"];
for(int i = 0; i < [category count]; i++)
{
NSDictionary *dic = [category objectAtIndex:i];
nome = [dic objectForKey:#"nome"];
email = [dic objectForKey:#"email"];
}