この記事はSwift Advent Calendar 2014の12日目の記事です。
Advent Calendarも折り返し地点目前です。
12月も既に12日となっており、年の瀬をひしひしと感じさせますね。
私感ですが、年を取るごとに一年がとても早く感じます。
先人が**「一年の体感日数は年齢の逆数となる」**という言葉も残していますしね。
(1以上の逆数をとっても値はそこまで変わらないというツッコミはしないでおきましょう)
さて、それではSwiftの話題へ移りましょう。
Swiftについて言語設計は良いのですが、まだまだXcodeのサポートとコンパイラがそこまでなため、一度は個人でアプリを作ったもののモチベーションがあがりません。
(あるコードを最適化すると無限ループする事も)
今回はコンパイラー・・の話ではなく、ポインタを使ってメモリに直接アクセスする話です。
Swiftでポインタを使用する
SwiftのC言語への互換性
まず、SwiftにはC言語と互換性のあるAPIが存在しています。
※詳しくはこちら→Interacting with C APIs
例えば、iOS, Macのアプリケーションを作成してる途中に
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
println("Unresolved error \(error), \(error?.localizedDescription)")
}
return _fetchedResultsController!
と書いた覚えがあるはずです。(プロジェクトのCoreDataテンプレートにあります)
このNSError?
で宣言された変数のアドレスをperformFetch
の引数へ渡しています。
performFetch
の定義を見に行くと
func performFetch(error: NSErrorPointer) -> Bool
となっており、NSErrorPointer
は下記のようにエイリアスされています。
typealias NSErrorPointer = AutoreleasingUnsafeMutablePointer<NSError?>
ここにあるAutoreleasingUnsafeMutablePointer<T>
がC言語のポインタとして連携してくれます。
UnsafePointer<T>
などなど
本来、Swiftにはメモリへ直接アクセスすることはできません。
ですが、先に述べたC言語との互換APIにあるUnsafePointer<T>
などを利用し、それらを可能にしています。
先ほどからUnsafePointer<T>
などと言っておりますが、他にも種類が公式のリファレンスに書いてあります。
For return types, variables, and arguments, the following mappings apply:
C Syntax | Swift Syntax |
---|---|
const Type * |
UnsafePointer<Type> |
Type * |
UnsafeMutablePointer<Type> |
For class types, the following mappings apply:
C Syntax | Swift Syntax |
---|---|
Type * const * |
UnsafePointer<Type> |
Type * __strong * |
UnsafeMutablePointer<Type> |
Type ** |
AutoreleasingUnsafeMutablePointer<Type> |
上記の通り、ポインタの種類は
- Constant Pointers
- Mutable Pointers
- Autoreleasing Pointers
に分けられています。
特徴として
- C言語の
NULL
を表現するにはSwiftではnil
を使用 - 必要があれば
UnsafePointer<Type>
に変換されます
実装について
これらを実際に使ってみましょう。
今回はポインタの例として、swapByPointer関数を作って紹介します。(swap関数はポインタの例で頻出してますね。)
型は任意の型にしたいため、ジェネリクスを使います。
swapByPointer関数
func swapByPointer<T>(x: UnsafeMutablePointer<T>, y: UnsafeMutablePointer<T>) {
let z: T = x.memory
x.memory = y.memory
y.memory = z
}
呼び出し
var a: Int = 10
var b: Int = 20
// a = 10, b = 20
println("a = \(a), b = \(b)")
// Swap
swapByPointer(&a, &b)
// a = 20, b = 10
println("a = \(a), b = \(b)")
このような感じです。
なんてことはない普通のswap関数を定義して、その引数にアドレスを渡しています。
関数の中でアドレスが指すメモリに直接代入を行っています。
In-Out (おまけ)
「いやいや、そんなのinout
使ったほうがシンプルでしょ?」のツッコミはあるかと思います。
今回はポインタを明示的に使いたいので、inout
は使いません。
「inout
ってなに?」という方は下記のコードを読んでください。
swapByInout関数(inout使用版)
func swapByInout<T>(inout x: T, inout y: T) {
let z: T = x
x = y
y = z
}
呼び出し
var a: Int = 10
var b: Int = 20
// a = 10, b = 20
println("a = \(a), b = \(b)")
// Swap
swapByInout(&a, &b)
// a = 20, b = 10
println("a = \(a), b = \(b)")
メモリ管理
さっきのは既に確保している変数のアドレスから参照していましたが、C言語に慣れている人なら「memory allocは!?」となるでしょう。
もちろんあります。
alloc
// Use UnsafeMutablePointer<T>
typealias IntPointer = UnsafeMutablePointer<Int>
// C Language:
// typedef int *IntPointer;
var a_ptr = IntPointer.alloc(1)
// C Language:
// int *a_ptr = (int *)malloc(1 * sizeof(int));
// IntPointer a_ptr = (IntPointer)malloc(1 * sizeof(int));
// or
// int *a_ptr = (int *)calloc(1, sizeof(int));
var b_ptr = IntPointer.alloc(1)
a_ptr.memory = a
// C Language:
// *a_ptr = a;
b_ptr.memory = b
// a_ptr.memory = 10, b_ptr.memory = 20
println("a_ptr.memory = \(a_ptr.memory), b_ptr.memory = \(b_ptr.memory)")
// Swap
swapByPointer(a_ptr, b_ptr)
// a_ptr.memory = 20, b_ptr.memory = 10
println("a_ptr.memory = \(a_ptr.memory), b_ptr.memory = \(b_ptr.memory)")
// Destroy
a_ptr.dealloc(1)
// C Language:
// free(a_ptr);
b_ptr.dealloc(1)
コメントとしてC言語のときの状況も記しておきました。
解説が必要そうなコードは alloc
, dealloc
, memory
の3つですかね。
var p = UnsafeMutablePointer.alloc(num)
C言語では
Type *p = (Type *)malloc(num * sizeof(Type));
// or
Type *p = (Type *)calloc(num, sizeof(Type));
となります。
必要分のメモリサイズを確保するための、宣言した型の個数をnum
に指定します。
p.dealloc(num)
C言語では
free(p);
解放しています。
p.memory
C言語では
*p
です。アドレスにある値を参照するために使用します。
大体、この程度を知っていればC言語のような考えでSwiftを組むことができます。
おわりに
今回のコードをGistにあげているので、よければ参照してください。
また、ポインタの話がでたなら**「関数ポインタは?」**という方が出てくるはずです。
もちろん、関数ポインタを扱うための互換APIも用意されています。
それは誰かが知りたいというか、この記事のストック数が100くらいになったら紹介するとしましょう。
こんな誰得な情報でしたが、Swiftでポインタを扱う日本語の記事は無いと思うので、もしよければ触ってみてください。
※SwiftではStructを恰もポインタらしく使ってるだけです。