bbs
oopers.com
5447 OOPer: 2015-09-18 16:27:50
Swift2[2]: 速やかにtakeRetainedValue()かtakeUnretainedValue()しなさい
ネタを投下してもらっても、すぐに記事にできないのは「それって公開されている情報だけを参照しているか」ってのを確認するのに時間がかかるから。以前に比べると大抵の情報は非デベロッパーにも公開されるようになったものの、やはり「実際に動かさんとそんな話はできないよね」なんてレベルの細かい動きをネタにしようとするとちょいと調べるのに手間がかかる。Xcode7も正式版が公開されたんで、ようやくあまり気にせずにSwiftの話ができるぞ。

と、長い前振りをしたのにSwift1.xの頃からある話で恐縮なのだが、本日はUnmanagedの話である。Tには通常CoreFoundationの型が入る。CoreFoundationの型はC言語的には(中身が秘密の)structへのポインタなのだが、現実的にはObjective-Cのインスタンスと全く同じ構造でretainカウントでメモリ管理をしている。ところがCoreFoundationのAPIは、Objective-Cほどお約束が徹底していないので、旧来のARCでは「本当は同じ方法で管理できるんだけど、そのための情報がないから管理できない」型がUnmanagedなのである。

Swiftでは、主にCoreFoundationのAPI関数群でUnmanagedにお目にかかることになる。で、その時のプログラミングスタイルのお話。

var umData = (CF関数) //umDataはUnmanaged になる
//...途中にあれこれ別の処理を書く
(別の関数とかメソッド)(umData.takeRetainedValue()) //使う直前にtakeRetainedValue()する


ってのが、ダメパターン。細かいことを書こうとすると自分でも訳が分からなくなるんで誤魔化すために省略するが、Unmanagedで実際に格納される値は「本当はretain/release使ってメモリ管理してやらなきゃいけないオブジェクト」を指しているので、そいつが管理されない状態で他にメモリ管理が必要になるようなことをあれこれやっちゃうと、まぁ、まともな状態になっていないことも多い。よってそんな使い方はしちゃいけませんって話。

正しくはこう書く。
var data = (CF関数).takeRetainedValue() //dataは あるCF型 になる
//...途中にあれこれ別の処理を書いてももう安全
(別の関数とかメソッド)(data) //使う時には正しくメモリ管理される


SwiftではCF型もARCでメモリ管理されるようになって使い勝手がよくなっている部分もあるんだが、Unmanagedが鬼門で正しく処理が書けていないってコードサンプルも時々見かける。Unmanagedへのポインタを使わなきゃいけないって場合は仕方ないんだが、それ以外の場合は決してUnmanagedの変数/定数を宣言しない、という態度で臨むとかなり正しいコードになる。

なんせ、つい先日もそこだけ気にして直しただけで一度も使ったことがないAPI関数を使ったコードを動くように直せちゃったので間違いない。英語ではこう書いておいた。

if you find Unmanaged, takeRetainedValue() or takeUnretainedValue() as soon as possible



Swift2になって、かなり多くのCF関数にretained/unretainedのannotationが付くようになったんで、実はかなり使う機会が減っているのだが、まだそれなりの数のAPIがUnmanagedの結果を返してくる。(下手すると実行時まで分からないものもある。)是非とも覚えておいていただきたい。
 ついでに、「takeRetainedValue()なんてメソッドはねーよ」なんて意味のエラーメッセージが出たら、このannotationのせいで、Unmanagedとおさらば出来ている可能性が高いので、さくっとtakeRetainedValue()を削除してみる、ってのもお試しいただきたい。