Alamofireをコードリーディングして、気になったコードの書き方です。
Kyobashi.swift #1でお話しした内容です。
http://kyobashi-swift.connpass.com/event/23712/
ProtocolとExtensionで組み込み型を抽象化
READMEを見るとHTTPリクエストを送信するメソッドはこう書かれています。
Alamofire.request(.GET, "https://httpbin.org/get")
これを見ると第二引数はString
を渡すように見えるのですが、Alamofireは一工夫しています。
public func request(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil)
-> Request
{
return Manager.sharedInstance.request(
method,
URLString,
parameters: parameters,
encoding: encoding,
headers: headers
)
}
String
ではなくURLStringConvertible
です。
この中身を確認するとこうなっています。
public protocol URLStringConvertible {
〜中略〜
var URLString: String { get }
}
extension String: URLStringConvertible {
public var URLString: String {
return self
}
}
extension NSURL: URLStringConvertible {
public var URLString: String {
return absoluteString
}
}
extension NSURLComponents: URLStringConvertible {
public var URLString: String {
return URL!.URLString
}
}
extension NSURLRequest: URLStringConvertible {
public var URLString: String {
return URL!.URLString
}
}
つまりURLとして使える組み込みの型を、Protocolを使って抽象化しています。
これで第二引数にこれらの型を同じように渡せます。
ProtocolごとにExtensionを分ける
これはやっている人も多いかと思いますが、ProtocolごとにExtensionを分けると見やすいです。
public struct Response<Value, Error: ErrorType> {
public let request: NSURLRequest?
public let response: NSHTTPURLResponse?
public let data: NSData?
public let result: Result<Value, Error>
public init(request: NSURLRequest?, response: NSHTTPURLResponse?, data: NSData?, result: Result<Value, Error>) {
self.request = request
self.response = response
self.data = data
self.result = result
}
}
extension Response: CustomStringConvertible {
public var description: String {
return result.debugDescription
}
}
extension Response: CustomDebugStringConvertible {
public var debugDescription: String {
var output: [String] = []
output.append(request != nil ? "[Request]: \(request!)" : "[Request]: nil")
output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil")
output.append("[Data]: \(data?.length ?? 0) bytes")
output.append("[Result]: \(result.debugDescription)")
return output.joinWithSeparator("\n")
}
}
各Protocolとメソッドの対応が、MARKで区切るより見やすい
Protocolとメソッドが近いし、括弧で初めから終わりまでの範囲がわかります。
各Protocolごとに関連するメソッドやサブタイプなどをまとめておけば管理も簡単
Protocolが不要になったらextensionの範囲丸ごと消せばいい、とか。
ただし...
例えばCustomStringConvertibleのextension内にdescriptionがある保証はないし、関係ないメソッドが書かれる可能性もあるので注意が必要です。
Alamofireにはありませんが、応用としてプライベートな関数や内部型などをprivate extension
で定義するのもわかりやすくていいです。
class MyClass {
}
private extension MyClass {
class PrivateClass { }
func privateMethod() { }
}
メソッドチェーンでrequestを使いやすく
リクエストのオプションを付与するこれらのメソッドは、オプションをセットするだけでなくself
を返すようにしています。これでメソッドチェーンが可能です。
public func authenticate(usingCredential credential: NSURLCredential) -> Self {
delegate.credential = credential
return self
}
public func progress(closure: ((Int64, Int64, Int64) -> Void)? = nil) -> Self {
if let uploadDelegate = delegate as? UploadTaskDelegate {
uploadDelegate.uploadProgress = closure
} else if let dataDelegate = delegate as? DataTaskDelegate {
dataDelegate.dataProgress = closure
} else if let downloadDelegate = delegate as? DownloadTaskDelegate {
downloadDelegate.downloadProgress = closure
}
return self
}
public func stream(closure: (NSData -> Void)? = nil) -> Self {
if let dataDelegate = delegate as? DataTaskDelegate {
dataDelegate.dataStream = closure
}
return self
}
こんな感じで呼べます。
Alamofire.request(.GET, “https://httpbin.org/")
.authenticate(usingCredential: credential)
.progress(closure)
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
print(response)
}
余分な変数が不要になりますし、何より書きやすいし読みやすいです。
メソッドチェーンは自分は馴染みがなくてあまり使ったことがないのですが、使いどころとして大変参考になりました。
最後に
以上です。他にも見つけたらまた書きたいと思います。