/XMNetworking

A lightweight but powerful network library with simplified and expressive syntax based on AFNetworking.

Primary LanguageObjective-CMIT LicenseMIT

XMNetworking

A lightweight but powerful network library with simplified and expressive syntax based on AFNetworking.

The prefix XM is the abbreviation of our team Xcode-Men. 中文文档

Platform Language CocoaPods Carthage License

Introduction

As shown in the picture above, the XMNetworking is designed with centralization thought, all the XMRequest objects are launched and managed by XMCenter, and you could modify the callback dispatch queue and general information such as server url, header and parameter for all request through XMCenter, as well as provide a custom processing block for response data, in which you could deal with model transformation, business error code checking, network cache and so on. Futhermore, in order to switch to other network library easily or implement the underlying logic by ourself in the future, we add a XMEngine layer to insulate the dependence of third party network library like AFNetworking.

Features

  • Simply and easily to use.
  • Powerful functions for all network reqeust usage scenarios (Normal/Upload/Download).
  • Designed for RESTful Server API, and providing various serialization type.
  • Supporting Batch and Chain requests.
  • Cancelable for running request and auto retrying for fail request.
  • Global configuration for genneral info and custom respnose processing block.
  • Network reachability checking and security policy based on AFNetworking.

Requirements

  • iOS 7.0 or later
  • Xcode 7.3 or later

Installation

CocoaPods

Add the following line to your Podfile, and then run pod install or pod update.

pod 'XMNetworking'

NOTE: The XMNetworking has contained AFNetworking source code with version 3.1.0, and you should NOT add pod AFNetworking to your Podfile to avoid conflict.

Carthage (Supported only iOS 8+)

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage

To integrate XMNetworking into your Xcode project using Carthage, specify it in your Cartfile:

github "kangzubin/XMNetworking"

Run carthage update --platform ios to build the framework and drag the built XMNetworking.framework into your Xcode project.

NOTE: The XMNetworking.framework has contained AFNetworking source code with version 3.1.0, and you should NOT add AFNetworking.framework to your Xcode project to avoid conflict.

Manually

Download all the files in the XMNetworking subdirectory, then add the source files to your Xcode project.

Getting Started

Import Headers in Your Source Files

  • Installed by CocoaPods or Carthage:
#import <XMNetworking/XMNetworking.h>
  • Installed Manually:
#import "XMNetworking.h"

Network Configuration

[XMCenter setupConfig:^(XMConfig *config) {
    config.generalServer = @"general server address";
    config.generalHeaders = @{@"general-header": @"general header value"};
    config.generalParameters = @{@"general-parameter": @"general parameter value"};
    config.generalUserInfo = nil;
    config.callbackQueue = dispatch_get_main_queue();
#ifdef DEBUG
    config.consoleLog = YES;
#endif
}];

You could configure the XMCenter througth a XMConfig object by invoking +setupConfig: method, the prorerties to be configured are as following:

  • generalServer: The general server address for XMCenter, if XMRequest.server is nil and the XMRequest.useGeneralServer is YES, this property will be assigned to XMRequest.server.
  • generalParameters: The general parameters for XMCenter, if XMRequest.useGeneralParameters is YES and this property is not empty, it will be appended to XMRequest.parameters.
  • generalHeaders: The general headers for XMCenter, if XMRequest.useGeneralHeaders is YES and this property is not empty, it will be appended to XMRequest.headers.
  • generalUserInfo: The general user info for XMCenter, if XMRequest.userInfo is nil and this property is not nil, it will be assigned to XMRequest.userInfo, and the userInfo might be used to distinguish requests with same context.
  • callbackQueue: The dispatch queue for request callback blocks. If NULL (default), a private concurrent queue is used.
  • consoleLog: Whether to print the request and response info in console or not, NO by default.

And you could modify the general headers and parameters for XMCenter by following methods:

+ (void)setGeneralHeaderValue:(nullable NSString *)value forField:(NSString *)field;
+ (void)setGeneralParameterValue:(nullable NSString *)value forKey:(NSString *)key;

Normal Request

GET

[XMCenter sendRequest:^(XMRequest *request) {
    request.url = @"http://example.com/v1/foo/bar";
    //request.server = @"http://example.com/v1/";
    //request.api = @"foo/bar";
    request.parameters = @{@"param1": @"value1", @"param2": @"value2"};
    request.headers = @{@"User-Agent": @"Custom User Agent"};
    request.httpMethod = kXMHTTPMethodGET;
} onSuccess:^(id responseObject) {
   NSLog(@"onSuccess: %@", responseObject);
} onFailure:^(NSError *error) {
   NSLog(@"onFailure: %@", error);
} onFinished:^(id responseObject, NSError *error) {
   NSLog(@"onFinished");
}];

NOTE1: The following two usages to set URL for request are both ok, but when the server, api and url are assigned for a request object at the same time, the value of url is used, while the server and api will be ignored.

request.url = @"http://example.com/v1/foo/bar";
// if request.server is `nil`, the general server address of XMCenter will be used.
request.server = @"http://example.com/v1/";
request.api = @"foo/bar";

NOTE2: The callback blocks (success/failure/finished/progress) are optional for a request object and there are several methods with different block arguments in XMCenter to send requests. The success/faillure/finished blocks are called on callbackQueue of XMCenter, while the progress block is called on the session queue, not the callbackQueue of XMCenter !!!

POST

[XMCenter sendRequest:^(XMRequest *request) {
    //request.server = @"http://example.com/v1/";
    request.api = @"foo/bar";
    request.parameters = @{@"param1": @"value1", @"param2": @"value2"};
    request.httpMethod = kXMHTTPMethodPOST; // optional, `POST` by default.
    request.requestType = kXMRequestNormal; // optional, `Normal` by default.
} onSuccess:^(id responseObject) {
   NSLog(@"onSuccess: %@", responseObject);
} onFailure:^(NSError *error) {
   NSLog(@"onFailure: %@", error);
}];

Other HTTP Methods

Requests with other HTTP methods such as HEAD, DELETE, PUT, PATCH, ... are also supporting, and the usage is similar to the above, we won't repeat it here。

See the comments on XMConst, XMRequest and XMCenter for more details.

Upload Request

// `NSData` form data.
UIImage *image = [UIImage imageNamed:@"testImage"];
NSData *fileData1 = UIImageJPEGRepresentation(image, 1.0);
// `NSURL` form data.
NSString *path = [NSHomeDirectory() stringByAppendingString:@"/Documents/testImage.png"];
NSURL *fileURL2 = [NSURL fileURLWithPath:path isDirectory:NO];

[XMCenter sendRequest:^(XMRequest *request) {
    request.server = @"http://example.com/v1/";
    request.api = @"foo/bar";
    request.requestType = kXMRequestUpload;
    [request addFormDataWithName:@"image[]" fileName:@"temp.jpg" mimeType:@"image/jpeg" fileData:fileData1];
    [request addFormDataWithName:@"image[]" fileURL:fileURL2];
    // see `XMUploadFormData` for more details.
} onProgress:^(NSProgress *progress) {
    // the progress block is running on the session queue.
    if (progress) {
        NSLog(@"onProgress: %f", progress.fractionCompleted);
    }
} onSuccess:^(id responseObject) {
    NSLog(@"onSuccess: %@", responseObject);
} onFailure:^(NSError *error) {
    NSLog(@"onFailure: %@", error);
} onFinished:^(id responseObject, NSError *error) {
    NSLog(@"onFinished");
}];

Download Request

[XMCenter sendRequest:^(XMRequest *request) {
    request.url = @"http://example.com/v1/testDownFile.zip";
    request.downloadSavePath = [NSHomeDirectory() stringByAppendingString:@"/Documents/"];
    request.requestType = kXMRequestDownload;
} onProgress:^(NSProgress *progress) {
    // the progress block is running on the session queue.
    if (progress) {
        NSLog(@"onProgress: %f", progress.fractionCompleted);
    }
} onSuccess:^(id responseObject) {
    NSLog(@"onSuccess: %@", responseObject);
} onFailure:^(NSError *error) {
    NSLog(@"onFailure: %@", error);
}];

Serialization

There are two properties named requestSerializerType and responseSerializerType in XMRequest to set the serialization type for request parameters and response data respectively.

The enumeration XMRequestSerializerType and XMResponseSerializerType are defined as following:

typedef NS_ENUM(NSInteger, XMRequestSerializerType) {
    kXMRequestSerializerRAW     = 0, // default
    kXMRequestSerializerJSON    = 1,
    kXMRequestSerializerPlist   = 2,
};
typedef NS_ENUM(NSInteger, XMResponseSerializerType) {
    kXMResponseSerializerRAW    = 0,
    kXMResponseSerializerJSON   = 1, // default
    kXMResponseSerializerPlist  = 2,
    kXMResponseSerializerXML    = 3,
};

See also AFURLRequestSerialization.h and AFURLResponseSerialization.h .

Custom Processing Logic for Response Data

Normally, the success block is called when the network reqeust finished successfully, and the failure block is called when error occurred.

Nonetheless, it's more likely that you might need to validate the response data or business error code agreed upon with server develorers even if the request is successfully finished.

Now you could invoke the [XMCenter setResponseProcessBlock:...] method to set a custom processing block for response data, the block is called before success block, and if the passed in error argument is assigned, the failure block will be called instead.

[XMCenter setResponseProcessBlock:^(XMRequest *request, id responseObject, NSError *__autoreleasing *error) {
    // Do the custom response data processing logic by yourself.
}];

Batch Requests

Send batch requests concurrently, the all reqeusts are independent to each other, and the success block is called until all reqeusts finished, while the failure block is called once error occurred.

[XMCenter sendBatchRequest:^(XMBatchRequest *batchRequest) {
    XMRequest *request1 = [XMRequest request];
    request1.url = @"server url 1";
    // set other properties for request1
        
    XMRequest *request2 = [XMRequest request];
    request2.url = @"server url 2";
    // set other properties for request2
        
    [batchRequest.requestArray addObject:request1];
    [batchRequest.requestArray addObject:request2];
} onSuccess:^(NSArray<id> *responseObjects) {
    NSLog(@"onSuccess: %@", responseObjects);
} onFailure:^(NSArray<id> *errors) {
    NSLog(@"onFailure: %@", errors);
} onFinished:^(NSArray<id> *responseObjects, NSArray<id> *errors) {
    NSLog(@"onFinished");
}];

The [XMCenter sendBatchRequest:...] method return the new running XMBatchRequest object, and the object might be used to cancel the batch requests by invoking its -cancelWithBlock: method.

Chain Requests

Send chain requests one by one, the next reqeust relied on the response result of the previous reqeust, and the success block is called until all reqeusts finished, while the failure block is called once error occurred. The bool value sendNext is used to confirm whether to start next reqeust.

[XMCenter sendChainRequest:^(XMChainRequest *chainRequest) {
    [[[[chainRequest onFirst:^(XMRequest *request) {
        request.url = @"server url 1";
        // set other properties for request
    }] onNext:^(XMRequest *request, id responseObject, BOOL *sendNext) {
        NSDictionary *params = responseObject;
        if (params.count > 0) {
            request.url = @"server url 2";
            request.parameters = params;
        } else {
            *sendNext = NO;
        }
    }] onNext:^(XMRequest *request, id responseObject, BOOL *sendNext) {
        request.url = @"server url 3";
        request.parameters = @{@"param1": @"value1", @"param2": @"value2"};
    }] onNext: ...];    
} onSuccess:^(NSArray<id> *responseObjects) {
    NSLog(@"onSuccess: %@", responseObjects);
} onFailure:^(NSArray<id> *errors) {
    NSLog(@"onFailure: %@", errors);
} onFinished:^(NSArray<id> *responseObjects, NSArray<id> *errors) {
    NSLog(@"onFinished");
}];

The [XMCenter sendChainRequest:...] method return the new running XMChainRequest object, and the object might be used to cancel the chain requests by invoking its -cancelWithBlock: method.

Cancel the Running Request

When you invoke [XMCenter sendRequest:...] to send a network reqeust, the method will return a unique identifier for the new running XMRequest object (0 for fail), you could save the identifier value, and then cancel the running request by identifier for your business logic later if need. If a request has already finished, and your still use its identifier to cancel the request, the action will be ignored directly.

// send a request
NSUInteger identifier = [XMCenter sendRequest:^(XMRequest *request) {
    request.server = @"https://kangzubin.cn/";
    request.api = @"test/index.php";
    request.httpMethod = kXMHTTPMethodGET;
    request.timeoutInterval = 10;
    request.retryCount = 1;
} onFailure:^(NSError *error) {
    NSLog(@"onFailure: %@", error);
}];

// your business code
sleep(2);

// cancel the running request by identifier with cancel block
[XMCenter cancelRequest:identifier onCancel:^(XMRequest *request) {
    NSLog(@"onCancel");
}];

NOTE: The canceled request object (if exist) will be passed in argument to the cancel block, and the cancel block is called on current thread who invoked the -cancelRequest: method, not the callbackQueue of XMCenter.

Network Reachability

There are two ways to get the network reachability:

[XMCenter isNetworkReachable]; 
// Return a bool value to comfirm whether network is reachable or not.

or

[[XMEngine sharedEngine] networkReachability]; 
// Return the current network reachablity status, -1 to `Unknown`, 0 to `NotReachable, 1 to `WWAN` and 2 to `WiFi`	

See also AFNetworkReachabilityManager for more details.

SSL Pinning for HTTPS Request

Adding pinned SSL certificates to your app helps prevent man-in-the-middle attacks and other vulnerabilities. Conveniently, the AFSecurityPolicy module could help to evaluate server trust against pinned X.509 certificates and public keys over secure connections.

There is a AFHTTPSessionManager object exposed in XMEngine named sessionManager, and you should firstly modify the securityPolicy mode for sessionManager to take SSL Pinning effective by following code, then add the .cer certificate file or public key to your project.

[XMEngine sharedEngine].sessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];

See also AFSecurityPolicy for more details.

Documents

See XMNetworking Documents Link.

Unit Tests

XMNetworking includes a suite of unit tests within the XMNetworkingDemoTests subdirectory, see test cases in the Tests Target for details.

Architecture

The soure code files for XMNetworking is compact and concise, there are only four core files in the library: The XMConst.h defines some const enums and blocks, and XMRequest, XMCenter, XMEngine are the declaration and implementation for core Class, the architecture of XMNetworking is as follwing:

To Do List

  • Support for resume download.
  • Support for network cache.
  • Test Supporting for tvOS/watchOS/OS X.
  • More powerful response data model transformation.
  • Plugin mechanism to extend the XMNetworking.

Author

Collaborators

License

XMNetworking is released under the MIT license. See LICENSE for details.