Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Protocol-Oriented Networking
@mostafa_amer
Starting Point
#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedC...
Starting Point
#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedC...
Starting Point
#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedC...
Starting Point
#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedC...
Starting Point
#import <AFNetworking/AFNetworking.h>
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedC...
Decoupling
protocol NetworkServiceType {
func requestEndpoint(path: String,
method: HTTPMethod,
parameters: [String: Any]?...
Decoupling
class NetworkService: AFHTTPSessionManager, NetworkServiceType {
func requestEndpoint(path: String, method: HTT...
Decoupling
class NetworkService: AFHTTPSessionManager, NetworkServiceType {
func requestEndpoint(path: String, method: HTT...
Hide Implementation
@interface GHAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient;
- (void)fetchListWithCompl...
Hide Implementation
@interface GHAPIClient : NSObject
+ (instancetype)sharedClient;
- (void)fetchListWithCompletionHandler...
@interface GHAPIClient()
@end
@implementation GHAPIClient
- (instancetype)initWithBaseURL:(NSURL *)baseURL {
self = [super...
Refactoring Client
@interface GHAPIClient()
@property (nonatomic, strong) id<NetworkServiceType> network;
@end
@implementa...
Refactoring Client
@interface GHAPIClient()
@property (nonatomic, strong) id<NetworkServiceType> network;
@end
@implementa...
Refactoring Client
@interface GHAPIClient()
@property (nonatomic, strong) id<NetworkServiceType> network;
@end
@implementa...
Testing Client
class NetworkMock: NetworkServiceType {
var response: Data?
var error: Error?
func requestEndpoint(path: St...
class APIClientSpecs: QuickSpec {
override func spec() {
var mock: NetworkMock!
var sut: GHAPIClient!
beforeEach {
mock = ...
Testing Client
class NetworkMock: NetworkServiceType {
var response: Data?
var error: Error?
func requestEndpoint(path: St...
Client Again
- (void)fetchGitHubUser:(NSString *)name
completionHandler:(void (^)(id, NSError *))handler {
[self.network r...
Client Again
- (void)fetchGitHubUser:(NSString *)name
completionHandler:(void (^)(id, NSError *))handler {
[self.network r...
Client Again
- (void)fetchGitHubUser:(NSString *)name
completionHandler:(void (^)(id, NSError *))handler {
[self.network r...
More Protocols
More Protocols
protocol Resource {
var path: String { get }
var method: HTTPMethod { get }
var parameters: [String: Any]? ...
More Protocols
protocol Response {}
protocol Resource {
var path: String { get }
var method: HTTPMethod { get }
var parame...
More Protocols
protocol Response {}
protocol Resource {
var path: String { get }
var method: HTTPMethod { get }
var parame...
Define Endpoints
struct GitHubUserEndpoint: Resource {
private let name: String
init(loginName: String) {
name = loginName
...
Testing Endpoints
class GitHubUserEndpointSpecs: QuickSpec {
override func spec() {
var sut: GitHubUserEndpoint!
beforeEac...
Testing Endpoints
class GitHubUserEndpointSpecs: QuickSpec {
override func spec() {
var sut: GitHubUserEndpoint!
beforeEac...
Testing Endpoints
class GitHubUserEndpointSpecs: QuickSpec {
override func spec() {
var sut: GitHubUserEndpoint!
beforeEac...
Testing Endpoints
class GitHubUserEndpointSpecs: QuickSpec {
override func spec() {
var sut: GitHubUserEndpoint!
beforeEac...
Testing Endpoints
class GitHubUserEndpointSpecs: QuickSpec {
override func spec() {
var sut: GitHubUserEndpoint!
beforeEac...
Client One MoreTime
- (void)fetchGitHubUser:(NSString *)name
completionHandler:(void (^)(id, NSError *))handler {
[self.ne...
Client One MoreTime
- (void)fetchGitHubUser:(NSString *)name
completionHandler:(void (^)(id, NSError *))handler {
[self.ne...
Client - General Handlers
private func request<R: Resource>(_ endpoint: R,
handler:(R.ResponseType?, Error?) -> ()) {
}
}
...
Client - General Handlers
private func request<R: Resource>(_ endpoint: R,
handler:(R.ResponseType?, Error?) -> ()) {
netw...
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...
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...
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...
@mostafa_amer
Questions?
ThankYou
@mostafa_amer
Questions?
Upcoming SlideShare
Loading in …5
×

Protocol-Oriented Networking

417 views

Published on

Describing how to use Swift protocols to refactor obj-c networking layer to Swift while improving project architecture and test coverage. CocoaHeads-Berlin Sep 16

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

Protocol-Oriented Networking

  1. 1. Protocol-Oriented Networking @mostafa_amer
  2. 2. Starting Point #import <AFNetworking/AFNetworking.h> @interface GHAPIClient : AFHTTPSessionManager + (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name completionHandler:(void(^)(id result, NSError *error))handler; @end
  3. 3. 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
  4. 4. 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
  5. 5. 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
  6. 6. 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
  7. 7. Decoupling protocol NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String: Any]?, handler: (Data?, Error?) -> ()) }
  8. 8. Decoupling class NetworkService: AFHTTPSessionManager, NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { } }
  9. 9. Decoupling class 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) } ) } } }
  10. 10. Hide Implementation @interface GHAPIClient : AFHTTPSessionManager + (instancetype)sharedClient; - (void)fetchListWithCompletionHandler:(void(^)(id result, NSError *error))handler; @end #import <AFNetworking/AFNetworking.h>
  11. 11. Hide Implementation @interface GHAPIClient : NSObject + (instancetype)sharedClient; - (void)fetchListWithCompletionHandler:(void(^)(id result, NSError *error))handler; @end
  12. 12. @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
  13. 13. 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; }
  14. 14. 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; }
  15. 15. 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; }
  16. 16. Testing Client class NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }
  17. 17. class APIClientSpecs: QuickSpec { override func spec() { var mock: NetworkMock! var sut: GHAPIClient! beforeEach { mock = NetworkMock() sut = GHAPIClient(networkService: mock) } } } Testing Client class NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }
  18. 18. Testing Client class 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()) } } } }
  19. 19. 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); }]; }
  20. 20. 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
  21. 21. 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
  22. 22. More Protocols
  23. 23. More Protocols protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get } }
  24. 24. More Protocols protocol Response {} protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get } associatedtype ResponseType: Response }
  25. 25. More Protocols protocol 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?) }
  26. 26. Define Endpoints struct 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 }
  27. 27. Testing Endpoints class GitHubUserEndpointSpecs: QuickSpec { override func spec() { var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } } }
  28. 28. Testing Endpoints class GitHubUserEndpointSpecs: QuickSpec { override func spec() { var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" } } }
  29. 29. Testing Endpoints class 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 } } }
  30. 30. Testing Endpoints class 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()) } } }
  31. 31. Testing Endpoints class 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 */ } } }
  32. 32. Client One MoreTime - (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); }]; }
  33. 33. Client One MoreTime - (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) }
  34. 34. 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) }
  35. 35. 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) }
  36. 36. Summary
  37. 37. Summary • Client has too many responsibilities
  38. 38. Summary • Client has too many responsibilities
  39. 39. Summary • Client has too many responsibilities • Client is tightly coupled to the network library
  40. 40. Summary • Client has too many responsibilities • Client is tightly coupled to the network library
  41. 41. Summary • Client has too many responsibilities • Client is tightly coupled to the network library • Client is hard to test
  42. 42. Summary • Client has too many responsibilities • Client is tightly coupled to the network library • Client is hard to test
  43. 43. Summary • Client has too many responsibilities • Client is tightly coupled to the network library • Client is hard to test • Implementation details is not hidden
  44. 44. Summary • Client has too many responsibilities • Client is tightly coupled to the network library • Client is hard to test • Implementation details is not hidden
  45. 45. 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
  46. 46. 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
  47. 47. 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
  48. 48. 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
  49. 49. @mostafa_amer Questions?
  50. 50. ThankYou @mostafa_amer Questions?

×