Simple NSURLCache Implementation on iOS8 - ios

I'm having trouble getting a simple implementation of NSURLCache working on iOS8. It's my understanding that once a shared cache is created, it automatically caches data requests with the proper cache policy. No configuration required unless you want to customize behavior. Is this correct?
I've included a simplified version of my code below. The cache is created in AppDelegate, and the TableViewController that needs the data uses the APICaller object to make the call. The request is using NSURLRequestReturnCacheDataElseLoad, as this information doesn't need to be updated frequently.
If I'm way off the mark here. What's the next step? The data received is 95KB.
AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
return YES;
}
TableViewController:
- (void)viewDidLoad {
APICaller *apiCaller = [APICaller alloc] init];
[apiCaller makeAPICallWithCompletionHandler:^(NSArray *result, NSError *error){
if (error) {
// Handle error
} else {
self.property = [result mutableCopy];
[self.tableView reloadData];
}
}
}
APICaller:
- (void)makeAPICallWithCompletionHandler:(void(^)(NSArray *result, NSError *error))completionHandler
{
NSString *urlString = [NSString stringWithFormat#"https://api.apiwebsite.com/json/query?key=#", API_KEY];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:10];
NSURLSessionConfiguration *config = [NSURLSession defaultSessionConfiguration];
self.urlSession = [URLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURLSessionDataTask *dataTask = [self.URLSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"There was an error");
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil, error);
});
} else {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortByName];
NSArray *sortedResult = [dict[#"result"] sortedArrayUsingDescriptors:sortDescriptors];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(sortedResponse, error);
});
}
}];
[dataTask resume];
}

According to this post and this post NSURLCache is broken in iOS 8 when using NSURLSession.
According to the first post I linked, NSURLConnection still seems to work. I remember seeing in one of the comments that this was still the case as of early Feb, but I haven't had a chance to investigate myself.
Best of luck.

Related

NSJSONSerialization returning null - Objective-c

I'm trying to test a NSJSONSerialization with this URL ; the link works, and the app runs, but when I log the serialization, I get a null result.
Can anyone tell me what I'm doing wrong? Code:
#implementation ViewController
- ( void )viewDidLoad
{
[super viewDidLoad];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"https:franciscocosta.net/lisbon-spots"]];
NSURLSessionDataTask *task = [[self getURLSession] dataTaskWithRequest:request completionHandler:^( NSData *data, NSURLResponse *response, NSError *error )
{dispatch_async( dispatch_get_main_queue(),
^{
NSError *jsonError;
NSArray *parsedJSONArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
NSLog( #"%#", parsedJSONArray );
} );
}];
[task resume];
}
- ( NSURLSession * )getURLSession
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once( &onceToken,
^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration];
} );
return session;
}
It looks like your JSON is not well-formed.
If type your link into Safari, then copy the resulting JSON and paste it into a JSON verifier like JSONLint, it shows an error on line 25. It looks like there is closing bracket (]) that doesn't belong there. It appears that that JSON contains 2 root array objects, which I don't think is valid.
If you refactor it to contain a single array of dictionaries, it passes the JSONLint test as valid JSON.

Implement custom cache for AFNetworking 3.x

Hi I'm writing a IOS api to fetch data from server using AFNetworking 3.x My Server does not support caching. This this server header
Connection →Keep-Alive
Content-Length →4603
Content-Type →text/html; charset=UTF-8
Date →Wed, 10 Aug 2016 04:03:56 GMT
Keep-Alive →timeout=5, max=100
Server →Apache/2.4.18 (Unix) OpenSSL/1.0.1e-fips PHP/5.4.45
X-Powered-By →PHP/5.4.45
I want to build custom API to enable cache (for example request and response should be cache for 10 second so that if user make same request, cache data is used. If cache expired app will then re download again)
My AFNetworking Singleton
#pragma mark - Shared singleton instance
+ (ApiClient *)sharedInstance {
static ApiClient *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:SINGPOST_BASE_URL] sessionConfiguration:sessionConfiguration];
return sharedInstance;
}
- (id)initWithBaseURL:(NSURL *)url
{
if ((self = [super initWithBaseURL:url])) {
self.requestSerializer = [AFHTTPRequestSerializer serializer];
}
return self;
}
My JSON request
- (void)sendJSONRequest:(NSMutableURLRequest *)request
success:(void (^)(NSURLResponse *response, id responseObject))success
failure:(void (^)(NSError *error))failure {
self.responseSerializer.acceptableContentTypes = [AFHTTPResponseSerializer serializer].acceptableContentTypes;
self.requestSerializer.timeoutInterval = 5;
self.requestSerializer.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
[self setDataTaskWillCacheResponseBlock:^NSCachedURLResponse * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSCachedURLResponse * _Nonnull proposedResponse) {
return [[NSCachedURLResponse alloc] initWithResponse:proposedResponse.response
data:proposedResponse.data
userInfo:proposedResponse.userInfo
storagePolicy:NSURLCacheStorageAllowed];
}];
NSURLSessionDataTask *dataTask = [ApiClient.sharedInstance dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"Error URL: %#",request.URL.absoluteString);
NSLog(#"Error: %#", error);
failure(error);
} else {
NSDictionary *jsonDict = (NSDictionary *) responseObject;
success(response,jsonDict);
NSLog(#"Success URL: %#",request.URL.absoluteString);
NSLog(#"Success %#",jsonDict);
}
}];
[dataTask resume];
}
How to modify the bellow code to enable caching request and response (I want response to be keep like 10 sec) (Or 1 day if no internet connection) Any help is much appreciate. Thanks!
[self setDataTaskWillCacheResponseBlock:^NSCachedURLResponse * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSCachedURLResponse * _Nonnull proposedResponse) {
return [[NSCachedURLResponse alloc] initWithResponse:proposedResponse.response
data:proposedResponse.data
userInfo:proposedResponse.userInfo
storagePolicy:NSURLCacheStorageAllowed];
}];
Instead of
return [[NSCachedURLResponse alloc] initWithResponse:proposedResponse.response
do
NSMutableDictionary *dictionary = [proposedResponse.response.allHeaderFields mutableCopy];
dictionary[#"Cache-Control" = ...
NSHTTPURLResponse *modifiedResponse =
[[NSHTTPURLResponse alloc initWithURL:proposedResponse.response.URL
statusCode:proposedResponse.response.statusCode
HTTPVersion:#"HTTP/1.1"
headerFields:modifiedHeaders];
[modifiedResponse
return [[NSCachedURLResponse alloc] initWithResponse:modifiedResponse
Note that it is not possible to retrieve the HTTP version from the original request. I'm pretty sure I filed a bug about that. I'm also pretty sure that it doesn't have any effect on anything, and I don't think it even gets stored in the underlying object, so it probably doesn't matter.

Wait for NSURLSessionDataTask to come back

I am new to Objective C and iOS development in general. I am trying to create an app that would make an http request and display the contents on a label.
When I started testing I noticed that the label was blank even though my logs showed that I had data back. Apparently this happens because the the response is not ready when the label text gets updated.
I put a loop on the top to fix this but I am almost sure there's got to be a better way to deal with this.
ViewController.m
- (IBAction)buttonSearch:(id)sender {
HttpRequest *http = [[HttpRequest alloc] init];
[http sendRequestFromURL: #"https://en.wiktionary.org/wiki/incredible"];
//I put this here to give some time for the url session to comeback.
int count;
while (http.responseText ==nil) {
self.outputLabel.text = [NSString stringWithFormat: #"Getting data %i ", count];
}
self.outputLabel.text = http.responseText;
}
HttpRequest.h
#import <Foundation/Foundation.h>
#interface HttpRequest : NSObject
#property (strong, nonatomic) NSString *responseText;
- (void) sendRequestFromURL: (NSString *) url;
- (NSString *) getElementBetweenText: (NSString *) start andText: (NSString *) end;
#end
HttpRequest.m
#implementation HttpRequest
- (void) sendRequestFromURL: (NSString *) url {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
}];
[task resume];
}
Thanks a lot for the help :)
Update
After reading a lot for the very useful comments here I realized that I was missing the whole point. So technically the NSURLSessionDataTask will add task to a queue that will make the call asynchronously and then I have to provide that call with a block of code I want to execute when the thread generated by the task has been completed.
Duncan thanks a lot for the response and the comments in the code. That helped me a lot to understand.
So I rewrote my procedures using the information provided. Note that they are a little verbose but, I wanted it like that understand the whole concept for now. (I am declaring a code block rather than nesting them)
HttpRequest.m
- (void) sendRequestFromURL: (NSString *) url
completion:(void (^)(NSString *, NSError *))completionBlock {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Create a block to handle the background thread in the dispatch method.
void (^runAfterCompletion)(void) = ^void (void) {
if (error) {
completionBlock (nil, error);
} else {
NSString *dataText = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
completionBlock(dataText, error);
}
};
//Dispatch the queue
dispatch_async(dispatch_get_main_queue(), runAfterCompletion);
}];
[task resume];
}
ViewController.m
- (IBAction)buttonSearch:(id)sender {
NSString *const myURL = #"https://en.wiktionary.org/wiki/incredible";
HttpRequest *http = [[HttpRequest alloc] init];
[http sendRequestFromURL: myURL
completion: ^(NSString *str, NSError *error) {
if (error) {
self.outputText.text = [error localizedDescription];
} else {
self.outputText.text = str;
}
}];
}
Please feel free to comment on my new code. Style, incorrect usage, incorrect flow; feedback is very important in this stage of learning so I can become a better developer :)
Again thanks a lot for the replies.
You know what, use AFNetworking to save your life.
Or just modify your HttpRequest's sendRequestFromURL:
- (void)sendRequestFromURL:(NSString *)url completion:(void(^)(NSString *str, NSError *error))completionBlock {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
completionBlock(nil, error);
} else {
completionBlock([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], error);
}
});
}];
[task resume];
}
and invoke like this
[http sendRequestFromURL:#"https://en.wiktionary.org/wiki/incredible" completion:^(NSString *str, NSError *error) {
if (!error) {
self.outputLabel.text = str;
}
}];
Rewrite your sendRequestFromURL function to take a completion block:
- (void) sendRequestFromURL: (NSString *) url
completion: (void (^)(void)) completion
{
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
if (completion != nil)
{
//The data task's completion block runs on a background thread
//by default, so invoke the completion handler on the main thread
//for safety
dispatch_async(dispatch_get_main_queue(), completion);
}
}];
[task resume];
}
Then, when you call sendRequestFromURL, pass in the code you want to run when the request is ready as the completion block:
[self.sendRequestFromURL: #"http://www.someURL.com&blahblahblah",
completion: ^
{
//The code that you want to run when the data task is complete, using
//self.responseText
}];
//Do NOT expect the result to be ready here. It won't be.
The code above uses a completion block with no parameters because your code saved the response text to an instance variable. It would be more typical to pass the response data and the NSError as parameters to the completion block. See #Yahoho's answer for a version of sendRequestFromURL that takes a completion block with a result string and an NSError parameter).
(Note: I wrote the code above in the SO post editor. It probably has a few syntax errors, but it's intended as a guide, not code you can copy/paste into place. Objective-C block syntax is kinda nasty and I usually get it wrong the first time at least half the time.)
If you want easy way then Don't make separate class for call webservice. Just make meethod in viewController.m instead. I mean write sendRequestFromURL in your viewController.m and update your label's text in completion handler something like,
- (void) sendRequestFromURL: (NSString *) url {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
self.outputLabel.text = self.responseText;
})
}];
[task resume];
}

How to parse json data using singleton class

I have a singleton class where i have implemented a method to parse json data through URL. The code is as below
-(id)parseJsonDataWIthURL:(NSString *)url :(NSString*)datumm
{
NSMutableDictionary *arrrrr=[[NSMutableDictionary alloc]init];
NSMutableURLRequest *reqqq=[[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
NSData *dataaa=[datumm dataUsingEncoding:NSUTF8StringEncoding];
[reqqq setHTTPMethod:#"POST"];
[reqqq setHTTPBody:dataaa];
NSURLSessionConfiguration *configg=[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession*sessionn=[NSURLSession sessionWithConfiguration:configg delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *taskk=[sessionn dataTaskWithRequest:reqqq completionHandler:^(NSData *data,NSURLResponse *responce,NSError *error){
if(error)
{
NSLog(#"%#", [error localizedDescription]);
}else{
NSMutableDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments|NSJSONReadingMutableContainers error:&error];
NSLog(#"data %#",d);
[arrrrr setDictionary:d];
}
}];
[taskk resume];
return arrrrr;
}
The method returns no values, it is because the blocks takes time to execute within that method returns the result. So is there any way to wait until block completes and return the value.
ragul ml,
Simplest solution I can suggest is to use blocks :)
Here is how you can achieve it :) modify -(id)parseJsonDataWIthURL:(NSString *)url :(NSString*)datumm to,
-(void)parseJsonDataWIthURL:(NSString *)url :(NSString*)datumm withCompletionBlock:(void(^)(NSMutableArray *))completionBlock
{
NSMutableDictionary *arrrrr=[[NSMutableDictionary alloc]init];
NSMutableURLRequest *reqqq=[[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
NSData *dataaa=[datumm dataUsingEncoding:NSUTF8StringEncoding];
[reqqq setHTTPMethod:#"POST"];
[reqqq setHTTPBody:dataaa];
NSURLSessionConfiguration *configg=[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession*sessionn=[NSURLSession sessionWithConfiguration:configg delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *taskk=[sessionn dataTaskWithRequest:reqqq completionHandler:^(NSData *data,NSURLResponse *responce,NSError *error){
if(error)
{
NSLog(#"%#", [error localizedDescription]);
completionBlock(nil)
}else{
NSMutableDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments|NSJSONReadingMutableContainers error:&error];
NSLog(#"data %#",d);
[arrrrr setDictionary:d];
if (completionBlock) {
completionBlock(arrrrr);
}
}
}];
[taskk resume];
}
finally modify your call as,
[[YourSingletonClass sharedInstance] parseJsonDataWIthURL:your_url :datumm withCompletionBlock:^(NSMutableArray *array) {
if (array){
//web service is successful
}
}];

Google Place api URL iOS

I am creating an app that uses Google Place. Before I used to use Yahoo's api and had to use a url that was responsible for local search that was provided by yahoo. The url was following:
http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid=SF0DVEvV34G4GnXEDU4SXniaDebJ_UvC1G1IuikVz3vpOJrBpyD.VqCJCVJHMh99He3iFz1Rzoqxb0b7Z.0-
Now since yahoo's api is discontinued I have decided to switch over to Google Place. But I cannot find an Url to use. I just dowlod the framework and use the api key. Where can I find such url for Google Place.
Register for the Google Places API by following the linke provided below:
https://code.google.com/apis/console
Refer Code Link for places Auto Search
https://github.com/AdamBCo/ABCGooglePlacesAutocomplete
NSString *const apiKey = #"*****23xAHRvnOf2BVG8o";
NSString * searchWord = #"search some place "
NSString *urlString = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/autocomplete/json?input=%#&types=establishment|geocode&radius=500&language=en&key=%#",searchWord,apiKey];
pragma mark - Network Methods
-(void)retrieveGooglePlaceInformation:(NSString *)searchWord withCompletion:(void (^)(BOOL isSuccess, NSError *error))completion {
if (!searchWord) {
return;
}
searchWord = searchWord.lowercaseString;
self.searchResults = [NSMutableArray array];
if ([self.searchResultsCache objectForKey:searchWord]) {
NSArray * pastResults = [self.searchResultsCache objectForKey:searchWord];
self.searchResults = [NSMutableArray arrayWithArray:pastResults];
completion(YES, nil);
} else {
NSString *urlString = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/autocomplete/json?input=%#&types=establishment|geocode&radius=500&language=en&key=%#",searchWord,apiKey];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jSONresult = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if (error || [jSONresult[#"status"] isEqualToString:#"NOT_FOUND"] || [jSONresult[#"status"] isEqualToString:#"REQUEST_DENIED"]){
if (!error){
NSDictionary *userInfo = #{#"error":jSONresult[#"status"]};
NSError *newError = [NSError errorWithDomain:#"API Error" code:666 userInfo:userInfo];
completion(NO, newError);
return;
}
completion(NO, error);
return;
} else {
NSArray *results = [jSONresult valueForKey:#"predictions"];
for (NSDictionary *jsonDictionary in results) {
}
//[self.searchResultsCache setObject:self.searchResults forKey:searchWord];
completion(YES, nil);
}
}];
[task resume];
}
}

Resources