Step by step to build your iOS network layer – HTTP articles


Regular runners must know, after every run time after 15 minutes, the mind usually began to appear: brothel flower, white king, the world is not dead, never surrender like two reverie. However leave after, these ideas suddenly disappeared, some iOS knowledge points and the previous project experience began in his mind, rather baffling actually own the permutation and combination of seven or eight articles. These strange ideas gradually satisfied when running within the brain, it is in the subway Commercial Street bus also runs fine hum sing: write down, write to
… This idea is like a second part-time encounter little sister the more is, try not to remember, she will become more beautiful…
… You want to write, then write it.


The network layer architecture design based on the casatwy’s request from the network construction to request processing results for you an overview of how to build a iOS network layer, easy to use, the full text is about five thousand words, is expected to spend time reading 20 – 30 minutes.


  • Construction of network requests
  • Network requests are distributed
    1 requests are distributed with the cancellation of
    2 multi server switching
  • Rational use of the request dispatcher
    1 protocol or configuration object
    2 simple request result buffer
    3 request the result of formatting
    two gadgets

Construction of network requests

The construction of the network request is very simple, according to the requirements of a request, such as URL, request method, request parameters, request the first definition of the interface can be defined:

@interface HHURLRequestGenerator: NSObject + sharedInstance; (instancetype) - (void) - (void) switchService; switchToService: (serviceType; HHServiceType) - (NSMutableURLRequest) generateRequestWithUrlPath: (NSString *) urlPath useHttps: (BOOL) useHttps method: (NSString * method) params: (NSDictionary *) params header: (NSDictionary) - NSMutableURLRequest (header; *) generateUploadRequestUrlPath: (* NSString) urlPath useHttps: (BOOL) useHttps params: (NSDictionary * params) Contents: (NSArray< HHUploadFile *> *) contents header: (NSDictionary *) header; @end

You can see the method parameters are generated to request basic components, of course, this parameter is relatively small, because the timeout of a request in my projects are the same, similar to these public settings I lazy write directly on the request configuration file inside. We look at the specific interface to achieve the request of the data request cases:

- (NSMutableURLRequest *) generateRequestWithUrlPath: (* NSString) urlPath useHttps: (BOOL) useHttps method: (NSString * method) params: (NSDictionary * params) header: (NSDictionary * header) {NSString *urlString = [self urlStringWithPath: urlPath useHttps:useHttps]; NSMutableURLRequest *request = [self.requestSerialize requestWithMethod:method URLString:urlString parameters:params error:nil]; request.timeoutInterval = RequestTimeoutInterval; [self setCookies] cookie [self setCommonRequestHeaderForRequest:request]; / / set do the public; / / request header here to set the [header enumerateKeysAndObjectsUsingBlock:^ (ID _Nonnull key, ID _Nonnull value, BOOL * _Nonnull stop) {[request setValue:value forHTTPHeaderField:key]; }]; return request;}
- (NSString *) urlStringWithPath: (* NSString) path useHttps: (BOOL useHttps) {if ([path hasPrefix:@ "HTTP"]) {return} path {NSString = *baseUrlString; else [HHService currentService].baseUrl; if (useHttps & & baseUrlString.length > 4) {NSMutableString *mString = [NSMutableString stringWithString:baseUrlString]; [mString insertString:@ "s" atIndex:4] [mString; baseUrlString = copy];} return [NSString stringWithFormat:@ "%@%@", baseUrlString, path];}}

The code is very simple, according to the parameters of interface by calling urlStringWithPath:useHttps: BaseURL and URLPath assembled a complete URL, then a URLRequest generated by the URL and other parameters, and then call setCommonRequestHeaderForRequest: to set the public request, and finally return to the URLRequest.

BaseURL from HHService, HHService exposed each environment (test / development / release) under baseURL and the switch server interface, internal factories generate the current server, I set the default connection is the first server and the APP closed after the restoration of this setting, APP operation can be based on the need to call the switchService server
HHService switch. The definition is as follows:

@protocol HHService < NSObject> @optional (NSString * testEnvironmentBaseUrl); - (NSString * developEnvironmentBaseUrl); - (NSString *) releaseEnvironmentBaseUrl @end @interface; HHService: NSObject< HHService> currentService; + (HHService *) + (void) switchService; (void) + switchToService: (HHServiceType) - serviceType (NSString *); baseUrl - (HHServiceEnvironment); environment; @end
#import "HHService.h" @interface (HHService) @property (assign, nonatomic) HHServiceType type @property (assign, nonatomic); HHServiceEnvironment environment; @end @interface HHServiceX @end @interface HHServiceY: HHService: HHService @end @interface HHServiceZ @end @implementation HHService #pragma: HHService mark - Interface static HHService *currentService; static dispatch_semaphore_t lock; + (HHService * currentService) {static dispatch_once_t onceToken; dispatch_once (& onceToken, lock (1) ^{= dispatch_semaphore_create; currentService = [HHService; serviceWithType:HHService0]; return currentService;})} + {[self (void) switchService switchToService:self.currentService.type + 1]; } + (void) switchToService: (HHServiceType serviceType) {dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER); currentService [HHService = serviceWithType: (serviceType% ServiceCount)]; dispatch_semaphore_signal (lock);} + (HHService *) serviceWithType: (HHServiceType) type {HHService *service; switch (type) {case HHService0: service = [HHServiceX new]; break; case HHService1: service = [HHServiceY new]; break case; HHService2: service = [HHServiceZ new]; break service.type;} = type; service.environment = BulidServiceEnvironment; return service;} - (NSString * baseUrl) {switch (self.environment) HHServiceEnvironmentTest: return [self testEnvironmentBaseU {case Rl]; case HHServiceEnvironmentDevelop: [self developEnvironmentBaseUrl]; case return [self releaseEnvironmentBaseUrl];} @end HHServiceEnvironmentRelease:

2 distribution of network requests

The payment request is realized by a single HHNetworkClient, if the request to the shell, then this is the case of single fired artillery shells of the fort, Fort people use only need to tell what kind of battery needed to launch shells and shell targets can be fired. In addition, should provide the function to cancel the attack in order to deal with unnecessary combat situations, then, according to the
HHNetworkClient. The fort is defined as follows:

@interface HHNetworkClient: NSObject + (instancetype) sharedInstance; - (NSURLSessionDataTask *) dataTaskWithUrlPath: (* NSString) urlPath useHttps: (BOOL) useHttps requestType: (HHNetworkRequestType) requestType params: (NSDictionary * params) header: (NSDictionary * header) (completionHandler: (void ^ (NSURLResponse) *response, ID responseObject, NSError *error) - (completionHandler); NSNumber dispatchTaskWithUrlPath: (NSString) * *) urlPath useHttps: (BOOL) useHttps requestType: (HHNetworkRequestType) requestType Params: (NSDictionary * params) header: (NSDictionary * header) (completionHandler: (void ^ (NSURLResponse) *response, ID responseObject, NSError *error) - (NSNumber) completionHandler; dispatchTask: (NSURLSessionTask * *) - (NSNumber) task; uploadDataWithUrlPath: (NSString * *)) urlPath useHttps: (BOOL) useHttps (NSDictionary * params:) params (contents: NSArray< HHUploadFile *> contents header: (* *) NSDictionary (header) progressHandler: (void ^) (NSProgress *) progressHandler completionHandler: (void) (^) (NSURLResponse *response, ID responseObj Ect, NSError *error) completionHandler - (void) cancelAllTask; - (void) cancelTaskWithTaskIdentifier: (NSNumber *) taskIdentifier; @end
@interface (HHNetworkClient) @property (strong, nonatomic) AFHTTPSessionManager *sessionManager (strong, nonatomic); @property NSMutableDictionary< NSURLSessionTask *> NSNumber * *dispathTable; @property (assign, nonatomic) CGFloat totalTaskCount @property (assign, nonatomic); CGFloat errorTaskCount; @end

1 requests and distribute cancel
external exposure data request and file upload interface, the necessary parameters required for the construction of the request, the return value is the request of taskIdentifier, the caller can cancel the request task is being executed by the taskIdentifier.
declare a dispathTable keep executing the task, and in reference task execution completed or when the task is canceled, remove task, to request data as an example, the specific implementation is as follows:

- (NSURLSessionDataTask *) dataTaskWithUrlPath: (* NSString) urlPath useHttps: (BOOL) useHttps requestType: (HHNetworkRequestType) requestType params: (NSDictionary * params) header: (NSDictionary * header) (completionHandler: (void ^) (NSURLResponse *, ID * completionHandler, NSError)) {NSString = *method (requestType = = "GET": @ HHNetworkRequestTypeGet? @ "POST"); NSMutableURLRequest *request = [[HHURLRequestGenerator sharedInstance] generateRequestWithUrlPath:urlPath useHttps:useHttps method:method params:params header:header]; NSMutableArray *taskIdentifier = [NSMutableArray arrayWithObject:@-1]; NSURLSessionDataTask *task = [self.sessionManager dataTaskWithRequest:request completionHandler:^ (NSURLResponse * _Nonnull response, ID _Nullable responseObject, NSError * _Nullable error) {dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER); [self checkSeriveWithTaskError:error]; [self.dispathTable removeObjectForKey:taskIdentifier.firstObject]; dispatch_semaphore_signal (lock); completionHandler? CompletionHandler (response, responseObject, error): Nil;}]; taskIdentifier[0] = @ (task.taskIdentifier); return task;} - (NSNumber *) dispatchTaskWithUrlPath: (NSString * urlPath) useHttps: (BOOL) useHttps requestType: (HHNetworkRequestType) requestType params: (NSDictionary * params) header: (NSDictionary * header) (completionHandler: (void ^) (NSURLResponse *, ID *, NSError) {return [self dispatchTask:[self completionHandler) DataTaskWithUrlPath:urlPath useHttps:useHttps requestType:requestType params:params header:header completionHandler:completionHandler]];} - (NSNumber *) dispatchTask: (NSURLSessionDataTask * task) {if (task = = Nil) {return @-1;} dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER); self.totalTaskCount + = 1; [self.dispathTable setObject:task forKey:@ (task.taskIdentifier)]; dispatch_semaphore_signal (lock); [task resume]; return (@ task.taskIdentifier);}

The code is very simple, the generation of URLRequest through the parameters, and then through the AFHTTPSessionManager mission in before the tasks we take task.taskIdentifier as key to hold the execution of the task, and then in the task execution after we remove this task, of course, can also be external when necessary we returned task.taskIdentifier manually remove tasks.

Note that we declared a NSMutableArray to taskIdentifier, and then in the task of setting taskIdentifier[0] to task. after the formation of taskIdentifier, and finally use the taskIdentifier[0] to complete the task in the block callback to remove the completed task.
may have doubt why not use task.taskIdentifier directly, block can not explain the acquisition of task? Why do you write:

We know the difference between the block function is the maximum object which can capture its scope, object, and captured in the block when the execution of access to specific, for the value type object block will create a copy of this object, the reference object of type block will generate a this object and make the object’s reference count +1 (here we only describe the non modified __block case). Then substituted into the above code, we step by step analysis:

  • Direct capture task written *task = [self.sessionManager dataTaskWithRequest:request completionHandler:^ NSURLSessionDataTask (NSURLResponse * _Nonnull response, ID _Nullable responseObject, NSError * _Nullable error [self.dispathTable removeObjectForKey:@ (omitted) {… Task.taskIdentifier)]; [self.dispathTable setObject:task}] slightly…; forKey:@ (task.taskIdentifier)]; we take it apart to see: NSURLSessionDataTask *task; NSURLSessionDataTask *returnTask = [self.sessionManager dataTaskWithRequest:request (completionHandler:^ NSURLResponse * _Nonnull response, ID _Nullable responseObject, NSError * _Nullable error) {… [self.dispathTable removeObjectForKey:@ (task.taskIdentifier)]; slightly}]… Task = returnTask; [self.dispathTabl; E setObject:task forKey:@ (task.taskIdentifier)]; we can see that returnTask is the actual storage tasks, while the task is only a temporary variable, the task point to nil, we generated returnTask block when the captured task is nil, so when the task finished our task.taskIdentifier is 0, this result of writing is that dispathTable will only add (does not delete the system taskIdentifier from the beginning of 0 in increasing), of course, because we are doing in returnTask storage, so when the task is not completed or we can do cancel.
  • If you start to give the task a placeholder object does not make it for nil? NSURLSessionDataTask *task = [NSObject new]; //1.suspend NSURLSessionDataTask *returnTask = [self.sessionManager dataTaskWithRequest:request completionHandler:^ (NSURLResponse * _Nonnull response, ID _Nullable responseObject, NSError * _Nullable error [self.dispathTable removeObjectForKey:@ (omitted) {… Task.taskIdentifier)]; //3.completed… //2.alloc task}] slightly; = returnTask; [self.dispathTable setObject:task forKey:@ (task.taskIdentifier)]; this is actually a reference to transform a simple question, we look at each point: suspend: pTask-> NSObject pointer block.pTask-> nil pReturnTask-&gt nil;
    alloc: pTask-> NSObject block.pTask-> NS Object pReturnTask-> returnTask
    completed: pTask-> returnTask block.pTask-> NSObject pReturnTask-> returnTask can be seen in the task completion we visited block.pTask but we started occupying the object, so this scheme is not, of course, cancel the task is still available, the fact that block.pTask is indeed captured a placeholder object, but we in then do not replace the block.pTask pointing to returnTask block.pTask, but we are not visit, so this plan won’t work.
  • If our placeholder object is a container? NSMutableArray *taskIdentifier = [NSMutableArray arrayWithObject:@-1]; NSURLSessionDataTask *returnTask = [self.sessionManager dataTaskWithRequest:request completionHandler:^ (NSURLResponse * _Nonnull response, ID _Nullable responseObject, NSError * _Nullable error [self.dispathTable removeObjectForKey:@ (omitted) {… TaskIdentifier.firstObject)]; taskIdentifier[0] = @… Slightly}]; (returnTask.taskIdentifier); [self.dispathTable setObject: task forKey:@ (task.taskIdentifier)]; since we can not access the block.pTask access object referred to by block.pTask, change the object’s content is not equivalent to changes in block.pTask, we follow the 2 ideas to go should be easy to understand, I will not say .

2 multi server on multiple servers. In fact I have no real experience, the company is deploying second servers, if access to specific needs is the first server always timeout or wrong, then switch to the second server, based on the needs of my simple implementation:

- (NSNumber) dispatchTask: (NSURLSessionDataTask * task) {... Dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER) slightly; self.totalTaskCount + = 1; [self.dispathTable setObject:task forKey:@ (task.taskIdentifier)]; dispatch_semaphore_signal (lock);} slightly...
- (NSURLSessionDataTask *) dataTaskWithUrlPath: (* NSString) urlPath useHttps: (BOOL) useHttps requestType: (HHNetworkRequestType) requestType params: (NSDictionary * params) header: (NSDictionary * header) (completionHandler: (void ^) (NSURLResponse *, ID * completionHandler, NSError)) {NSString = *method (requestType = = "GET": @ HHNetworkRequestTypeGet? @ "POST"); NSURLSessionDataTask *task = [self.sessionManager dataTaskWithRequest:request slightly... CompletionHandler:^ (NSURLResponse * _Nonnull response, ID _Nullable responseObject, NSError * _Nullable error) {... [self checkSeriveWithTaskError:error];...}] slightly slightly;}...
- (void) checkSeriveWithTaskError: (NSError * error) {if ([HHAppContext sharedInstance].isReachable) {switch (error.code) case NSURLErrorUnknown: case NSURLErrorTimedOut: case NSURLErrorCannotConnectToHost: {{self.errorTaskCount}} = 1; default:break; if (self.totalTaskCount > = 40; & & (self.errorTaskCount / self.totalTaskCount) = = 0.1) {self.totalTaskCount = self.errorTaskCount = 0; [[HHURLRequestGenerator sharedInstance] switchService];}}}
- (void) didReceivedSwitchSeriveNotification: (NSNotification * Notif) {dispatch_semaphore_wait (lock, DISPATCH_TIME_FOREVER); self.totalTaskCount = self.errorTaskCount = 0; dispatch_semaphore_signal (lock); [[HHURLRequestGenerator sharedInstance] switchToService:[notif.userInfo[@ service integerValue]];}

That task in the APP network in the process of using the error rate of 10% it should switch to server, we will distribute the total number of tasks in the task before +1, and then at the end of the task to determine whether the task is successful, it will fail the task fails and then determine whether the total number of +1 reached the maximum error rate, and then switch to another
server. Another case is that most servers are hung up, go back directly push APNS server is available over the number, not one by one, one by one switch.

Three. Reasonable use request distributor

OK, the shells are there, and the battery is ready. Let’s see how to use the battery

#pragma mark - HHAPIConfiguration typedef void (^HHNetworkTaskProgressHandler) (CGFloat progress); typedef void (^HHNetworkTaskCompletionHander) (NSError *error, ID result); @interface HHAPIConfiguration: NSObject @property (copy, nonatomic) NSString *urlPath @property (strong, nonatomic); NSDictionary *requestParameters; @property (assign, nonatomic) BOOL useHttps @property (strong, nonatomic); NSDictionary *requestHeader @property (assign, nonatomic); HHNetworkRequestType requestType; @end @interface HHDataAPIConfiguration: HHAPIConfiguration @property (assign, nonatomic) NSTimeInterval cacheValidTimeInterval; @end @interface HHUploadAPIConfiguration: HHAPIConfiguration @ property (strong, nonatomic) NSArray< HHUploadFile *> * uploadContents @end #pragma mark HHAPIManager @interface; HHAPIManager: cancelAllTask; NSObject - (void) - (void) cancelTaskWithtaskIdentifier: (NSNumber * taskIdentifier); (void) + cancelTaskWithtaskIdentifier: (NSNumber * taskIdentifier); (void) + cancelTasksWithtaskIdentifiers: (NSArray * taskIdentifiers); - (NSURLSessionDataTask) dataTaskWithConfiguration: (HHDataAPIConfiguration * completionHandler: (config) completionHandler; HHNetworkTaskCompletionHander) - (NSNumber) dispatchDataTaskWithConfiguration: (HHDataAPIConfiguration *) config completionHandler: (HHNetworkTaskCompletionHander) completionHandler; - (NSNumber *) dispatchUploadTaskWithConfiguration: (* HHUploadAPIConfiguration) config progressHandler: (HHNetworkTaskProgressHandler) progr EssHandler completionHandler: (HHNetworkTaskCompletionHander) completionHandler; @end
- (void) cancelAllTask for (NSNumber *taskIdentifier in self.loadingTaskIdentifies {sharedInstance]}) {[[HHNetworkClient cancelTaskWithTaskIdentifier:taskIdentifier]; [self.loadingTaskIdentifies removeAllObjects];} - (void) cancelTaskWithtaskIdentifier: (NSNumber * taskIdentifier) {[[HHNetworkClient sharedInstance] cancelTaskWithTaskIdentifier:taskIdentifier]; [self.loadingTaskIdentifies removeObject:taskIdentifier];} + (void) cancelTaskWithtaskIdentifier: (NSNumber *) taskIdentifier sharedInstance] {[[HHNetworkClient cancelTaskWithTaskIdentifier:taskIdentifier];} + ((void) cancelTasksWithtaskIdentifiers: NSArray * taskIdentifiers for (NSNumber) {*taskIdentifier {[[HHNet in taskIdentifiers) WorkClient sharedInstance] cancelTaskWithTaskIdentifier:taskIdentifier];}} - (NSURLSessionDataTask *) dataTaskWithConfiguration: (* HHDataAPIConfiguration) config completionHandler: (HHNetworkTaskCompletionHander) completionHandler HHNetworkClient sharedInstance] dataTaskWithUrlPath:config.urlPath useHttps:config.useHttps {return "requestType:config.requestType params:config.requestParameters header:config.requestHeader completionHandler:^ (NSURLResponse *response, ID responseObject, NSError *error) {completionHandler? CompletionHandler ([self formatError:error] responseObject): Nil;}];}

HHAPIManager external data request and cancel the interface, the internal call HHNetworkClient for the actual request operation

1 agreement or configuration object
HHAPIManager interface, we do not provide as many parameters as before, but a combination of multiple parameters as a configuration object, the following to say why do:

  • Why the interface mode of multiple parameters is not good? A call to
    APP in the API are usually hundreds or even thousands, if one day need to have an additional parameter in molded all API, the change of foot, so the male female programmer programmer silence, tears for
    . Example: APP1.0 has been on the line, the 1.1 version director suddenly requirements plus to cache data request operation request without cache, if it is in the form of general interface parameters is written like this:
/ / the old interface (NSNumber *) dispatchTaskWithUrlPath: (* NSString) urlPath useHttps: (BOOL) useHttps method: (NSString * method) params: (NSDictionary * params) header: (NSDictionary * header); / / new interface (NSNumber *) dispatchTaskWithUrlPath: (* NSString) urlPath useHttps: (BOOL) useHttps method: (NSString *) method params: (NSDictionary * params) header: (NSDictionary *) header shouldCache: (BOOL) shouldCache;

Then the old interface original all call the new interface of shouldCache default NO, do not need to cache API without change, and need a cache of API had to be changed to call the new interface and shouldCache YES.
that can temporarily solve the problem, the workload will be smaller, and after two days the director came and said, why not to API also, we distinguish the cache time? There is a new demand. Hehe!

  • Use agreement to enhance the expansion
@protocol HHAPIManager < NSObject> useHttps; @required - (BOOL) - (NSString * urlPath); - (NSDictionary) - (OTSNetworkRequestType) parameters; requestType; checkParametersIsValid; @optional - (BOOL) - (NSTimeInterval) - cacheValidTimeInterval (NSArray< OTSUploadFile; *> uploadContents; @end *)
@interface HHAPIManager: NSObject< HHAPIManager>; slightly (NSNumber *) dispatchTaskWithCompletionHandler: (OTSNetworkTaskCompletionHander) completionHandler; * @end

In fact, the original design is HHAPIManager agreement, abide by the agreement, internal to the default parameters, dispatchTaskWithCompletionHandler: will go one by one to obtain these parameters, each sub class to achieve their own custom parts, so even after any expansion, only need to add a method in the base class to the agreement which the default value, need to subclass API rewrite it.

  • Replace protocol for configuration object
- (NSURLSessionDataTask *) dataTaskWithConfiguration: (* HHDataAPIConfiguration) config completionHandler: (HHNetworkTaskCompletionHander) completionHandler; - (NSNumber *) dispatchDataTaskWithConfiguration: (* HHDataAPIConfiguration) config completionHandler: (HHNetworkTaskCompletionHander) completionHandler; - (NSNumber *) dispatchUploadTaskWithConfiguration: (* HHUploadAPIConfiguration) config progressHandler: (HHNetworkTaskProgressHandler) progressHandler completionHandler: (HHNetworkTaskCompletionHander) completionHandler;

Protocol design is actually very good, is what I want. But the agreement was for classes, which means that each add a API in the future we need to create a subclass of HHAPIManager, it is easy to have hundreds of API class files, maintenance is very troublesome, looking up a lot of trouble (above is colleagues asked replacement protocol for agreement, but they still support me. So many people) will replace the protocol configuration object, then API module function, each module is a class file give a API interface, a API within each appropriate configuration object, so only need ten class files.
short, taking into account the configuration object not only can realize the design of single API single class, but also can satisfy the needs of colleagues, the protocol was changed into the configuration object
. In addition, all block parameters are not written In the configuration of the object, but directly at the interface statement, look awkward to write a convenient (block do the parameters and do the attributes which write simple we all understand)

2 simple request results above mentioned simple
buffer cache, in fact, we are not doing the cache, because our HTTP API now basically obsolete, is full of TCP, but the TCP cache is another story. But it is simple to achieve:

#define HHCacheManager [HHNetworkCacheManager sharedManager] @interface HHNetworkCache NSObject (instancetype) + cacheWithData: (ID + data); (instancetype) cacheWithData: (ID) data validTimeInterval: (NSUInteger) - (ID) interterval; data; isValid; @end - (BOOL) @interface HHNetworkCacheManager: NSObject + sharedManager; (instancetype) - (void) removeObejectForKey: (ID) key; - (void) setObjcet: (HHNetworkCache *) object forKey: (ID) key (HHNetworkCache * objcetForKey:); - (ID) key; @end
#define ValidTimeInterval 60 @implementation + HHNetworkCache (instancetype) cacheWithData: (ID) data [self cacheWithData:data {return validTimeInterval:ValidTimeInterval];} + (instancetype) cacheWithData: (ID) data validTimeInterval: (NSUInteger) interterval *cache new] {HHNetworkCache = [HHNetworkCache; = data; cache.cacheTime = [[NSDate date] cache.validTimeInterval = timeIntervalSince1970]; interterval > interterval ValidTimeInterval; return: 0? Cache - (BOOL);} isValid {if {return ( [[NSDate date] timeIntervalSince1970] self.cacheTime < self.validTimeInterval return NO;};} @end #pragma mark - HHNetworkCacheManager @interface HHNetworkCacheManager (@p) Roperty (strong, nonatomic) NSCache *cache; @end @implementation HHNetworkCacheManager (instancetype) + sharedManager HHNetworkCacheManager static {static *sharedManager; dispatch_once_t onceToken; dispatch_once (& onceToken, sharedManager = [[super ^{allocWithZone:NULL] init]; [sharedManager configuration]; return sharedManager;}}); + (instancetype) allocWithZone: (struct * _NSZone) zone {return [self sharedManager] - (void);} configuration {self.cache [NSCache = new]; self.cache.totalCostLimit = 1024 * 1024 * 20}; #pragma mark - Interface - (void) setObjcet: (HHNetworkCache *) object forKey: (ID) key setObject:object {[self.cache forKey:key];} - (void) removeObejectForKey: (ID Key {[self.cache removeObjectForKey:key];} (HHNetworkCache *) objcetForKey: (ID) key {return objectForKey:key];} @end
- (NSNumber *) dispatchDataTaskWithConfiguration: (* HHDataAPIConfiguration) config completionHandler: (HHNetworkTaskCompletionHander) completionHandler{NSString *cacheKey; if (config.cacheValidTimeInterval > 0) {NSMutableString * mString = [NSMutableString stringWithString:config.urlPath]; NSMutableArray *requestParameterKeys = [config.requestParameters.allKeys mutableCopy]; if (requestParameterKeys.count > 1) {[requestParameterKeys sortedArrayUsingComparator:^ NSComparisonResult (NSString * _Nonnull obj1 NSString * _Nonnull obj2) {return [obj1 compare:obj2]}]; [requestParameterKeys enumerateObjectsUsingBlock:^;} (NSString * _Nonnull key, NSUInt Eger IDX, BOOL * _Nonnull stop) {[mString appendFormat:@ &%@=%@, key, config.requestParameters[key]]; cacheKey =}]; [self md5WithString:[mString copy]]; HHNetworkCache *cache = [HHCacheManager objcetForKey:cacheKey]; if (! Cache.isValid) {[HHCacheManager} else {removeObejectForKey:cacheKey]; completionHandler? CompletionHandler (nil, Nil; return @-1;}} NSMutableArray *taskIdentifier = [NSMutableArray arrayWithObject:@-1]; taskIdentifier[0] = [[HHNetworkClient sharedInstance] dispatchTaskWithUrlPath:config.urlPath useHttps:config.useHttps requestType:config.requestType params:config.requestParameters header:c Onfig.requestHeader completionHandler:^ (NSURLResponse *response, ID responseObject, NSError *error) {if (error & & config.cacheValidTimeInterval! > 0) {HHNetworkCache *cache = [HHNetworkCache cacheWithData:responseObject validTimeInterval:config.cacheValidTimeInterval]; [HHCacheManager setObjcet:cache forKey:cacheKey];} [self.loadingTaskIdentifies removeObject:taskIdentifier.firstObject]; completionHandler? CompletionHandler ([self formatError:error] responseObject): Nil;}]; [self.loadingTaskIdentifies addObject:taskIdentifier.firstObject]; return taskIdentifier.firstObject;

A simple definition of a HHCache object storage, data cache, cache cache time, aging, and internal HHNetworkCacheManager singleton NSCache memory cache object, because the NSCache comes with thread safety effects, chain are not.
in the task before the launch of our check whether there is any available cache cache is available directly back, did not go after the success of the mission, the network data can be my request.

network after the completion of the task 3 request results back to the data in what form returned to the caller, are divided into two kinds: the success of the mission and task failure. Here we define a task of success and failure, said network request was successful and brought back by the data, said the failure did not get into the available data of
. For example: obtain a list of topics, users want to see rows of color picture, if you call API to get a pile of data that the user is a failure. So don’t get data may be a network error, or network problems not just the user no concern about any topic. It shows a network error corresponding prompt or recommended topic prompt.

The success of the mission is very simple, direct corresponding JSON analytic normal return on the line, if a XXXAPI has special requirements that a new XXXAPIConfig inherits the APIConfig base class, add a property or method in which describe what you have special needs, XXXAPI is responsible for a good return on the line format (a API or so a kind of good, clean).

The task fails a little trouble, I hope I can return any API friendly error, specifically, if there is an error, then the error code returned to the caller of the error.code must be readable enumeration instead of 301 like to document comparison (must), error.domain is usually the error prompt (optional), which requires the programmer to write each API are well defined error enumeration (so a API or a kind of good, clean) tips and the corresponding error is probably like this:

//HHNetworkTaskError.h general typedef enum error: NSUInteger {HHNetworkTaskErrorTimeOut = 101, HHNetworkTaskErrorCannotConnectedToInternet = 102, HHNetworkTaskErrorCanceled = 103, HHNetworkTaskErrorDefault = 104, HHNetworkTaskErrorNoData = 105, HHNetworkTaskErrorNoMoreData = 106} HHNetworkTaskError; static NSError *HHError (NSString *domain, int code return [NSError errorWithDomain:domain code:code userInfo:nil]) {static}; NSString *HHNoDataErrorNotice = @ "here is what is not." static; NSString *HHNetworkErrorNotice = @ "current network, please check your network settings." static NSString; *HHTimeoutErrorNotice = @ "request timeout ~"; static NSString *HHDefaultErrorNotice = "@ request failed ~" static NSString * HHNoMo; ReDataErrorNotice = @ no more ~";
- (NSError) formatError: (NSError * error) {if (error! = Nil) {switch (error.code case) {NSURLErrorCancelled: {error = HHError (HHDefaultErrorNotice, HHNetworkTaskErrorCanceled);} case {error = break; NSURLErrorTimedOut: HHError (HHTimeoutErrorNotice, HHNetworkTaskErrorTimeOut);} break case NSURLErrorCannotFindHost: case NSURLErrorCannotConnectToHost: case; NSURLErrorNotConnectedToInternet: {// product request, all cannot connect to the server is user network error = HHError (HHNetworkErrorNotice, HHNetworkTaskErrorCannotConnectedToInternet);} break default:; {error = HHError (HHNoDataErrorNotice, HHNetworkTaskErrorDefault);} break;}} return error;}

Enumeration and error prompt to define general in a.H, after a new general description here added, easy to manage. The HHAPIManager base class will first format some general errors, then each sub class defines its own unique error enumeration (not universal description and conflict) and the error description, like this:

//HHTopicAPIManager.h typedef enum: NSUInteger {HHUserInfoTaskErrorNotExistUserId = 1001 / / HHUserInfoTaskError1 / / user does not exist, blind write, meaning to go HHUserInfoTaskError2 HHUserInfoTaskError typedef: NSUInteger enum}; {HHUserFriendListTaskError0 = 1001, HHUserFriendListTaskError1, HHUserFriendListTaskError2, HHTopicListTaskError};
//HHTopicAPIManager.m - (NSNumber) fetchUserInfoWithUserId: (NSUInteger) userId completionHandler: (HHNetworkTaskCompletionHander) completionHandler *config new] {HHDataAPIConfiguration = [HHDataAPIConfiguration; config.urlPath = @ "fetchUserInfoWithUserIdPath"; config.requestParameters = nil; return [super dispatchDataTaskWithConfiguration:config completionHandler:^ (NSError *error, ID result) {if (error!) {// universal error base has been handled, do their own data the format of the line switch ([result[@ "code" integerValue] case 200: ") {{/ / request data and correct corresponding analytical / / result = [HHUser objectWithKeyValues:resu Lt[@ "data" break ";}; case 301: {error = HHError (@" user does not exist ", HHUserInfoTaskErrorNotExistUserId);} case {error = break; 302: HHError (@" XXX error ", HHUserInfoTaskError1);} case {error = break; 303: HHError (@" YYY error ", HHUserInfoTaskError2); break default:break;}}}; completionHandler? CompletionHandler (error, result): Nil;}];}

Then the caller generally needs only this:

[[HHTopicAPIManager new] fetchUserInfoWithUserId:123 completionHandler:^ (NSError *error, ID result) {error? [self showToastWithText:error.domain]: [self reloadTableViewWithNames:result];}];

Of course, the situation can only be so complex, a little more code, but there is no trouble to read enumeration:

[[HHTopicAPIManager new] fetchUserInfoWithUserId:123 completionHandler:^ (NSError *error, ID result) {error? [self showErrorViewWithError:error]: [self reloadTableViewWithNames:result];}]; - (void) showErrorViewWithError: (NSError * error) {switch (error.code) {// if their switch case HHNetworkTaskErrorTimeOut: complex display timeout error page} {/ / break request; case HHNetworkTaskErrorCannotConnectedToInternet: {/ / display network error page case {HHUserInfoTaskErrorNotExistUserId:}} / /... Default:break;}} / /...

Here at the request of the two, I am in the callback (error, ID) returns, rather than like AFN are successBlock and failBlock. actually I itself is support for AFN practices, distinguish between success and failure forced two business code appeared in two different parts, it is well, different business processing should be in different function / method. But in the actual development there are many successes and failures will be executed, a typical example is the HUD two block, so I need to add [HUD hide] in the two place, this code will write more annoying. But I was lazy, so the success or failure in a callback return. But
! You should also distinguish different business write two different methods (like the one above, as part of the public to do) is written only once is enough like this:

[hud show:YES]; [[HHTopicAPIManager new] fetchUserInfoWithUserId:123 completionHandler:^ (NSError *error, ID result) {[hud hide:YES]; error? [self showToastWithText:error.domain]: [self reloadTableViewWithNames:result];}];

Moreover, even if you are lazy than me, do not declare the two methods that should also be written in a shorter logic in the front, longer in the back, easy to read, like this:

(if! Error) {...} else {short... Short switch (error.code) {// if their switch case HHNetworkTaskErrorTimeOut: complex display timeout error page} {/ / break request; case HHNetworkTaskErrorCannotConnectedToInternet: {/ / display network error page case} HHUserInfoTaskErrorNotExistUserId: {} / / / /... Long length default:break;}}}...

4 two gadgets to this
article basically the network layer of the said that almost, you can according to their own needs to change change can be used, finally introduces the gadgets and the next two will end:

  • HHNetworkTaskGroup
@protocol HHNetworkTask < NSObject> cancel; - (void) - (void) resume; @end @interface HHNetworkTaskGroup: NSObject - (void) addTaskWithMessgeType: (NSInteger) type message: (ID) message completionHandler: (HHNetworkTaskCompletionHander) - (void) completionHandler; addTask: (id< HHNetworkTask> task; (void) - (-) cancel; void dispatchWithNotifHandler: (void) (^) (void)) notifHandler @end;
@interface (HHNetworkTaskGroup) @property (copy, nonatomic) void (^notifHandler) (void); @property (assign, nonatomic) NSInteger signal @property (strong, nonatomic); NSMutableSet *tasks; @property (strong, nonatomic) dispatch_semaphore_t lock @property (strong, nonatomic); ID keeper; @end @implementation HHNetworkTaskGroup (void) addTaskWithMessgeType: (HHSocketMessageType //- type) message: (PBGeneratedMessage *) message completionHandler: (HHNetworkCompletionHandler completionHandler) {/ / / / *task = HHSocketTask [[HHSocketManager sharedManager] taskWithMessgeType:type message:message completionHandler:completionHandler]; [self / addTask:task]; //} - (void) addTask: (id< HHNetworkTask> task {if ([task respondsToSelecto) R:@selector (cancel)] & & [task respondsToSelector:@selector (resume)] & & containsObject:task]) {[self.tasks [self.tasks! AddObject:task]; [(ID) task addObserver:self forKeyPath:NSStringFromSelector (@selector (state)) options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];}} - (void) dispatchWithNotifHandler: (void (^) (void)) {if notifHandler (self.tasks.count = = 0) {dispatch_async (dispatch_get_main_queue), notifHandler (notifHandler) (^{?: Nil; return; self.lock;})} (1) = dispatch_semaphore_create; self.keeper = self; self.signal = self.tasks.count; self.notifHandler = notifHandler; for (id< HHNetworkTask> task in; self.tasks.allObjects) {[task resume];}} - {for (void) cancel (id< HHNetworkTask> task in self.tasks.allObjects (if) {[(ID) task state] < NSURLSessionTaskStateCanceling) {[(ID) task removeObserver:self forKeyPath:NSStringFromSelector (@selector (state))]}}; [task cancel]; [self.tasks removeAllObjects]; self.keeper = nil;} #pragma mark - KVO - (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (ID) object change: (NSDictionary< NSKeyValueChangeKey, id> change (void * *) context: context if ([keyPath) {isEqualToString:NSStringFromSelector (@selector (state)])) {NSURLSessionTaskState oldState = [change[NSKeyValueC HangeOldKey] integerValue]; NSURLSessionTaskState newState = [change[NSKeyValueChangeNewKey] integerValue]; if (oldState! = newState & & newState > [object removeObserver:self forKeyPath: = NSURLSessionTaskStateCanceling) {NSStringFromSelector (@selector (state)); dispatch_semaphore_wait (self.lock, DISPATCH_TIME_FOREVER); self.signal--; dispatch_semaphore_signal (self.lock); if (self.signal = = 0 {dispatch_after (dispatch_time (DISPATCH_TIME_NOW), (int64_t) (0.1 * NSEC_PER_SEC)), dispatch_get_main_queue (), self.notifHandler (self.notifHandler) ^{?: Nil; [self.tasks removeAllObjects]; Self.keeper = nil;}}}}}); #pragma - Getter - mark (NSMutableSet * tasks) {if (_tasks! = [NSMutableSet) {_tasks set]}}; return _tasks; @end

Look at the name should know that this is almost the same thing as dispatch_group_notif, but the object is not distributed dispatch_block_t but id< HHNetworkTask> code is very simple, talk about the idea on the line

  • Most of the API with Block keeper
    system has a feature that only need to generate does not need to hold, do not have to worry about Block to hold our object caused by circular references, such as dispatch_async, dataTaskWithURL:completionHandler: and so on, in fact, the specific implementation is the first to get rid of the circular reference circular references, such as dispatch_async queue and block circular references so in the block during the execution of both sides are not released, then wait until after the completion of the execution of block nil queue.block is set to get rid of circular references, block did not, it captured queue and other objects can count -1, it can release the normal. Code inside the keeper is to create a circular reference.
  • Signal tasks and
    signal is actually tasks.count, why don’t we directly in the task directly after tasks.remove and then determine tasks.count = = 0 but indirectly to a signal to do this? The reason is simple:
    cannot change the container object in forin process. When we forin task task is a distributed, asynchronous execution that may be in the task implementation of the completion of time triggered KVO our forin is still at remove crash. will directly traverse, if not forin, but with while or for (;;). So it will drain to declare a signal count. The addition of addObserve and removeObserve must appear in pairs, the control is good.
  • Dispatch_after
    in all tasks completed and did not execute Notif (), but immediately wait for 0 seconds and then execute Notif (), this is because the task.state settings will execute before task.completionHandler, so we need to wait for confirmation, after the implementation of the completionHandler in our Notif ().
  • How to use the HHNetworkTaskGroup *group = [HHNetworkTaskGroup new]; HHTopicAPIManager *manager = [HHTopicAPIManager new]; for (int i = 1; I 6; < i++) {NSURLSessionDataTask *task = [manager topicListDataTaskWithPage:i pageSize:20 completionHandler:^ (NSError *error, ID result) {/ /… CompletionHandler… I}]; [group addTask: (ID) task] [group dispatchWithNotifHandler:^{//notifHandler;}}]; again, absolutely should not directly call HHNetworkClient or HHAPIManger dataTaskxxx… The general interface to generate task, should belong to the task in the API exposed interface to generate task, simply do not cross layer access. Parameters of each API and even the signature rules are not the same, the caller should only provide API to generate task The corresponding parameters and should not need to know the specific assembly logic of these parameters
  • HHNetworkAPIRecorder
@interface HHNetworkAPIRecorder: NSObject @property (strong, nonatomic) ID rawValue @property (assign, nonatomic); int pageSize; @property (assign, nonatomic) int currentPage @property (assign, nonatomic); NSInteger itemsCount; @property (assign, nonatomic) - (void) NSInteger lastRequestTime; reset; - (BOOL) - NSInteger (hasMoreData; maxPage; @end)

Daily requests in many interface involves paging, however there is no doubt that the paging logic in each page is as like as two peas, but you need to keep a currentPage call each page and then call logic are written once, in fact, directly in the internal implementation of the API a paging logic, and then exposed the first page and the next page interface you don’t have to declare currentPage and repeat these boring logic like this:

//XXXAPI.h - (NSNumber) refreshTopicListWithCompletionHandler: (HHNetworkTaskCompletionHander) completionHandler; / / the first page - (NSNumber *) loadmoreTopicListWithCompletionHandler: completionHandler (HHNetworkTaskCompletionHander); / / the current page next page - (NSNumber) fetchTopicListWithPage: (NSInteger) page completionHandler: completionHandler (HHNetworkTaskCompletionHander); / / the specified page (general external use, see exposure)
//XXXAPI.m - (NSNumber) refreshTopicListWithCompletionHandler: (HHNetworkTaskCompletionHander) completionHandler return [self fetchTopicListWithPage:self.topicListAPIRecorder.currentPage {[self.topicListAPIRecorder reset]; completionHandler: completionHandler];} - (NSNumber *) loadmoreTopicListWithCompletionHandler: (HHNetworkTaskCompletionHander) completionHandler return [self fetchTopicListWithPage:self.topicListAPIRecorder.currentPage {self.topicListAPIRecorder.currentPage++; completionHandler:completionHandler];}
//SomeViewController self.topicAPIManager [HHTopicAPIManager = new]; self.tableView.header = [MJRefreshNormalHeader... HeaderWithRefreshingBlock:^{// refreshTopicListWithCompletionHandler:^ [weakSelf.topicAPIManager (NSError *error pull-down refresh, ID result) {...}];}]; self.tableView.footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{// [weakSelf.topicAPIManager loadmoreTopicListWithCompletionHandler:^ (NSError load *error, ID result) {...}]}];


HHURLRequestGenerator: network request generator, request header in common, cookie in this setting.
HHNetworkClient: network requests for payment, it will record each service request, and switch when necessary. The caller request server distributed
HHAPIManager: network, the corresponding data format here as a result of the request for return API to the caller, provide request module development support, and provide a reasonable Task for TaskGroup distribution.

Write in the end

Originally wanted a copious and fluent writing speech touched your mobile phone, come over a push, a look to know that today is Valentine’s day, I wish you a happy holiday……

This article with the demo address