View
223
Download
0
Category
Preview:
Citation preview
Protocol-Oriented Networking@mostafa_amer
Starting Point#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name
completionHandler:(void(^)(id result, NSError *error))handler; @end
Starting Point#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name
completionHandler:(void(^)(id result, NSError *error))handler; @end
• Client has too many responsibilities
Starting Point#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name
completionHandler:(void(^)(id result, NSError *error))handler; @end
• Client has too many responsibilities• Client is tightly coupled to the network library
Starting Point#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name
completionHandler:(void(^)(id result, NSError *error))handler; @end
• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test
Starting Point#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name
completionHandler:(void(^)(id result, NSError *error))handler; @end
• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden
Decouplingprotocol NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String: Any]?, handler: (Data?, Error?) -> ()) }
Decouplingclass NetworkService: AFHTTPSessionManager, NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) {
} }
Decouplingclass NetworkService: AFHTTPSessionManager, NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { switch method { case .GET: get(path, parameters: parameters, success: {_, result in handler(result, nil) }, failure: {_, error in handler(nil, error) } ) } } }
Hide Implementation
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient; - (void)fetchListWithCompletionHandler:(void(^)(id result, NSError *error))handler; @end
#import <AFNetworking/AFNetworking.h>
Hide Implementation
@interface GHAPIClient : NSObject
+ (instancetype)sharedClient; - (void)fetchListWithCompletionHandler:(void(^)(id result, NSError *error))handler; @end
@interface GHAPIClient()
@end
@implementation GHAPIClient
- (instancetype)initWithBaseURL:(NSURL *)baseURL { self = [super init]; if(self) {
} return self; }
+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
_instance = [[GHAPIClient alloc] init]; }); } return _instance; }
Refactoring Client
Refactoring Client@interface GHAPIClient() @property (nonatomic, strong) id<NetworkServiceType> network; @end
@implementation GHAPIClient
- (instancetype)initWithBaseURL:(NSURL *)baseURL { self = [super init]; if(self) {
} return self; }
+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
_instance = [[GHAPIClient alloc] init]; }); } return _instance; }
Refactoring Client@interface GHAPIClient() @property (nonatomic, strong) id<NetworkServiceType> network; @end
@implementation GHAPIClient
- (instancetype)initWithNetworkService:(id<NetworkServiceType>)service { self = [super init]; if(self) { _network = service; } return self; }
+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
_instance = [[GHAPIClient alloc] init]; }); } return _instance; }
Refactoring Client@interface GHAPIClient() @property (nonatomic, strong) id<NetworkServiceType> network; @end
@implementation GHAPIClient
- (instancetype)initWithNetworkService:(id<NetworkServiceType>)service { self = [super init]; if(self) { _network = service; } return self; }
+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSURL *baseURL = [NSURL URLWithString:@"https://api.github.com"]; NetworkService *service = [[NetworkService alloc] initWithBaseURL:baseURL]; _instance = [[GHAPIClient alloc] initWithNetworkService:service]; }); } return _instance; }
Testing Clientclass NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }
class APIClientSpecs: QuickSpec { override func spec() { var mock: NetworkMock! var sut: GHAPIClient! beforeEach { mock = NetworkMock() sut = GHAPIClient(networkService: mock) }
} }
Testing Clientclass NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }
Testing Clientclass NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }
class APIClientSpecs: QuickSpec { override func spec() { var mock: NetworkMock! var sut: GHAPIClient! beforeEach { mock = NetworkMock() sut = GHAPIClient(networkService: mock) } it("handle error") { mock.data = // Data from JSON file sut.fetchGitHubUser(loginName: "mosamer") { (_, error) in expect(error).notTo(beNil()) } } } }
Client Again
- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {
[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }
Client Again
- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {
[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }
• Client still has too many responsibilities
Client Again
- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {
[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }
• Client still has too many responsibilities• Client lacks general handlers
More Protocols
More Protocols
protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get }
}
More Protocolsprotocol Response {} protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get }
associatedtype ResponseType: Response
}
More Protocolsprotocol Response {} protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get }
associatedtype ResponseType: Response func parse(response: Data) -> (ResponseType?, Error?) }
Define Endpointsstruct GitHubUserEndpoint: Resource { private let name: String init(loginName: String) { name = loginName } var path: String { return "users/\(name)" } var method: HTTPMethod { return .GET } var parameters: [String : Any]? { return nil } func parse(response: Data) -> ( GitHubUser?, Error?) {
// Parse JSON -> data model object } }struct GitHubUser: Response { // Define data model
}
Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {
var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") }
} }
Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {
var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" }
} }
Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {
var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" } it("GET method") { expect(sut.method) == HTTPMethod.GET }
} }
Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {
var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" } it("GET method") { expect(sut.method) == HTTPMethod.GET } it("without parameters") { expect(sut.parameters).to(beNil()) }
} }
Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {
var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" } it("GET method") { expect(sut.method) == HTTPMethod.GET } it("without parameters") { expect(sut.parameters).to(beNil()) } it("parse response") { let userJSON = Data() /* Data from JSON file */ let (user, _) = sut.parse(response: userJSON) /* Check user properties are fetched correctly */ } } }
Client One More Time
- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {
[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }
Client One More Time
- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {
[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }
func user(loginName: String, completionhandler: (GitHubUser?, Error?) -> ()) { let endpoint = GitHubUserEndpoint(loginName: loginName) request(endpoint, handler: completionhandler) }
Client - General Handlers
private func request<R: Resource>(_ endpoint: R, handler:(R.ResponseType?, Error?) -> ()) {
} } }
extension GHAPIClient { func user(loginName: String, completionhandler: (GitHubUser?, Error?) -> ()) { let endpoint = GitHubUserEndpoint(loginName: loginName) request(endpoint, handler: completionhandler) }
Client - General Handlers
private func request<R: Resource>(_ endpoint: R, handler:(R.ResponseType?, Error?) -> ()) {
network.requestEndpoint(path: endpoint.path, method: endpoint.method, parameters: endpoint.parameters) { (data, error) in if let _ = error { handler(nil, error) return } guard let data = data else { handler(nil, nil) return } let (result, error) = endpoint.parse(response: data) handler(result, error) } } }
extension GHAPIClient { func user(loginName: String, completionhandler: (GitHubUser?, Error?) -> ()) { let endpoint = GitHubUserEndpoint(loginName: loginName) request(endpoint, handler: completionhandler) }
Summary
Summary• Client has too many responsibilities
Summary• Client has too many responsibilities
Summary• Client has too many responsibilities• Client is tightly coupled to the network library
Summary• Client has too many responsibilities• Client is tightly coupled to the network library
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities• Client lacks general handlers
Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities• Client lacks general handlers
@mostafa_amer
Questions?
Thank You@mostafa_amer
Questions?
Recommended