/
Moya+ReactiveCocoa.swift
115 lines (95 loc) · 4.34 KB
/
Moya+ReactiveCocoa.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//
// Moya+ReactiveCocoa.swift
// Moya
//
// Created by Ash Furrow on 2014-08-22.
// Copyright (c) 2014 Ash Furrow. All rights reserved.
//
import Foundation
public class MoyaResponse {
public let statusCode: Int
public let data: NSData
public let response: NSURLResponse?
public init(statusCode: Int, data: NSData, response: NSURLResponse?) {
self.statusCode = statusCode
self.data = data
self.response = response
}
}
extension MoyaResponse: Printable, DebugPrintable {
public var description: String {
return "Status Code: \(statusCode), Data Length: \(data.length)"
}
public var debugDescription: String {
return description
}
}
/// Subclass of MoyaProvider that returns RACSignal instances when requests are made. Much better than using completion closures.
public class ReactiveMoyaProvider<T where T: MoyaTarget>: MoyaProvider<T> {
/// Current requests that have not completed or errored yet.
/// Note: Do not access this directly. It is public only for unit-testing purposes (sigh).
public var inflightRequests = Dictionary<Endpoint<T>, RACSignal>()
/// Initializes a reactive provider.
override public init(endpointsClosure: MoyaEndpointsClosure, endpointResolver: MoyaEndpointResolution = MoyaProvider.DefaultEnpointResolution(), stubResponses: Bool = false) {
super.init(endpointsClosure: endpointsClosure, endpointResolver: endpointResolver, stubResponses: stubResponses)
}
/// Designated request-making method.
public func request(token: T, method: Moya.Method, parameters: [String: AnyObject]) -> RACSignal {
let endpoint = self.endpoint(token, method: method, parameters: parameters)
// weak self just for best practices – RACSignal will take care of any retain cycles anyway,
// and we're connecting immediately (below), so self in the block will always be non-nil
return RACSignal.defer { [weak self] () -> RACSignal! in
if let existingSignal = self?.inflightRequests[endpoint] {
return existingSignal
}
let signal = RACSignal.createSignal({ (subscriber) -> RACDisposable! in
self?.request(token, method: method, parameters: parameters) { (data, statusCode, response, error) -> () in
if let error = error {
if let statusCode = statusCode {
subscriber.sendError(NSError(domain: error.domain, code: statusCode, userInfo: error.userInfo))
} else {
subscriber.sendError(error)
}
} else {
if let data = data {
subscriber.sendNext(MoyaResponse(statusCode: statusCode!, data: data, response: response))
}
subscriber.sendCompleted()
}
}
return nil
}).finally({ [weak self] () -> Void in
if let weakSelf = self {
objc_sync_enter(weakSelf)
weakSelf.inflightRequests[endpoint] = nil
objc_sync_exit(weakSelf)
}
}).publish().autoconnect()
if let weakSelf = self {
objc_sync_enter(weakSelf)
weakSelf.inflightRequests[endpoint] = signal
objc_sync_exit(weakSelf)
}
return signal
}
}
public func request(token: T, parameters: [String: AnyObject]) -> RACSignal {
return request(token, method: Moya.DefaultMethod(), parameters: parameters)
}
public func request(token: T, method: Moya.Method) -> RACSignal {
return request(token, method: method, parameters: Moya.DefaultParameters())
}
public func request(token: T) -> RACSignal {
return request(token, method: Moya.DefaultMethod())
}
}
/// Required for making Endpoint conform to Equatable.
public func ==<T>(lhs: Endpoint<T>, rhs: Endpoint<T>) -> Bool {
return lhs.urlRequest.isEqual(rhs.urlRequest)
}
/// Required for using Endpoint as a key type in a Dictionary.
extension Endpoint: Equatable, Hashable {
public var hashValue: Int {
return urlRequest.hash
}
}