I'm using WKWebView in my app to get data from The HTML with javascript, I'm using this code:
NSString *jsCall = [NSString stringWithFormat:#"xxxxxx('%#');",html];
__block NSString *searchJson = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_sync(dispatch_get_main_queue(), ^{
[self.searchWebView evaluateJavaScript:jsCall completionHandler:^(id result, NSError * _Nullable error) {
if (error || result == nil || ![result isKindOfClass:[NSString class]]) {
searchJson = nil;
} else {
searchJson = result;
}
dispatch_semaphore_signal(semaphore);
}];
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if ([NSString stringIsNilOrEmpty:searchJson]) {
return nil;
}
return [self getResultsWithJSonString:searchJson];
I need to wait for the response so i'm using the semaphore_wait method, It's working fine but sometimes the searchJson is released from memory and in the [NSString stringIsNilOrEmpty:searchJson] I'm getting this error:
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000443439e70
Any idea of how to fix this released problem? I'm using Non-ARC in my project.
If it is non-ARC, the you must retain result explicitly (because callback might be in different thread), like
} else {
searchJson = [result retain];
}
and then, to avoid leak, use
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[searchJson autorelease]; // << call it here, once !!
if ([NSString stringIsNilOrEmpty:searchJson]) {
return nil;
}
return [self getResultsWithJSonString:searchJson];
Related
I'm trying to run some intensive processes serially, with multiple serial queues. The code is working, however my UI update doesn't occur, even though the method is called.
Here is the code that runs several processes serially.
- (void)importProcess {
dispatch_queue_t serialQueue = dispatch_queue_create("com.cyt.importprocessqueue", DISPATCH_QUEUE_SERIAL);
NSLog(#"checking image sizes");
__block NSMutableArray *assets;
dispatch_sync(serialQueue, ^() {
assets = [self checkImageSizes];
});
dispatch_sync(serialQueue, ^() {
[self appendLogToTextView:[NSString stringWithFormat:#"%i screenshot(s) ignored due to invalid size.",(int)(self.assets.count-assets.count)]];
if(assets.count==0) {
[self showNoRunesFoundAlert];
}
else {
[self appendLogToTextView:#"Preparing to process screenshots..."];
self.assets = [NSArray arrayWithArray:assets];
}
});
NSLog(#"processing uploads");
dispatch_sync(serialQueue, ^() {
[self processUploads];
});
NSLog(#"recognizing images");
dispatch_sync(serialQueue, ^() {
[self recognizeImages];
});
NSLog(#"recognizing images");
dispatch_sync(serialQueue, ^() {
[self processRuneText];
});
//dispatch_sync(dispatch_get_main_queue(), ^ {
//});
}
Within checkImageSizes, I have another serial queue:
- (NSMutableArray *)checkImageSizes {
dispatch_queue_t serialQueue = dispatch_queue_create("com.cyt.checkimagesizequeue", DISPATCH_QUEUE_SERIAL);
NSMutableArray *assets = [NSMutableArray new];
for(int i=0;i<self.assets.count;i++) {
dispatch_sync(serialQueue, ^{
PHAsset *asset = (PHAsset *)self.assets[i];
if(asset.pixelWidth==self.screenSize.width && asset.pixelHeight==self.screenSize.height) {
[assets addObject:asset];
NSString *logText = [NSString stringWithFormat:#"Screenshot %i/%i size is OK.",i+1,(int)self.assets.count];
[self performSelectorOnMainThread:#selector(appendLogToTextView:) withObject:logText waitUntilDone:YES];
}
else {
[self appendLogToTextView:[NSString stringWithFormat:#"ERROR: Screenshot %i/%i has invalid size. Skipping...",i+1,(int)self.assets.count]];
}
});
}
return assets;
}
The appendLogToTextView method is supposed to update the UI. Here is that code:
- (void)appendLogToTextView:(NSString *)newText {
dispatch_block_t block = ^ {
self.logText = [NSString stringWithFormat:#"%#\n%#", self.logText, newText];
NSString *textViewText = [self.logText substringFromIndex:1];
[UIView setAnimationsEnabled:NO];
if(IOS9) {
[self.textView scrollRangeToVisible:NSMakeRange(0,[self.textView.text length])];
self.textView.scrollEnabled = NO;
self.textView.text = textViewText;
self.textView.scrollEnabled = YES;
}
else {
self.textView.text = textViewText;
NSRange range = NSMakeRange(self.textView.text.length - 1, 1);
[self.textView scrollRangeToVisible:range];
}
[UIView setAnimationsEnabled:YES];
};
if ([NSThread isMainThread]) {
block();
}
else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
As you can see, I have tried calling appendLogToTextView both directly and using performSelectorOnMainThread. However, I'm not getting any updates to my text view, even though I confirm that the method is being called properly.
Interestingly, the UI updating works properly when I only use a single serial queue and use iteration counts to call the next method, as shown in the code below (_serialQueue is defined in viewDidLoad). However, I do not believe that implementation is good practice, as I'm wrapping serial queues within serial queues.
- (void)checkImageSizes {
NSMutableArray *assets = [NSMutableArray new];
for(int i=0;i<self.assets.count;i++) {
dispatch_async(_serialQueue, ^{
PHAsset *asset = (PHAsset *)self.assets[i];
if(asset.pixelWidth==self.screenSize.width && asset.pixelHeight==self.screenSize.height) {
[assets addObject:asset];
[self appendLogToTextView:[NSString stringWithFormat:#"Screenshot %i/%i size is OK.",i+1,(int)self.assets.count]];
}
else {
[self appendLogToTextView:[NSString stringWithFormat:#"ERROR: Screenshot %i/%i has invalid size. Skipping...",i+1,(int)self.assets.count]];
}
//request images
if(i==self.assets.count-1) {
[self appendLogToTextView:[NSString stringWithFormat:#"%i screenshot(s) ignored due to invalid size.",(int)(self.assets.count-assets.count)]];
if(assets.count==0) {
[self showNoRunesFoundAlert];
}
else {
[self appendLogToTextView:#"Preparing to process screenshots..."];
self.assets = [NSArray arrayWithArray:assets];
[self processUploads];
}
}
});
}
}
What am I not understanding about serial queues that is causing the UI updates in this version of the code to work, but my attempt at a "cleaner" implementation to fail?
In the end, I just ended up using dispatch groups and completion blocks in order to solve this problem.
I'm using Xcode 4.6.3 and iOS 5.5/6.1.6 .
I am using a background thread to load large quantities of jpg from a server to iOS devices.
dispatch_async(kBgQueue, ^
{
// get the array of filenames to download
NSURL* url = [NSURL URLWithString:webPath];
NSArray* theArray = [NSArray arrayWithContentsOfURL:url];
if( theArray )
{
dispatch_async(dispatch_get_main_queue(), ^{
// disable screen buttons
[self setButtons:false];
});
[self loadImagesFromList:theArray sourceBundle:bundlePath destBundle:localBundlePath manager:manager];
if (!stopFlag) {
// if no memory error has occurred
NSLog(#"calling refresh after load_images");
dispatch_async(dispatch_get_main_queue(), ^{
[self refresh];
});
}
theArray = nil;
}
else
{
NSLog(#"Error loading bundle");
}
});
The background method:
-(void)loadImagesFromList:(NSArray *)theArray
sourceBundle:(NSString *)bundlePath
destBundle:(NSString *)localBundlePath
manager:(NSFileManager *)manager {
// initialize the progress and activity indicator
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndictor startAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self.progressIndictor setProgress:0 animated:NO];
});
NSURL *url;
NSString *srcFile;
NSString *destFile;
NSError *error = nil;
int counter = 0;
float prog = 0;
float increment = 1.0 / [theArray count];
float stepSize = [theArray count] / 10;
for (NSString *file in theArray)
{
if (stopFlag) {
NSLog(#"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
return;
}
srcFile = [bundlePath stringByAppendingPathComponent:file];
destFile = [localBundlePath stringByAppendingPathComponent:file];
counter += 1;
prog += increment;
if (counter == stepSize) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressIndictor.progress = prog;
});
counter = 0;
}
// only download if file isn't already here
BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
if (!fileExists) {
// jpg or folder check
if ([[destFile pathExtension] isEqualToString:#"jpg"]) {
url = [NSURL URLWithString:srcFile];
data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
[data writeToFile:destFile options:NSDataWritingAtomic error:&error];
data = nil;
} else {
[manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
}
}
}
}
If the files exist, the loop zips through the array and exits back to the main thread ok.
If any files are missing, the download/write part seems to chew up the RAM and cause low memory warning to trigger. It takes several thousand files to do it.
I've tried declaring the variables outside the loop, and even doing the whole thing in the main thread to test if that was causing the leak.
I tried using the alternate dataWithContentsOfURL:options:error call.
I tried Instruments, but it is really slow and crashes often. Before crashing, it does show allocation going up, up, up slowly.
After several days on this, I'm stumped.
The first thing I'd suggest is using an #autoreleasepool to control the peak amount of memory consumed. Right now, you're downloading the contents into the NSData as an autorelease object, and when done, you're nil-ing that variable, which simple flags it to be deallocated once the autorelease pool is drained (which will not happen until loadImagesFromList is done). By (a) moving the variable declarations inside the for loop; and (b) wrapping this in an #autoreleasepool, your memory will be deallocated as the individual downloads finish.
-(void)loadImagesFromList:(NSArray *)theArray
sourceBundle:(NSString *)bundlePath
destBundle:(NSString *)localBundlePath
manager:(NSFileManager *)manager {
// initialize the progress and activity indicator
dispatch_async(dispatch_get_main_queue(), ^{
// your UI update here
});
int counter = 0;
float prog = 0;
float increment = 1.0 / [theArray count];
float stepSize = [theArray count] / 10;
for (NSString *file in theArray)
{
#autoreleasepool {
if (stopFlag) {
NSLog(#"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
return;
}
NSString *srcFile = [bundlePath stringByAppendingPathComponent:file];
NSString *destFile = [localBundlePath stringByAppendingPathComponent:file];
counter += 1;
prog += increment;
if (counter == stepSize) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressIndictor.progress = prog;
});
counter = 0;
}
// only download if file isn't already here
BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
if (!fileExists) {
NSError *error = nil;
// jpg or folder check
if ([[destFile pathExtension] isEqualToString:#"jpg"]) {
NSURL *url = [NSURL URLWithString:srcFile];
NSData *data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
[data writeToFile:destFile options:NSDataWritingAtomic error:&error];
} else {
[manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
}
}
}
}
}
You might want to refactor this code to use NSOperationQueue. This addresses the peak memory issue, but also let's you enjoy a degree of concurrency. Because iOS only allows 4-5 concurrent requests anyway, you want to limit the maximum number of concurrent operations to a reasonable number, and this mitigates network timeout risks if trying to run too many concurrent requests. (This maxConcurrentOperationCount feature is the main reason I suggest using operation queues.)
Anyway, that might look like:
-(void)loadImagesFromList:(NSArray *)theArray
sourceBundle:(NSString *)bundlePath
destBundle:(NSString *)localBundlePath
manager:(NSFileManager *)manager {
// initialize the progress and activity indicator
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// your UI update here
}];
int __block counter = 0;
float __block prog = 0;
float increment = 1.0 / [theArray count];
float stepSize = [theArray count] / 10;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
for (NSString *file in theArray)
{
[queue addOperationWithBlock:^{
if (stopFlag) {
NSLog(#"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
return;
}
NSString *srcFile = [bundlePath stringByAppendingPathComponent:file];
NSString *destFile = [localBundlePath stringByAppendingPathComponent:file];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
counter += 1;
prog += increment;
if (counter == stepSize) {
self.progressIndictor.progress = prog;
counter = 0;
}
}];
// only download if file isn't already here
BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
if (!fileExists) {
NSError *error = nil;
// jpg or folder check
if ([[destFile pathExtension] isEqualToString:#"jpg"]) {
NSURL *url = [NSURL URLWithString:srcFile];
NSData *data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
[data writeToFile:destFile options:NSDataWritingAtomic error:&error];
} else {
[manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
}
}
}];
}
}
There are other refinements I might suggest (e.g. implementing cancelation logic rather than looking at stopFlag), but I was trying to minimize the code changes. I'm just taking advantage of the fact that one can easily replace dispatch_async:
dispatch_async(dispatchQueue, ^{ ... });
with NSOperationQueue method addOperationWithBlock:
[operationQueue addOperationWithBlock:^{ ... }];
But now we can use a concurrent NSOperationQueue with maxConcurrentOperationCount of 4 or 5, and you suddenly enjoy a nice, constrained degree of concurrency. You may find that this is observably faster than downloading files sequentially.
I've ran into a problem, I'm currently using GKLeaderboards and using it to fill a model. I'm having no problems fetching the data, my problem occurs when I go to fill the tableView with the data, and it's not done filling the array before its called to fill the TableView. From what i've read i need to use Grand Central Dispatch so that its not loading on the main thread.
Any help would be much appreciated.
+(EILeaderBoardModel *)scoresAndNameFromLeaderBoard
{
static EILeaderBoardModel *leaderBoard = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
leaderBoard = [[EILeaderBoardModel alloc] init];
leaderBoard.highScorePlayerArray = [[self class] GameCenterLeaderBoard];
});
return leaderBoard;
}
+ (NSMutableArray *)GameCenterLeaderBoard
{
NSMutableArray *_highScorePlayer = [NSMutableArray new];
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboardRequest.range = NSMakeRange(1, 20);
leaderboardRequest.identifier = GameHighscoreIdentifier;
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
// Handle the error.
}
if (scores != nil)
{
for (NSUInteger i = 0; i < scores.count; i++) {
GKScore *score = (GKScore *)scores[i];
[GKPlayer loadPlayersForIdentifiers:#[score.playerID] withCompletionHandler:^(NSArray *players, NSError *error) {
GKPlayer *player = (GKPlayer *)players[0];
[player loadPhotoForSize:GKPhotoSizeSmall withCompletionHandler:^(UIImage *photo, NSError *error) {
if (error != nil) {
}
UIImage *_avatar;
if (photo != nil) {
_avatar = photo;
} else {
_avatar = [UIImage imageNamed:#"unknownPersonImage.png"];
}
EIPlayer *currentPlayer = [EIPlayer nameLabel:player.displayName
scoreLabel:[NSString stringWithFormat:#"%llD",score.value]
avatar:_avatar];
[_highScorePlayer addObject:currentPlayer];
}];
}];
}
}
}];
return _highScorePlayer;
}
dispatch_queue_t queue = dispatch_queue_create("com.yourQueue.company", 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue, ^{
// Do all your data fetching work here
// also like adding values to arrays.
dispatch_async(main, ^{
// Do all your UI update logic here like updating data into tables.
[self.tableView reloadData];
});
});
hope this helps.
Below is the method with the code, where I am getting a memory leak using manual memory management. The memory leak is detected by using Xcode instruments and specifically points to the line where I am using NSJSONSerialization. I am running the target app (on a device with iOS 6.1).
The first time that i tap on the refreshButton there is no leak. Any subsequent tap generates the leak(and more leaks on top of that if i continue tapping the button). Below is the code - This is basic stuff for consuming JSON web services(the web service link is bogus but the real one that I am using works). You will notice that I am using Grand Central Dispatch so that I can update the UI without waiting for the parsing of the JSON to finish.
The line detected by instruments is surrounded by the asterisks. I would like to get some help to anyone who might have an idea of what is going on here. The full stack trace(as mentioned in the below comments i will put here:)
+(NSJSONSerialization JSONObjectWithData:option:error:] -> -[_NSJSONReader parseData:options:] -> -[_NSJSONReader parseUTF8JSONData:skipBytes:options]->newJSONValue->newJSONString->[NSPlaceholderString
initWithBytes:length:encoding:]
-(void)parseDictionary:(NSDictionary *)dictonary{
self.transactions = [dictonary objectForKey:#"transactions"];
if(!self.transactions){
NSLog(#"Expected 'transactions' array");
return;
}
for (int arrayIndex = 0; arrayIndex < [self.transactions count]; arrayIndex++) {
TransactionResult *result = [[[TransactionResult alloc] init] autorelease];
result.transactionID = [[self.transactions objectAtIndex:arrayIndex] objectForKey:#"ID"];
result.transactionDescription = [[self.transactions objectAtIndex:arrayIndex] objectForKey:#"description"];
result.transactionPrice = [[self.transactions objectAtIndex:arrayIndex] objectForKey:#"price"];
self.totalPrice += [result.transactionPrice doubleValue];
NSLog(#"total price: %f", self.totalPrice);
[self.transactionResults addObject:result];
result = nil;
}
}
- (IBAction)refreshButtonPressed:(UIBarButtonItem *)sender {
__block id resultObject;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSURL *url = [NSURL URLWithString:#"http://mywebservice.php"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error;
***resultObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];***
if(!error){
if([resultObject isKindOfClass:[NSDictionary class]]){
NSDictionary *dictonary = resultObject;
[self parseDictionary:dictonary];
NSLog(#"Done parsing!");
dispatch_async(dispatch_get_main_queue(), ^{
self.isLoading = NO;
[self.transactionsTableView reloadData];
});
}
else{
NSLog(#"JSON Error: Expected Dictionary");
resultObject = nil;
return;
}
}
else{
NSLog(#"JSON Error: %#", [error localizedDescription]);
dispatch_async(dispatch_get_main_queue(), ^{
resultObject = nil;
[self.transactionsTableView reloadData];
[self showError];
});
return;
}
});
}
I used a ARC as soon as it came out with 4.3, and put an app in the store with it - point being you could switch to ARC. That said, I tried to reproduce your problem by creating a class/file that has the no-arc flag applied to it, but cannot reproduce the problem. This makes me believe your problem is elsewhere. In the code below, I create a Test object in another file, retain it, and send it the test message. No matter what I set "i" to, it always deallocs the object:
#import "Tester.h"
#interface Obj : NSObject <NSObject>
#end
#implementation Obj
- (id)retain
{
NSLog(#"retain");
id i = [super retain];
return i;
}
- (oneway void)release
{
NSLog(#"release");
[super release];
}
- (void)foo
{
}
- (void)dealloc
{
NSLog(#"Obj dealloced");
[super dealloc];
}
#end
#implementation Tester
- (void)test
{
int i = 2;
__block Obj *obj;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
obj = [[Obj new] autorelease];
if(i == 0) {
Obj *o = obj;
dispatch_async(dispatch_get_main_queue(), ^
{
[o foo];
} );
} else if(i == 1) {
obj = nil;
} else if(i == 2) {
dispatch_async(dispatch_get_main_queue(), ^
{
obj = nil;
} );
}
} );
}
#end
What I am trying to do is a Facebook wrapper for the Facebook iOS SDK. Basically the idea is that my ViewController should do nothing but showing ex. my friends that will be acquired with a simple call like
self.friends = [FacebookWrapper myFriends];
[self.tableView reloadData];
My wrapper myFriends method should look like this
+ (NSArray *)myFriends
{
__block NSArray *friends = nil;
[FBSession openActiveSessionWithReadPermissions:nil allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
if(FB_ISSESSIONOPENWITHSTATE(status)) {
[FBRequestConnection startForMyFriendsWithCompletionHandler:^(FBRequestConnection *connection, id data, NSError *error) {
CFRunLoopStop(CFRunLoopGetCurrent());
if(error) {
return;
}
NSArray *friendsData = (NSArray *)[data data];
NSMutableArray *fbFriends = [NSMutableArray array];
for(id friendData in friendsData) {
Friend *friend = [Friend friendWithDictionary:friendData];
fbFriends addObject:friend];
}
friends = [NSArray arrayWithArray:fbFriends];
}];
CFRunLoopRun();
}
}];
return friends;
}
The issue is that the openActiveSessionWithReadPermissions and startForMyFriendsWithCompletionHandler are asynchronous blocks so the method returns before the blocks complete their task.
Any help would be much appreciated.
I created a similar wrapper in the past and my approach was passing a "completion block" when calling my wrapper method; this completion block is then triggered once all the asynchronous calls are done running, and it receives whatever data your method would return in a synchronous scenario (in your case, the array of friends).
To illustrate - you could have your "myFriends" method redefined as:
+ (void)myFriendsWithCompletionBlock:(void (^)(NSArray *friends))completionBlock;
Then in the implementation, right after the friends = [NSArray arrayWithArray:fbFriends]; line, you would add this:
if (completionBlock != nil) {
completionBlock(friends);
}
... and remove the return statement at the end.
Finally, on your view controller (or any object using the method, you would do something like this:
[FacebookWrapper myFriendsWithCompletionBlock:^(NSArray *friends){
// do what you need to do with the friends array
}];
Of course, this is still asynchronous - but there's no way around since that's how the Facebook SDK was build (and, to be fair, this is probably the best way to do it - waiting for requests to finish synchronous would be terrible!)
Edit: I noticed you're also returning from the wrapper method in case it fails; in that situation, instead of returning you would do something like this:
if (completionBlock != nil) {
completionBlock(nil);
}
That would cause the friends array to be nil when your completion block is called - you can then treat that error there however seems appropriate to you.
Hope this helped!
If you are dispatching an asynchronouos block, you can communicate with your UIViewController subclass by calling back to it:
[self someSelectorWithCallbackData:stuffWhichYouWantToGiveBack];
This will call self to get captured by the block, and so will work as expected. From the relevant method you can refresh the view / reload the tableview / dance a jig as required.
Depending on the context, you may need to __block scope self, eg
__block UIViewController *bsself = self;
But if you do the latter, be careful to avoid a retain loop (the build and analysis tools are fairly good at pointing this out).
Think you need to use a protol
#class Webservice;
#protocol WebserviceDelegate
#optional
-(void)webservice:(Webservice *)webservice didFetchPosts:(NSArray *)posts;
-(void)webservice:(Webservice *)webservice didFetchComments:(NSArray *)comments forPostID:(NSString *)postID launchComments:(BOOL)launch;
-(void)webservice:(Webservice *)webservice didLoginWithUser:(User *)user;
-(void)webservice:(Webservice *)webservice didVoteWithSuccess:(BOOL)success forObject:(id)object direction:(BOOL)up;
#end
#interface Webservice : NSObject {
__weak id <WebserviceDelegate> delegate;
}
//Delegate
#property (weak) id <WebserviceDelegate> delegate;
-(void)getHomepage {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURLResponse *response;
NSError *error;
// Create the URL Request
NSMutableURLRequest *request = [Webservice NewGetRequestForURL:[NSURL URLWithString:#"https://www.hnsearch.com/bigrss"]];
// Start the request
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//Handle response
//Callback to main thread
if (responseData) {
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSStringEncodingConversionAllowLossy];
if (responseString.length > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self parseIDsAndGrabPosts:responseString];
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
});
}
-(void)parseIDsAndGrabPosts:(NSString *)parseString {
// Parse String and grab IDs
NSMutableArray *items = [#[] mutableCopy];
NSArray *itemIDs = [parseString componentsSeparatedByString:#"<hnsearch_id>"];
for (int xx = 1; xx < itemIDs.count; xx++) {
NSString *idSubString = itemIDs[xx];
[items addObject:[idSubString substringWithRange:NSMakeRange(0, 13)]];
}
// Send IDs back to HNSearch for Posts
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURLResponse *response;
NSError *error;
// Create Request String
NSString *requestString = #"http://api.thriftdb.com/api.hnsearch.com/items/_bulk/get_multi?ids=";
for (NSString *item in items) {
requestString = [requestString stringByAppendingString:[NSString stringWithFormat:#"%#,", item]];
}
// Create the URL Request
NSMutableURLRequest *request = [Webservice NewGetRequestForURL:[NSURL URLWithString:requestString]];
// Start the request
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//Handle response
//Callback to main thread
if (responseData) {
NSArray *responseArray = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&error];
if (responseArray) {
NSMutableArray *postArray = [#[] mutableCopy];
for (NSDictionary *dict in responseArray) {
[postArray addObject:[Post postFromDictionary:dict]];
}
NSArray *orderedPostArray = [self orderPosts:postArray byItemIDs:items];
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:orderedPostArray];
// Update Karma for User
if ([HNSingleton sharedHNSingleton].User) {
[self reloadUserFromURLString:[NSString stringWithFormat:#"https://news.ycombinator.com/user?id=%#", [HNSingleton sharedHNSingleton].User.Username]];
}
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
});
}