Objective-C : reading from JSON file too slow for remote notifications - ios

I'm trying to setup the new ios 10 push notifications that are mutable, and in the didReceiveRequest method I am trying to read some argruments, one that is stored in NSUserDefaults and one that is in a local JSON file like this:
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger index = [defaults integerForKey:#"index"];
NSString *path = [[NSBundle mainBundle] pathForResource:#"Words" ofType:#"json"];
NSData *data = [NSData dataWithContentsOfFile:path];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSString *word = dict[#"Irish"][index];
self.bestAttemptContent.body = [NSString stringWithFormat:#"%#", word];
self.contentHandler(self.bestAttemptContent);
}
The problem is the method runs too slowly and doesn't finish. I know this is the case because apple warns about it and because if I replace that whole method above with a simple line that changes the title to a random string then it runs fine. Does anyone know how to make this faster or a way around this ?
What I'm trying to accomplish is each day the 'index' variable will increase when a daily push notification is recieved and it should read that index from the json file. So I basically want the user to receive the next string in the JSON file each day
Thanks

Why dont you do the actual processing in a new thread and return to complete the method call:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
self.contentHandler = contentHandler;
...
});

Related

Save char array to NSPasteBoard

I need to save unsigned char array to NSPasteBoard and then read it. How I can do it? I tried to save it to NSString, but this working only with ASCII codes.
Try this:
char myChars[] = "This is a test";
NSData *charsData = [NSData dataWithBytes:myChars length:strlen(myChars)];
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setData:charsData forType:NSPasteboardTypeString];
NSData *data = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
char myChars2[data.length];
[data getBytes:myChars2 length:data.length];
NSLog(#"%s", myChars2);
The question is a bit unclear. Do your application use pasteboard to communicate with other apps or just to store data between launches?
1. Your application needs to store binary data for itself between launches.
I suggest you to use NSUserDefaults for that purpose. As it said in docs, it's a database so you may achieve data even in next application launch.
static const NSString *kCharsDefaultsKey = #"kCharsDefaultsKey";
- (void)saveChars:(unsigned char *)chars length:(size_t)length
{
NSData *data = [NSData dataWithBytes:chars length:length];
NSUserDefaults *defaults = [NSUserDefaults standartDefaults];
[defaults setValue:data forKey:kCharsDefaultsKey];
[defaults synchronize];
}
- (void)getChars:(unsigned char *)chars length:(size_t)length
{
NSUserDefaults *defaults = [NSUserDefaults standartDefaults];
NSData *data = [default valueForKey:kCharsDefaultsKey];
if(data)
{
[data getBytes:chars];
}
}
2. Your application needs to push data between some apps.
The pasteboard-mechanism can be a way to establish this kind of communication. But I strongly recommend you to choose another way. You can't be sure there isn't another data inside the pasteboard.
- (void)someMethod
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *contents = [pasteboard readObjectsForClasses:#[[NSData class]] options: nil];
// how should you distinguish which object contains your chars and which doesn't?
}
I would rather recommend you to use NSConnection.
Also please take a look at this great article.

NSDictionary constant looping

SO I have a program which calls the FlickR API, gets the URL's puts them into a dictionary and then assigns them into a table view, using an image view.
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
int countAttempts = 0;
[[self.flickr photosForUser:#"James Kinvig"]count];
for (int i = 0; i < [[self.flickr photosForUser:#"James Kinvig"]count]; i++) {
for(NSDictionary *dictionary in photos) {
countAttempts++;
NSString *farmId = [dictionary objectForKey:#"farm"];
NSString *serverId = [dictionary objectForKey:#"server"];
NSString *photoId = [dictionary objectForKey:#"id"];
NSString *secret = [dictionary objectForKey:#"secret"];
self.url= [NSURL URLWithString:[NSString stringWithFormat:#"http://farm%#.staticflickr.com/%#/%#_%#.jpg", farmId, serverId, photoId, secret]];
//NSLog(#"self.url = %#", self.url);
NSLog(#"count = %d", countAttempts);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:self.url];
dispatch_sync(dispatch_get_main_queue(), ^{
UIImage *img = [UIImage imageWithData:imgData];
cell.imageView.image = img;
[cell setNeedsLayout];
});
});
}
}
return cell;
}
This is the method it calls, photosForUser:
- (NSMutableArray *) photosForUser: (NSString *) friendUserName
{
NSString *request = [NSString stringWithFormat: #"https://api.flickr.com/services/rest/?method=flickr.people.findByUsername&username=%#", friendUserName];
NSDictionary *result = [self fetch: request];
NSString *nsid = [result valueForKeyPath: #"user.nsid"];
request = [NSString stringWithFormat: #"https://api.flickr.com/services/rest/?method=flickr.photos.search&per_page=%ld&has_geo=1&user_id=%#&extras=original_format,tags,description,geo,date_upload,owner_name,place_url", (long) self.maximumResults, nsid];
result = [self fetch: request];
return [result valueForKeyPath: #"photos.photo"];
}
Which does a fetch to the flickr API.
What is happening though is that is stuck in an eternal loop. Even with the for statement being less than the count, it still eternal loops. I have NSLog'd the count of the FlickR photos and it = 11.
This may have something to do with it, but whenever I press the button to take me to the table view controller, I get a HUGE lag, close to a minute, and nothing is being calculated (photo-wise) as I've done a count++
Thanks
let me understand this.. By the last line of your first block of code, I conclude that that is the uitableview dataSource method, cellForRowAtIndexPath.. what doesn't really makes sense.. you you have a fetch there, you have A loop inside a loop, that is setting many images (by download them) in one single imageView, and this is happening for all your visible cells at the same time. This will never work!
The solution is:
1 - remove this method from the cellForRow, this is not the place to request the images
2 - create another method that will fetch the content
3 - create a method that will do your loops and store the images on the array so you don't need to do that many times, only one..
4 - reload the tableview after you finish the step 3
5 - use the array of images that is already done to set your images by indexPath.row in your cell..
6 - I recommend you to use a Library for imageCache (i.e https://github.com/rs/SDWebImage)
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
for (int i = 0; i < [[self.flickr photosForUser:#"James Kinvig"] count]; i++)
{
for (NSDictionary *dictionary in photos)
{
You have two nested loops iterating over the same collection. This turns what should be an O(n) operation into O(n^2) and explains why your process is taking a very long time.
Since the loop bodies never use i, I would fix it by getting rid of the outer loop:
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
for (NSDictionary *dictionary in photos)
{

Web Service App Crash That Doesn't Happen Too Frequently

About a couple weeks ago, I submitted my JobSearch App to the App Store. This app uses the USA Job Search Web Service to get me the jobs in JSON data so that I can put those jobs into an array.
Anyway, my app got rejected due to a crash on the iPhone 5. I read the logs and they said
Exception Type: EXC_CRASH (SIGABRT)
So I created an exception breakpoint in my application. It turns out if I enter random garbage for my input, the web service might return me 0 jobs, and sometimes it will return SIGABRT. I found the crash at this line of my code, where I'm creating an array to hold the service:
NSMutableArray *jobsCallArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
I really can't find anything wrong with this code, and this is the only thing I need to fix to get my app accepted. If you would like to see how I call the service to it's entirety and all that, here is the code:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"push"])
{
NSString *urlString = [NSString stringWithFormat:#"http://api.usa.gov/jobs/search.json?query=%#+jobs+in+%#", [jobDescription text], [location text]];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error;
NSMutableArray *jobsCallArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSMutableArray *positionArray = [[NSMutableArray alloc] init];
for (NSDictionary *theJob in jobsCallArray)
{
NSString *jobDesc = theJob[#"position_title"];
[positionArray addObject:jobDesc];
}
FoundJobsTableViewController *detailVC = (FoundJobsTableViewController*)segue.destinationViewController;
[detailVC setArray:positionArray];
[detailVC setTheUrlString:urlString];
}
}
All help is appreciated, thanks in advance
You need to check for an error and whether you're actually receiving data:
if (!error && data) {
NSMutableArray *jobsCallArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
}

Why method executes slow?

There is a JSON file on server, which contain four "key/value" pairs about version of data in app (it is a cook book, so the version is a version of recipes)
When application starts, it download JSON and check version.
Here is my method, but I think it is very slow.
- (void)isUpdatesAvail
{
updatesAvail = NO;
NSInteger iVer = [[NSUserDefaults standardUserDefaults] integerForKey:#"ingredientsVer"];
NSInteger rVer = [[NSUserDefaults standardUserDefaults] integerForKey:#"recipesVer"];
NSInteger iCnt = [[NSUserDefaults standardUserDefaults] integerForKey:#"ingredientsCount"];
NSInteger rCnt = [[NSUserDefaults standardUserDefaults] integerForKey:#"recipesCount"];
NSString *countPath = [downloadPath stringByAppendingString:#"/versioninfo"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:countPath]];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSError *error = nil;
if (!responseData) {
return;
}
NSDictionary *recipesCount = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
if (!recipesCount) {
return;
}
rCount = [recipesCount objectForKey:#"recipeCount"];
iCount = [recipesCount objectForKey:#"ingredientCount"];
rVersion = [recipesCount objectForKey:#"recipeVersion"];
iVersion = [recipesCount objectForKey:#"ingredientVersion"];
if ([iVersion integerValue] > iVer || [rVersion integerValue] > rVer || [iCount integerValue] > iCnt || [rCount integerValue] > rCnt) {
updatesAvail = YES;
}
}
Can somebody give me advice (or may be example) how to do that?
It's 'slow' because it performs a synchronous URL request. It's probably just waiting for the network transfer to complete for most of the time it executes if it is noticeably slow.
There's not much you can do to make it faster, apart from either caching the data or reducing the size of the data.
In any event, you should just perform an asynchronous request then do your work/update after it's completed.

iOS exec function in background

I am new to multithreading and was wondering how I could run this function in the background? The function simply returns a NSURL that is used for XML parsing and is called from another function. Or is it even worth it to run in the background since the function that calls it does not continue until this function returns its NSURL. Basically, I am just trying to figure out how to speed this up because it is taking a little time to finish!
+ (NSURL *)parserURL {
NSURL *theURL = [NSURL URLWithString:#"http://www.wccca.com/PITS/"];
NSData *data = [[NSData alloc] initWithContentsOfURL:theURL];
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
NSArray *elements = [xpathParser searchWithXPathQuery:#"//input[#id='hidXMLID']//#value"];
if (elements.count >= 1) {
TFHppleElement *element = [elements objectAtIndex:0];
TFHppleElement *child = [element.children objectAtIndex:0];
NSString *idValue = [child content];
NSString *stg = [NSString stringWithFormat:#"http://www.wccca.com/PITS/xml/fire_data_%#.xml", idValue];
NSURL *url = [NSURL URLWithString:stg];
return url;
}
return nil;
}
The main issue with your code is that you are using a blocking operation to get the data from the website. You definitely want to execute this in the background thread. However, I would recommend you to have a look at networking frameworks that help you do these kinds of operations very easily, i.e., AFNetworking,
In any case, the strategy that I would follow to multithread that operation, or a similar one is the following:
It breaks down to dispatching it with GDC, and then executing a receiving completion block back in the main thread with the results.
Here is the code:
Description
First start by declaring your function to receive a block. The block will be executed in the end, once you've finished retrieving and parsing the data. The next thing the code does it asking GDC to execute a block of code in a background queue. When it is done, we ask the code to execute the completion block that was provided as parameter of the function in the main thread, supplying the parsed string to it.
+(void) parserURL:(NSURL *) theURL completion:(void (^) (NSURL *finalURL))completionBlock{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [[NSData alloc] initWithContentsOfURL:theURL];
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
NSArray *elements = [xpathParser searchWithXPathQuery:#"//input[#id='hidXMLID']//#value"];
NSURL *url;
if (elements.count >= 1) {
TFHppleElement *element = [elements objectAtIndex:0];
TFHppleElement *child = [element.children objectAtIndex:0];
NSString *idValue = [child content];
NSString *stg = [NSString stringWithFormat:#"http://www.wccca.com/PITS/xml/fire_data_%#.xml", idValue];
url = [NSURL URLWithString:stg];
}else{
url = nil;
}
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(url);
});
});
}
You call the method the following way:
[URLParser parserURL:[NSURL URLWithString:#"http://www.wccca.com/PITS/"] completion:^(NSURL *finalURL) {
NSLog(#"Parsed string %#", [finalURL absoluteString]);
}];

Resources