Edited at
SwiftDay 12

SwiftでUnsafePointer<T>などのポインタを扱う

More than 3 years have passed since last update.

この記事は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にあげているので、よければ参照してください。

https://gist.github.com/kaneshin/564fc0780e0d1c9cac7d

また、ポインタの話がでたなら「関数ポインタは?」という方が出てくるはずです。

もちろん、関数ポインタを扱うための互換APIも用意されています。

それは誰かが知りたいというか、この記事のストック数が100くらいになったら紹介するとしましょう。

こんな誰得な情報でしたが、Swiftでポインタを扱う日本語の記事は無いと思うので、もしよければ触ってみてください。

※SwiftではStructを恰もポインタらしく使ってるだけです。