Edited at

Alamofireから学ぶSwift実践テクニック

More than 3 years have passed since last update.

Alamofireをコードリーディングして、気になったコードの書き方です。

Kyobashi.swift #1でお話しした内容です。

http://kyobashi-swift.connpass.com/event/23712/


ProtocolとExtensionで組み込み型を抽象化

READMEを見るとHTTPリクエストを送信するメソッドはこう書かれています。


README.md

Alamofire.request(.GET, "https://httpbin.org/get")


これを見ると第二引数はStringを渡すように見えるのですが、Alamofireは一工夫しています。


Alamofire.swift

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です。

この中身を確認するとこうなっています。


Alamofire.swift

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を分けると見やすいです。


Response.swift(コメント省略)

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を返すようにしています。これでメソッドチェーンが可能です。


Request.swift

    public func authenticate(usingCredential credential: NSURLCredential) -> Self {

delegate.credential = credential

return self
}



Request.swift

    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
}



Request.swift

    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)
}

余分な変数が不要になりますし、何より書きやすいし読みやすいです。

メソッドチェーンは自分は馴染みがなくてあまり使ったことがないのですが、使いどころとして大変参考になりました。


最後に

以上です。他にも見つけたらまた書きたいと思います。