changing AFNetworking baseURL - objective-c

I am using AFNetworking with the singleton model suggested in their example.
+ (SGStockRoomHTTPClient *)sharedClient
{
static SGStockRoomHTTPClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
NSString *baseUrlString = [[NSUserDefaults standardUserDefaults] stringForKey:#"server_root_url_preference"];
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:baseUrlString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"text/html"];
return self;
}
Initialization is done with a baseURL taken from the user defaults.
My problem is that the baseURL property is read-only. If the user goes to settings and changes the baseURL user default, how can I change it in my client?
Another similar case I have with a need to change the baseURL is an API which requires multiple calls and logic to determine to the right baseURL. And the base url can still change while the app is running (e.g. user changes networking environment requiring a change from local connection to 3G connection via external proxy server.).
I see why the baseURL property is read-only: there are things like networkReachabilityStatus that run in the background and are tied to that setting. This said, it seems fairly easy to have a setBaseURL method that stops monitoring, changes the value, then starts monitoring again...
I guess my design is not right, should I give-up the singleton in this case and re-create the client each time the baseURL should change?

The class AFHTTPClient is designed to work with a single base URL. This is why it is readonly.
Here are some solutions if you have more than one base URL:
in your AFHTTPClient subclass override the property qualifier. Place this inside the #interface: #property (readwrite, nonatomic, retain) NSURL *baseURL;
Don't use AFHTTPClient at all
Create multiple instances of AFHTTPClient
When you create HTTP operations you can override the baseURL. Just set the full URL in the path instead of a relative path.

Hope this helps you.
#interface EFApiClient : AFHTTPSessionManager
#property (nonatomic,assign)BOOL isTestEnvironment ;
+ (instancetype)sharedMClient;
#end
#implementation EFApiClient
+ (instancetype)sharedMClient
{
if ([EFApiClient sharedClient].isTestEnvironment) {
return [EFApiClient sharedTestClient] ;
}
else{
return [EFApiClient sharedClient];
}
}
+ (instancetype)sharedClient
{
static EFApiClient *_sharedMClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedMClient = [[EFApiClient alloc] initWithBaseURL:[NSURL URLWithString:#"https://xxx.xxx.com"]];
[EFApiClient clientConfigWithManager:_sharedMClient];
});
return _sharedMClient;
}
+ (instancetype)sharedTestClient
{
static EFApiClient *_sharedMClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedMClient = [[EFApiClient alloc] initWithBaseURL:[NSURL URLWithString:#"https://test.xxx.xxx.com"]];
[EFApiClient clientConfigWithManager:_sharedMClient];
});
return _sharedMClient;
}
+ (void)clientConfigWithManager:(EFApiClient *)client
{
AFSecurityPolicy* policy = [[AFSecurityPolicy alloc] init];
[policy setAllowInvalidCertificates:YES];
[policy setValidatesDomainName:NO];
[client setSecurityPolicy:policy];
client.requestSerializer = [AFHTTPRequestSerializer serializer];
client.responseSerializer = [AFHTTPResponseSerializer serializer];
//client.requestSerializer.HTTPMethodsEncodingParametersInURI = [NSSet setWithArray:#[#"POST", #"GET", #"HEAD"]];
client.responseSerializer = [AFJSONResponseSerializer serializer];
client.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/x-javascript",#"application/json", #"text/json", #"text/html", nil];
}
#end

Related

Why do I get Use of undeclared identifier 'downloadDataFromURL' when method is defined in same class?

I have written a method that I want to reuse from another method in the same class but I am getting this error message and I don't understand why, how can it be undeclared when it is declared in the same class?
the h file looks like this
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import "NWTillHelper.h"
#interface JoshuaWebServices : NSObject
+ (void)downloadDataFromURL:(NSURL *)url withCompletionHandler:(void (^)(NSData *))completionHandler;
- (void)downloadCollections;
#end
And the m file as follows
#import "JoshuaWebServices.h"
#implementation JoshuaWebServices
#synthesize xmlParser;
+ (void)downloadDataFromURL:(NSURL *)url withCompletionHandler:(void (^)(NSData *))completionHandler {
if([NWTillHelper isDebug] == 1) {
NSLog(#"%s entered", __PRETTY_FUNCTION__);
}
// Lots of irrelevant code here
}
- (void)downloadCollections {
// Prepare the URL that we'll get the neighbour countries from.
NSString *URLString = [NSString stringWithFormat:#"https://url.is.not.here"];
NSURL *url = [NSURL URLWithString:URLString];
// Download the data.
[downloadDataFromURL:url withCompletionHandler:^(NSData *data) {
// Make sure that there is data.
if (data != nil) {
self.xmlParser = [[NSXMLParser alloc] initWithData:data];
self.xmlParser.delegate = self;
// Initialize the mutable string that we'll use during parsing.
self.foundValue = [[NSMutableString alloc] init];
// Start parsing.
[self.xmlParser parse];
}
}];
}
Why can I not use the method declared in same class?
Your method needs a receiver. Unlike functions that can just be called on there own. Methods must be called by something, either the class, or an instance of a class. In your case you should use a class because it's a class method.
Change
[downloadDataFromURL:url withCompletionHandler:^(NSData *data) {
// Make sure that there is data.
if (data != nil) {
self.xmlParser = [[NSXMLParser alloc] initWithData:data];
self.xmlParser.delegate = self;
// Initialize the mutable string that we'll use during parsing.
self.foundValue = [[NSMutableString alloc] init];
// Start parsing.
[self.xmlParser parse];
}
}];
to be
[JoshuaWebServices downloadDataFromURL:url withCompletionHandler:^(NSData *data) {
// Make sure that there is data.
if (data != nil) {
self.xmlParser = [[NSXMLParser alloc] initWithData:data];
self.xmlParser.delegate = self;
// Initialize the mutable string that we'll use during parsing.
self.foundValue = [[NSMutableString alloc] init];
// Start parsing.
[self.xmlParser parse];
}
}];

Subclassing a singleton not working as expected

I'm writing a class that communicates with an API. I started by creating a SessionManager:
#interface SessionManager : AFHTTPSessionManager
+ (id)sharedManager;
#end
static NSString *const kBaseURL = #"https://myapi.com";
#implementation SessionManager
- (id)init {
self = [super initWithBaseURL:[NSURL URLWithString:kBaseURL]];
if(!self) return nil;
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.requestSerializer = [AFJSONRequestSerializer serializer];
return self;
}
+ (id)sharedManager {
static SessionManager *_sessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sessionManager = [[self alloc] init];
});
return _sessionManager;
}
#end
I wrote several classes that extend this SessionManager. One for every entity in the API: RestaurantManager, StreetManager and AreaManager. What happens, though, is that when I use one of them and then another, it will still use the first one.
NSArray *restaurants = [[RestaurantManager sharedManager] getRestaurants];
// restaurants contains all the restaurants
NSArray *streets = [[StreetsManager sharedManager] getStreets];
// streets still contains all the restaurants
Any ideas as to how to fix this?
You should override + (id)sharedManager methods in subclasses. Otherwise, they go to the same sharedManager method and interact with the same static SessionManager variable.

Try to change variable in singleton but it stays nullable

Just started programming on objective-c and now i have issue with which can't deal by myself. I'm receiving data from asynchronous request and try to delver it to singleton, but it's not changed.
This is where i'm trying to store my data
Data.h
#import <Foundation/Foundation.h>
#interface Data : NSObject
#property (nonatomic, strong) NSDictionary *products;
-(void)setProducts:(NSDictionary *)value;
#end
Data.m
#import "Data.h"
#implementation Data
+(Data *)sharedInstance
{
static Data *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[Data alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if ( self )
{
_products = [[NSDictionary alloc] init];
}
return self;
}
#end
This is the class, where i'm receiving data from server:
ConnectionService.m
- (void)getProductsWithCompletion:(void (^)(NSDictionary *products))completion
{
NSString *urlString = [NSString stringWithFormat:#"serverurl", [[AppDelegate instance]getUrl]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *getData = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSString *rawJson = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *value = [rawJson JSONValue];
completion(value);
}];
[getData resume];
}
This is the class where i'm calling request and try to deliver it to singleton:
viewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance] products]);//all is working, products contains data
}];
// checking received data
NSDictionary *tmp = [[Data sharedInstance] products];
NSLog(#"tmp: %#", tmp); //now it's null
}
The issue is the fact that the request is asynchronous and things aren't happening in the order you expect:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
// (2)
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);//all is working, products contains data
}];
// (1)
NSDictionary *tmp = [[Data sharedInstance]products];
NSLog(#"tmp: %#", tmp); //now it's null
}
In the code you posted, (1) will happen before (2). That's because (2) is part of the completion block and is set to run once the network request has completed and all the data has been parsed and is ready to use. While that asynchronous request is prepared and run in a background thread, the main thread ((1)) continues and executes before the request has taken place.
To resolve the issue, move your logging into the completion routine, or simply remove (1).
Another way is to use protocol, to notify your completion block is finished.So that you can simply do:
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
if(self.delegate){
[self.delegate myNotifyMethod:products];
}
}];
and your protocol method:
-(void)myNotifyMethod:(NSDictionary *)items{
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);
}
You can declare the protocol as:
#protocol MyProtocol <NSObject>
- (void)myNotifyMethod: (NSDictionary *)items;
#end
and set the delegate property as:
#property (nonatomic, weak) id<MyProtocol> delegate;

NSURL Caching issues

I'm having an issue with a login API. First call works fine, but subsequent calls are cached. This is causing an issue since login/logout functionality is essentially broke.
I've tried many methods and I'm implementing AFNetworking library.
In AppDelegate.m:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
In my Networking class:
(AFHTTPRequestOperation *)createRequestOperationWithMethod:(NSString *) method andPath: (NSString *)path andParams:(NSDictionary *)params
{
GRAPIClient *httpClient = [GRAPIClient sharedClient];
[httpClient setParameterEncoding:AFFormURLParameterEncoding];
NSMutableURLRequest *request = [httpClient requestWithMethod:method
path:path
parameters:params];
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData]
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
return operation;
}
I even tried to overwrite the request being generated in AFHTTPClient
In AFHTTPClient.m:
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[request setTimeoutInterval:2.0];
My GRAPIClient implementation:
#interface GRAPIClient : AFHTTPClient
+ (GRAPIClient *)sharedClient;
+ (BOOL) isInternetReachable;
#end
#implementation GRAPIClient
+ (BOOL) isInternetReachable
{
return reachable;
}
+ (GRAPIClient *)sharedClient {
static GRAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[GRAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kAFAppDotNetAPIBaseURLString]];
});
[_sharedClient setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if (status == AFNetworkReachabilityStatusReachableViaWWAN ||
status == AFNetworkReachabilityStatusReachableViaWiFi ) {
NSLog(#"Reachable on!");
reachable = YES;
}
else
{
NSLog(#"Reachable off!");
reachable = NO;
}
}];
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
return self;
}
#end
I've debugged responses from the server and tested with hard coding two NSURLRequests simultaneously to the server. One for User A and one for User B, then printed the response data for both users.
On first login, User A login returned User A credentials. User B returned User B credentials. On second login, User A returned User A credentials, User B returned User A credentials. I have no idea how to fully disable cacheing.
Try:
[operation setCacheResponseBlock:^NSCachedURLResponse*(NSURLConnection* connection, NSCachedURLResponse* cachedResponse) {
return nil;
}];
And:
[request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];
The issue for me as suggested by SixteenOtto was a session being sent from the server and AFNetworking automatically using the cookie. I hadn't considered this before since we're using a restless API based on auth tokens, so a cookie for the session makes no sense. However, inspecting the HTTP response headers with Charles allowed me to see this.
Adding
[request setHTTPShouldHandleCookies:NO];
To my operation generator solved the issue.

Singleton pattern with parameter

in my iPhone application, I'm using a subclass of AFHTTPClient to access a rest web service. I want all my requests to be handled by one instance of my API client so I use a singleton pattern.
This works fine when the service is running only on once URL. I can use a constant value to set the URL.
Now, in the final version of the application, each app will actually talk to another service that will be installed in the corporate network.
So I will be getting the service URL from a remote configuration. Is the singleton pattern still a good choice here? How am I supposed to parameterise it if the URL could actually even change during the runtime of the app ?
cheers
#import "FooAPIClient.h"
#import "AFJSONRequestOperation.h"
static NSString * const kFooAPIBaseURLString = #"http://192.168.0.1";
#implementation FooAPIClient
+ (instancetype)sharedClient {
static FooAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kFooAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
return self;
}
#end
This could be the solution. Instead of subclassing the AFHTTPClient, just set it as property and re-instantiate it if the URL changes:
#import "FooAPIClient.h"
#import "AFJSONRequestOperation.h"
#import "AFHTTPClient.h"
static NSString * const kFooAPIBaseURLString = #"http://192.168.0.1";
#interface FooAPIClient ()
#property AFHTTPClient * httpClient;
#end
#implementation FooAPIClient
+ (instancetype)sharedClient {
static FooAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kFooAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super init];
if (!self) {
self.httpClient = [self setupClientForURL:url];
}
return self;
}
-(AFHTTPClient*) setupClientForURL:(NSURL*) url {
AFHTTPClient * httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[httpClient setDefaultHeader:#"Accept" value:#"application/json"];
return httpClient;
}
#pragma mark - RemoteConfigurationDelegate
-(void) apiURLChanged:(NSURL*) newURL {
self.httpClient = [self setupClientForURL:newURL];
}
#pragma mark - Public
-(void) consumeAPI:(CompletionBlock) completion {
[self.httpClient getPath:#"foo" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if(completion) {
completion(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if(completion) {
completion(nil, error);
}
}];
}
#end
The singleton pattern doesn't have to be a straitjacket.
This lesson I learned from Cocoa Touch. There are several classes in the framework that use shared instances, but allow you the flexibility of creating your own instances if you need them. NSNumberFormatter, NSDateFormatter, NSBundle, NSFileManager and many many others are examples of classes where you can create your own instances if you need them.
In your case, I would have two class methods that return instances:
+ (instancetype)sharedClient {
static FooAPIClient *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kFooAPIBaseURLString]];
});
return instance;
}
static FooAPIClient *FooSharedCorporateInstance;
+ (instancetype)sharedCorporateClient {
#syncronized (FooSharedCorporateInstance) {
return FooSharedCorporateInstance;
}
}
+ (void)setSharedCorporateClientWithURL:(NSURL *)URL {
#syncronized (FooSharedCorporateInstance) {
FooSharedCorporateInstance = [[self alloc] initWithBaseURL:URL];
}
}
As a side benefit, this forces separate class and instance responsibilities that tend to blur in singleton classes.