本日は、前回に話せなかった unowned
にオプションが添えられる周りについてのほんの少し補足と、続いて引き続き 強参照循環
を解消する手段のうちのもうひとつの無所有参照
を オプショナル
で扱ったときの様子を眺めていきます。どうぞよろしくお願いしますね。
———————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #239
00:00 開始 00:19 今回は unowned(unsafe) の話 02:27 unowned(safe) ってなんだろう 04:43 検証などで何かクラス型を用意したいときに AnyObject は便利かも 05:05 unowned は unowned(safe) と同等らしい 07:10 unowned といえば __unsafe_unretained なイメージ 07:55 unowned は、解放済みインスタンスかを検査 14:05 unowned(unsafe) は検査しない様子 15:44 unowned は unowned(safe) と同じ様子 16:50 @convention は関数型の値で使う 19:38 @convention は JavaScriptCore でも活躍 24:46 JavaScriptCore おすすめ 25:59 unowned(unsafe) の所感 26:43 クロージングと次回の展望 ————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #239
はい、では始めていきますね。今日は雑談をしていこうかなと思います。これ、Unowned
とUnsafe
について、前々回に話題にしたことがありましたね。「こういう書き方もできるんだ」という話から、いろいろと全然関係ない話をしましたけど。この話題、せっかくApple公式から提供されているので、軽く紹介しておこうと思います。
紹介といっても、細かい動きや機能の話ではなく、書き方についてです。Unowned
と書いて実行時チェックをしないよ、という書き方がある中で、別の書き方ができる、という話をしたと思いますが、それについて思い出したことを話しますね。
私は以前、Unowned
について知ったのは公式ドキュメントを読んでいるときだったと思います。もしかしたら、Swift Programming Language
を読んでいた時期のことかもしれません。確かに、Safe
とUnsafe
の両方があるんですよね。でも、Swift
は基本的に同じ書き方をひとつしか許さないというコンセプトがあるので、あんまり意識することはないかもしれません。
ただ、違う書き方が本当に必要かどうかは、ある程度調べてみないと分からないですね。例えば、Unowned Safe
やUnowned Unsafe
について調べることが必要かもしれません。私もこれを使う場面がほとんどないので、調べても意味がないことが多いですが、それでもUnowned Unsafe
やSafe
の違いを理解するのは面白いかもしれません。
例えば、Unowned
は弱参照と似ていますが、両者には微妙な違いがあります。Unowned
は常にオブジェクトが存在することを仮定しますが、Weak
はオブジェクトがなくなったら自動的にnil
になるという違いがあります。これが重要な特徴ですね。
また、Unowned
の際、特にUnsafe
の場合は、メモリの解放後にもポインタがその場所を指し続ける可能性があるため、実行時に問題が発生することがあります。これは非常に重要です。
どれか例を試してみたいと思います。例えば、Unsafe
に対してUnowned
やSafe
を使い、実際にその違いを確認することができます。例えば、以下のようなコードで実際に試してみると分かりやすいかもしれません。
class MyClass {
var value: Int
init(value: Int) {
self.value = value
}
deinit {
print("MyClass instance is being deallocated")
}
}
var myObject: MyClass? = MyClass(value: 10)
var unownedReference: MyClass! = myObject
myObject = nil
// この時点で unownedReference は解放されたメモリを指している可能性があります。
// アクセスするとクラッシュすることがあります。
print(unownedReference.value) // ここでクラッシュする可能性があります
このように、Unowned
の使用には十分注意する必要があります。メモリ管理をしっかりと理解し、適切に使わなければなりません。
こんな感じで、Unowned
やUnsafe
の詳細を知ることは大いに価値があります。皆さんも時間があれば調べてみてください。 ポインターの扱いに関してですが、ユーザーが64などの具体的な値を使うと、64ビットCPU以外では正しく動作しないコードになってしまいますね。ですので、こちらの方法が良いでしょうか。次に、オブジェクトの開放について考えます。
オブジェクトを代入した場合、これを開放させるためには、オプショナルにする必要があります。ここでオブジェクトにnil
を設定する際には、まず変数を宣言しておかなければなりません。
let o1 = object
この変数は後で使わなくても構いません。そして、ブロックを作って次に進みます。
let o2 = object
このようにして、次にアンセーフなキャストを実施する部分に移ります。
let o2 = object
let object2 = o2
ここまで順調に進めました。次に、unsafeBitCast
を使います。
let object2 = unsafeBitCast(o2, to: ObjectType.self)
これで、object2
にダミーの値を入れ、その後さらに小さいスコープで新しいオブジェクトを作成し、外側でのアンセーフなキャストによって終了します。このスコープを抜けたタイミングでo2
が解放され、オブジェクトには何が入るかを確認します。
次にprint
で表示させる部分を考えます。例えば、以下のようにします。
print(o2)
Playgroundで実行する場合には、特定のフォーマットによって詳細を確認できるかもしれません。しかし、今回は細かいことを抜きにして、プレイグラウンド以外の方法で実行します。
func output(_ object: Object) {
print(object)
}
output(object2)
この関数を使って、オブジェクトをプリントしてみます。この際、UnsafeMutablePointer
のラベルも使います。
let label = "Label"
print("Label: \\(label)")
これでビルドを実行し、プリントが正常に行われるか確認します。デバッグ中にAlready Deallocated
のメッセージが表示された場合、オブジェクトが既に解放されてしまっているのが原因です。
最終的に、アンセーフな操作とセーフな操作の違いについて理解できるでしょう。セーフな操作では、メモリの解放に関する安全チェックが行われますが、アンセーフな操作ではそれが行われず、座標等の詳細が確認できるだけです。これがセーフとアンセーフの違いです。 自分の知識が多いように感じますね。なるほど、よく分かりました。では次ですが、これを無効にして、このコードでビルドをかけて実行したときにエラーが出るか確認しましょう。同じような感じなのでエラーが出ますね。これだけで同じ動きが見られるのは、ある程度想像がつくでしょうが、完全に同じとは断言できません。しかし大体同じだろうという警戒心をもって捉えておけば大丈夫でしょう。
さて、チェックするかどうかという話ですが、こんな感じで@unchecked
や@unsafe
のような属性もあります。そして、前回か前々回の勉強会で、他にも特性が付けられるという話をしましたが、覚えていますか? コンベンションに関しても、そのブログには特性まで載っていなかったかもしれませんね。
前回の会話の中でうまく伝わらなかった部分があったので調査しました。例えば、関数に対して@convention
修飾子を付けて宣言することができます。しかし、実際に書いてみるとエラーが発生していました。正しくは、@convention(c) -> Int
のようにクロージャに書く必要があります。
let function: @convention(c) (_: Int) -> Int
function = { input in
return input * 2
}
これでC呼び出し規約に基づいた関数が定義され、それに応じた呼び出しができるようになるということです。同じように、@convention(block)
でObjective-Cのブロックとして定義することもあります。Swiftのマップ関数や他の変換関数でも、さまざまな書き方ができます。
実際にこれを使うことはそう多くありませんが、JavaScriptCoreを使用する際に便利です。例えば、以下のようにJavaScriptの仮想マシンを作成し、SwiftからJavaScriptの関数を呼び出すことができます。
import JavaScriptCore
let context = JSContext()
let evaluateScript = """
function doubleValue(value) {
return value * 2;
}
"""
context?.evaluateScript(evaluateScript)
if let result = context?.evaluateScript("doubleValue(100)").toInt32() {
print(result) // 200
}
この結果、JavaScript側でdoubleValue
という関数が呼び出され、結果が得られました。これを使うと必要な処理をJavaScript側で動かすことができ、結果をSwiftで利用できるようになります。このようにして異なる言語間でのインターフェースを作成することができます。
他にも、JavaScriptのコンテキストに対してスクリプトを書き込むことができ、例えば次のようにします。
context?.evaluateScript("const value = 100;")
if let value = context?.objectForKeyedSubscript("value")?.toInt32() {
print(value) // 100
}
これは、100という定数をJavaScript側で設定し、それをSwift側で取得する例です。このようにJavaScriptCoreを使うことで、柔軟にJavaScriptとSwiftの連携を行うことができます。
以上のように、各言語やフレームワークの特性を知り、それを活用することで、より柔軟かつ効率的な開発が可能になります。 JavaScriptコンテキストの評価スクリプトについてですが、他に何か面白いことができてもいい気がしますね。例えば、値が持っている状態で何か処理を行うとき、このようにしてJavaScriptコンテキストにパースを行い、実行するのはどうでしょう。
例えば、12 * 100
や10 * 200
のような演算を行うことができます。これは単にJavaScriptの話ですが、Number
型に変換すると0か1になることもあります。ただ、ここからはどうでもいい内容ですが、実際に0が渡されると面白いです。もしかしたらNaN
(Not-A-Number)になるかもしれません。
とにかく、何でも良いのですが、JavaScript側からSwift側の関数を呼べることが面白い点です。こういったところがJavaScriptの魅力ですね。ただし、コンベンションシリーズだと動かないことがあります。これは、コンベンションが付いていないため、呼び出せないのです。しかし、コンベンションブロックにしておくと呼び出せるようになります。これはとても良い話です。
アンオーンドとは関係ないですが、特性にパラメータを付けるものもあります。他にも色々ありますが、今回思い出したのはこの点でした。JavaScript側は非常に面白いです。今は文字列リテラルで渡していますが、ファイルから読んだテキストをString
型にして渡すこともできます。応用範囲が広いのです。
JavaScriptは古いバージョンから使えます。iOS7でも使えるので心配ありません。この点が非常に簡単で、使いやすいです。応用力もあるので、知っていると便利です。ここまでが雑談でしたが、本題に入りましょう。
基本的に、アンオーンドとアンセーフの使い分けが重要です。例えば、パフォーマンスを重視したいときは、アンセーフに切り替えることも可能です。しかし、通常はアンオーンドを使う方が良いでしょう。次回は、無所有なオプショナル参照について話します。
要は、オプショナルプロパティにアンオーンドを付けることができるという話です。この内容については次回ゆっくりと説明します。今日の勉強会はこれで終わります。お疲れ様でした。ありがとうございました。