Swiftで引数リストのうしろに続くカッコは何?Trailing Closureについて

Swiftのコードを書いているとC/C++脳では見慣れぬ文法があります。 今回誤った解釈をしてしまったので、反省の意をこめてちゃんと調べました。

何を間違えたか。

URLSessionを使っていると、次のような構文が出てきました。

let url:URL = URL(string: "http://127.0.0.1:8080/jsonsample.json")!
let task = URLSession.shared.dataTask(with: url){ data, response, error in
	print(data)
	
}
task.resume()

ざっと見た感じ、URLSession に 「URL取ってきて」 とCallするとクロージャーでリクエストの結果を受け取るという関数です。 このdataTask関数を使っている箇所を見てみると、 dataTask(with: url ){ data, response, error in
のように第一引数でカッコが閉じてしまい、引数が1つしかない関数に見えます。

CやC++な脳だと URLSessionには 引数が1つのdataTask関数が存在するので、コレを使っているのかと思いました。

func dataTask(with url: URL) -> URLSessionDataTask

dataTask(with:) - URLSession | Apple Developer Documentation

dataTask(with url: URL) が返すURLSessionDataTask を何かするの? でも、ピリオドも何もないのに、クロージャーが引き続き発生するとは。どんな構文? このあたりで変だなぁ、よくわからないなぁとなります。

正体は Trailing Closures

この書き方の正体はTrailing Closuresでした。 [The Swift Programming Language (Swift 4): Closures] (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID97) Trailing Closures の章に説明があります。

closure引数を取るsomeFunctionThatTakesAClosure関数の例が記載されています。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // closureの処理
}

someFunctionThatTakesAClosureを普通に使うときの書き方はこうです。

someFunctionThatTakesAClosure(closure: {
    // closureの処理
})

Swiftには、最後のクロージャー引数を 引数リストを閉じた後に記載できる Trailing Closureという記法があります。 記述が長すぎないようにすっきり書くことができます。 someFunctionThatTakesAClosureの例で引数に指定していた closure: {…} は 引数リストの後に書けます。

someFunctionThatTakesAClosure() {	
    // closureの処理
}

Trailing Closuresな脳で 最初のURLSession の例を見てみると、引数リストの後の{…} は最終引数のクロージャーだったのです。 呼び出していたのはURLSession.dataTask(with url: URL)ではなく、こちらの関数だったのです。

func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

dataTask(with:completionHandler:) - URLSession | Apple Developer Documentation

Trailing Closure なしの場合

let task = URLSession.shared.dataTask(with: url,completionHandler:{ data, response, error in
	print(data)
})

Trailing Closure を使った場合

let task = URLSession.shared.dataTask(with: url){ data, response, error in
	print(data)
}

Trailing Closure を使った省略例

Trailing Closureのことを知ってみると、実はすでにたくさんTrailing Closureな書き方をしていました。

メインスレッドで処理をするときのTrailing Closureな書き方

// Trailing Closureで書く場合
DispatchQueue.main.async {
	print("main thread proc.")
}

// 引数内に書く場合
DispatchQueue.main.async(execute: {
	print("main thread proc.")
})

あれっ?いままで知らず知らずTrailing Closure使っていました‥

配列のmap関数

配列処理の書き方では多くの省略がありますが、ココでも知らず知らずTrailing Closureを使っていました。

 map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
let numbers:[Int] = [1, 2, 3]

// Trailing Closureで書く場合
let res1 = numbers.map { (number) -> Int in
	return number * 10
}
print(res1)

// 引数内に書く場合
let res2 = numbers.map( { (number) -> Int in
	return number * 10
})
print(res2)

// たくさん省略した場合
let res3 = numbers.map {
	$0 * 10
}
print(res3)

結果はどの書き方もちろん同じです。

[10, 20, 30]
[10, 20, 30]
[10, 20, 30]

後記

そ、そういうことか、リリ○ … 今まできちんと理解せず使っていてやっと読めるようになりました…あっ!引数の途中にクロージャーを作ったことが。 クロージャー引数は引数リストの一番最後に配置しておいたほうが良さそうです。

参考

The Swift Programming Language - Trailing Closures