2016年10月2日日曜日

Swift3.0でdo-catch構文によるエラーハンドリング

Javaなどにあるtry-catch構文のSwift版、と言ったところでしょうか。

let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
let input:AVCaptureDeviceInput?
do {
    input = try AVCaptureDeviceInput.init(device: device)
} catch  {
    input = nil
}
実行する分の手前にtryキーワードを置くところがポイント。 また、catchの部分でパターンマッチも可能で、

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."
なんて書き方もできる。 ちなみにエラーを投げる関数を定義する場合には、戻り値の手前にthrowsキーワードをつける必要あり。

func canThrowErrors() throws -> String
参考: The Swift Programming Language (Swift 3): Error Handling

Swift3.0でのC言語っぽいforループ

Swift3の破壊的変更が話題ですが、久々にSwiftを書くとその破壊っぷりに驚きます。 例えばforループといえば一般にC言語風forループのイメージが強いですが、

for(int i = 0; i < length; i++){
    // do something...
}
Swift3ではこの書き方ができなくなります。 代わりに、for inを使ってね、とのこと。 具体的には以下。

for i in (0..<length) {
    // do something...
}
ということで、気をつけましょう。

2016年10月1日土曜日

Swift3.0で長さの決まった配列を宣言する方法

久しぶりにXcodeをアップデートしてSwiftを触ってみると、だいぶ以前と感触が違っていて驚き。

ガンガンアップデートされてより良い言語になっているのであろうけれども、ちょっとブランクがある好きに全く違う言語になってしまうのは少々厳しいところ。とはいえ久々にiPhoneでGPGPU的なプログラムを書きたい気分なので、Swiftを使わない手はないでしょう。

ということで今詰まった固定長の配列の宣言方法について。

var fixedLengthArray = [Int](repeating: 0, count: 100)

repeatingを変えると初期の数値が変更でき、countで長さを決定。

何のためにこれをしているかというと、OpenGL ESでテクスチャを読み込むために、UIImageから生のピクセルデータを取り出す必要があったから。



let width = Int(image.size.width)
let height = Int(image.size.height)
var pixelData = [GLubyte](repeating: 0, count: width * height * 4)
let colorSpace = image.cgImage?.colorSpace
let context = CGContext.init(data: &pixelData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
context?.draw(in: CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height)), image: image.cgImage!)
これでpixelDataの中に生情報が格納されたはず。 このデータをテクスチャにアップロードして、テクスチャをバインドしてレンダリングすれば絵が出るぞ!

2015年11月8日日曜日

El CapitanでAndroidStudioがクラッシュする際の対策

El Capitanになんとなくアップグレードしたのち、Androidアプリを公開しようとしてGenerate Signed Apkを実行したところ、キーストア周りでクラッシュしまくるという謎現象に遭遇しました。

AndroidStudioを1.4から1.4.1にバージョンアップしてみたところ、今度は起動しなくなってしまったため、以下のブログを参考にAndroidStudioを再インストールしました。
http://dev.classmethod.jp/smartphone/android/android-studio-mac-uninstall/


rm -rf /Applications/Android\ Studio.app
rm -rf ~/Library/Preferences/AndroidStudioPreview
rm -rf ~/Library/Preferences/com.google.android.studio.plist
rm -rf ~/Library/Application\ Support/AndroidStudioPreview
rm -rf ~/Library/Caches/AndroidStudioPreview

その後、再度全てをインストールしなおし、少なくとも起動させることには成功。

からの、
https://support.apple.com/kb/DL1572?locale=ja_JP
より、試しにJava6をMacにインストールしなおした後に再度Generate Signed Apkを実行してみたところ、クラッシュせずにスムーズに書き出しができました。

というわけで、同様の問題に困った場合にはお試しください。

2015年3月20日金曜日

MacでWindows Azureの仮想マシンのリージョン変更をする方法

もはやSwiftではないですが、いろいろとハマったのでメモ。

Windows Azureがいつの間にやら日本リージョンでも仮想マシンを立てられるようになっていたので、東アジアリージョンに設置していたマシンを移行してみました。

一旦マシンを終了して、リージョンを変えて再起動、というような具合にはいきませんで、やはり物理的にデータをリージョン間で移動させるという処理が必要でした。

データのコピー命令を実行するには、コマンドラインツール経由でしかできず、そのためにはまずアカウント情報をコマンドラインツール側に教えてやる必要があります。

以下手順です。


1. 移転先のリージョン(今回は東日本)にストレージアカウントを作成

2. 新しく作ったストレージアカウントにコンテナを追加(プライベート)

3. この記事を参考にMac用のAzureコマンドラインツールをインストール

4. インストールが完了したら、ターミナルを開く
5. $ azure account download
6. 自動でブラウザが開き、publishsettingsがダウンロードされる

7. $ azure account import [手順6でダウンロードしたpublishsettingのパス]
8. $ azure storage blob copy start --account-name [コピー元ストレージのアカウント名] --account-key [コピー元ストレージのアカウントキー] --dest-account-name [コピー先ストレージのアカウント名] --dest-account-key [コピー先ストレージのアカウントキー]
9. SourceURIにコピー元ストレージのコンテナ内にある、コピー元仮想マシンのストレージ.vhdのURLを入力
10. Destination Container Nameに手順2で作ったコンテナ名を入力

コピーが完了するまで待つ
11. $ azure storage blob copy show  --account-name [コピー先ストレージのアカウント名] --account-key [コピー先ストレージのアカウントキー] で進捗を確認可能


今日は眠いので今度追記。

2015年2月26日木曜日

VRDisplay1.03をリリースしました

以前公開したVRDisplayのマイナーアップデートを行いました。
変更点は以下のとおり。

  1. ディスプレイの表示を調整し、以前よりも見やすく大きく表示するように
  2. Windowのタイトルバーを非表示に
  3. OSのメニューバーを非表示に
  4. Oculus Riftが接続されていない時のエラーメッセージを追加
  5. Mac OS 10.9における不具合により、対応OSを10.10以降に変更
以下詳細です。

1. ディスプレイの表示を調整し、以前よりも見やすく大きく表示するように
DK1に比べて、DK2の解像度が上がったとはいえ、やはりOculus Rift経由で画面を表示すると、DPIの関係上、どうしても文字がほぼ読めなくなってしまいます。そのため、以前のバージョンよりも画面を大きめにレンダリングするように変更しました。デメリットとしては、実際に巨大なスクリーンが目の前に出現するようになり、視界をほとんど覆ってしまうことですが、その分文字は読みやすくなっています。

また、Tips的には、メインのディスプレイの解像度をあえて低めに設定することで、解像度の低い小さめのディスプレイがVRDisplay内に表示され、現実的に作業がしやすくなります。

2. Windowのタイトルバーを非表示に
3. OSのメニューバーを非表示に
Oculus Riftを装着した際に、なぜか視界の上方がぼんやりと明るくなっていたので調査を行ってみたところ、WIndowのタイトルバーとOSのメニューバーの二つが表示されておりました。

この二つを非表示にすることで、先の問題も解決され、本来あるべき没入感を提供することが可能になりました。

4. Oculus Riftが接続されていない時のエラーメッセージを追加
5. Mac OS 10.9における不具合により、対応OSを10.10以降に変更
4は5の調査を行った際に副次的に追加したものですが、とりあえずそちらの方が親切なので残しました。
5に関しましては、ユーザー様より報告があり、調査をしたところ、どうやら10.9においてはNSApplicationDelegateのapplicationDidFinishLaunchingが呼び出されていないようで、一旦現状の対応端末から外しています。

原因については調査中で、何か分かり次第このブログでも報告したいと思います。

以上小さめのアップデートでした。

Gumroadで$4で販売中です。

公開時の元記事は以下
SwiftでOculus Rift DK2向け仮想デスクトップアプリを作った

2015年1月23日金曜日

SwiftでOculus Rift DK2向け仮想デスクトップアプリを作った

先日Oculus Rift DK2を買ったので、Swiftで仮想デスクトップアプリを作りました。
http://vrdisplay.tumblr.com/post/108885580424/vrdisplay-vr-desktop-application-for-mac-with

このアプリを開発するにあたって、以下の点で色々と苦労しました。
  1. Swift関数ポインタを引数に取る関数がうまく呼べない
  2. 画面キャプチャが重い
  3. 仮想ディスプレイがソフトウェアレンダリングでしか動かない
  4. ウィンドウごとのリアルタイムキャプチャが取れない
  5. 開発中にVR酔いになる
  6. DK2でも解像度が足りない
  7. Mac AppStore用にsandbox化するとOculus Riftが動かない
1. Swift関数ポインタを引数に取る関数がうまく呼べない
Swift + OpenGL + OculusSDKというかなり無理のある構成だったのですが、OpenGLのセットアップを行うときに、CADisplayLinkを用いて画面のリフレッシュに合わせてレンダリングを行うということをしようとしたのですが、
どうしてもSwiftからいい感じに関数ポインタを渡すことができませんでした。
そのため、ここは諦めて素直にObjective-Cを利用し、あとは基本Swiftという構成でなんとか乗り切りました。

OpenGLやOculusに関しては意外にもすんなりとSwiftで利用することができたので、
とにかく関数ポインタをコールバックとして渡す系が鬼門なのかなという印象です。

2. 画面キャプチャが重い
ただでさえOculusの駆動にマシンパワーが必要で、さらに3Dのレンダリングも利用しているので、画面データの転送にはなるべく軽快に済ませる必要がありました。
はじめはCGWindowListCreateImageを利用して毎フレーム画像を作っていたのですが、さすがに重すぎたために、AVCaputureScreenInputを利用して書き直しました。
この関数は毎フレームのキャプチャに向いていて、iOSのカメラのようにひたすらCMSampleBufferが届くので、それをOpenGLのテクスチャとして貼り付けてレンダリングすることで、軽快な動作を実現しました。

3. 仮想ディスプレイがソフトウェアレンダリングでしか動かない
このアプリを利用することによって、実際に存在するディスプレイの制約から解放されます。すなわち、Oculus Riftさえあれば、実際のディスプレイは必要ないということです。

なので、Kernel Extensionを利用して、Macに仮想的なディスプレイが存在していると思い込ませ、そのディスプレイのキャプチャをリアルタイムでOculus Riftで表示してあげれば、
ディスプレイは無限に増やせることになります。

実際にEWProxyFramebufferなどを利用して、仮想ディスプレイを作り出し、それらをVRDisplay上で表示するテストなどを行ってみたのですが、ハードウェアアクセラレーションが効かないようで、非常に重たく、使い物にならないというのが現状でした。

なので、今回のバージョンでは仮想ディスプレイを利用することを一旦諦め、
潔く一枚の画面が目の前に表示されるという方法にしています。

また、現在開発に利用しているMacBookProが、ディスプレイを2枚までしかサポートしていないので、
もしかしたら複数枚サポートしているMac Proなどでは、ある程度軽快に動かすことができるのかもしれません。

4.ウィンドウごとのリアルタイムキャプチャが取れない
仮想ディスプレイデバイスを作成して、仮想空間に好きなだけディスプレイを置く作戦には失敗したので、どうにかしてウィンドウのキャプチャを取り続け、それらを独自に再構成することはできないものかと調べてみました。

2で説明した、もともと利用していたCGWindowListCreateImageは、個別のWindowのキャプチャを取れる点が非常に良いのですが、その分動作が重く、毎フレーム取得しようとすると、極端にFPSが低下し、全くもって使い物になりません。

MacのWindowServerに相当するアプリを一から作ることができれば、この辺りは好き勝手にできるようですが、ドキュメントなどが一切存在しないため、不可能に近い状態です。

5.開発中にVR酔いになる
例えば先ほどのFPSが非常に低下したバージョンを実際に試すと、頭を動かした瞬間に目の前全体がガクガクし、その勢いで強烈な吐き気がこみ上げてきます。

開発の序盤のうちは、正しくレンダリングができておらず、視界の隅を気にしてしまった瞬間に気分が悪くなり、そのまま復活せずにその日の開発は終了せざるを得なくなることが多々ありました。

幸いにして段々と自分の体に耐性がつき、またアプリもまともに動くなるように連れて、このような現象にさいなまれることは減りましたが、あるゲームベンダーは開発者がVR酔いをしてしまったがために、Oculus Riftへの対応を見送ったというニュースが出るくらいなので、これはこれで非常に大きな問題であるように思います。

80年代SFホラー『Routine』のOculus Riftサポートが廃止に「現行のキットでは開発全員がVR酔いする」

6.DK2でも解像度が足りない
Oculus Rift DK2はSamsungのGalaxy Note 3のフロントパネルをそのまま利用しており、解像度は1920x1080で、片目あたり960x1080の画面が利用されています。両眼でFullHDなので、テレビであれば非常に高精細で綺麗にみえますが、虫眼鏡で拡大して目の前に置くような用途では、ドットとドットの間が目立ちます。特にプログラムを書いている時などは、フォントを大きめに設定するか、画面に近づかないとうまく文字を読むことができません。

このあたりは、ハードウェア的な進化を待つしかなさそうですが、他にもソフトウェア的に虫眼鏡を実装したり、その他拡大縮小などの便利系コマンドを用意することで、多少の改善が見込めないかと考えています。

7.Mac AppStore用にSandbox化するとOculus Riftが動かない
Mac AppStoreでアプリを公開する場合には、sandbox化と呼ばれる作業が必須項目になり、USBデバイスやカメラにアクセスするようなアプリケーションは、Entitlementsに宣言しなくてはならなくなったり、アクセスするフォルダを事前に書いておかなければなりません。

XcodeのSandbox化用のUIから、USBを利用するというチェックボックスがあったのでそのチェックを入れてみたところ、画面は正しく表示されるものの、ヘッドトラッキング/ポジショントラッキングが完全に利用できなくなってしまいました。

何かしらのキーが足りていないせいだと思われるのですが、いろいろと調べてみてもあまり情報が載っておらず、またArduinoと通信するアプリケーションを作成している記事にあるのと同様の設定をしてみても利用できないので、現状AppStoreでは正常に動作するものが公開できませんでした。

他にもUSBを利用する系のアプリをSandbox化したところ、正しく動作しないという例が存在しており、Oculus Riftを利用するアプリは基本的にはMac AppStoreで配ることができなさそうです。
Apple XCode entitlements Sandbox - つらつらと日々のメモを公開するブログ

まとめ

  • SwiftでもCのライブラリは素直に利用できる
  • Oculus Riftすごい
  • 仮想ディスプレイのハードウェアアクセラレーションは課題。Mac Proならソフトウェアレンダリングでも速いかも
  • USBデバイスを使うアプリはMac AppStoreでは基本的にリリースできない
  • より高解像度のOculusが待ち遠しい


というわけで、もし気になりましたらダウンロードしてみてください。
Gumroadで$4で販売中です。
Oculus Rift DK2の元が取れたら万々歳かなと思っています。

2014年6月8日日曜日

Swift言語の入門 (7) クラス編

さてさてお待ちかね、Swiftにおけるクラスの扱いについていろいろと見ていこうと思います。Objective-Cでは、プログラムを書く上で、必要なコンポーネントは大体クラス化されており、一部C言語由来のものなどに対して構造体を使ったり、定数のまとまり等に列挙を使う程度のイメージでしたが、Swiftではそのバランスが大きくことなりそうです。

Swift言語の入門 (2) 列挙型編Swift言語の入門 (5) 構造体編などで説明したとおり、これら二つの要素もクラスに引けを取らない機能を備えており、かつクラスとは全く異なる特徴を兼ね備えています。

この章ではこれらの違いも確認しながら、クラスの定義から利用方法までをひととおりみていきます。

1.宣言


もっとも小さいクラスの宣言は、以下の通りです。

class Player{
}

宣言と実装のように記述を二つ書く必要はなく、ただ単にクラスキーワード、クラス名、中括弧で終わりです。

プロパティや関数を宣言するには、中括弧の中にかきます。

struct Point {
    var x:Double
    var y:Double
}

class Player{
    var position:Point = Point(x:0, y:0);
    func moveTo(p:Point){
        position = p;
    }

}

このPlayerクラスは、positionという名前の座標情報を保持するプロパティを持っており、デフォルトでは(0, 0)です。moveToという関数はプレイヤーを任意の位置に移動させる関数です。構造体のときには、mutating修飾子が必要でしたが、クラスは基本的に内部状態を変えながら動くものなので、クラスで宣言される関数にmutating修飾子をつける必要はありません。そのため、内部状態を頻繁に変えるようなものにはクラスを用いて、そうでないものには構造体を採用するというのが自然な使い方になります。

ここではpositionに初期値を設定してありますが、設定しないとコンパイルエラーになります。その理由は、現状Playerクラスはイニシャライザ(コンストラクタに相当)を持っていないためpositionが初期化されず、またpositionの型はPoint型でnilを受け付けないからです。なので、Point型の最後に「?」や「!」をつけてオプションにするか、イニシャライザの中でpositionを初期化すればコンパイルエラーは起きなくなります。

2.インスタンスの生成


クラスのインスタンスを生成するには、ただ単にクラス名の後に「()」をつけ、引数を記述します。それぞれの引数には変数名をつける必要があります。

var player = Player(position:Point(x:1, y:2))

これらの変数名は、外部変数名と呼ばれ、通常イニシャライザの内部処理を記述するときにそのまま内部変数名として使われますが、以下のように書くことで、外部変数名と内部変数名を別の文字列にすることが可能です。

init(position p:Point){
    self.position = p

}

positionが外部変数名で、pは内部変数名になります。引数に外部変数名をつけられるという仕様はSmalltalkからObjective-C、そしてSwiftへと受け継がれてきており、コードを読みやすくすると同時に、外部変数名の違いによる関数のオーバーロードを行う等、他の言語にはない独特の特徴を実現しています。

3.関数の呼び出し


クラスのインスタンスの持つ関数を呼び出すには、構造体や列挙型とおなじく、「.」のあとに関数名を書きます。

player.moveTo(Point(x:100, y:-100))

引数の外部変数名は、関数側で明示的に書かない限り、呼び出し時には指定せず、内部変数名を外部変数名として指定した場合には、コンパイルエラーになります。

4. クラスの継承


クラスを継承して新たなクラスを作成するには、以下のようにクラス名の後に「:」を書き、その後に親クラスの名前を書きます。継承はクラスのみに許されている機能で、他のクラスの機能を受け継ぎながら、新たな機能を追加し再利用するための機構です。

class RoboPlayer : Player{
    init(){
        super.init(position:Point(x:6, y:0))
    }
    
    override func moveTo(p:Point){
        //ロボはたまに言うことをきかない、とか
    }
}

子クラスは、自動的に親クラスの持つイニシャライザを継承します。また、親クラスの持つ関数をオーバーライド(上書き)したいときには、override修飾子をつける必要があります。これにより、うっかり全く同じ名前の関数を作ってしまうという問題を防いでいます。

子クラスのイニシャライザでは、親クラスのイニシャライザを呼び出す必要があります。

5.デイニシャライザ(Deinitializer)


クラスのインスタンスが解放されるときには、デイニシャライザと呼ばれる特別な関数が実行されます。デイニシャライザは引数をとらず、書き方は以下の通りです。

class DeinitialzerTest{
    deinit{
        
    }
}

総括

2014年6月5日木曜日

Swift言語の入門 (6) オプション型編

Swiftの言語仕様を読んでいくと、はじめにみつかる特徴的な仕様に、オプション型があります。オプション型とは、名前の通り、必ずしも存在する訳ではない、と言った意味合いがあり、その型そのものの値を含むか、もしくはその値が存在しないことを意味する型です。

他の言語ではNullableなどと呼ばれていることもありますが、Swiftでは全ての型にオプション型が用意されており、IntやDoubleに対しても利用することができます。

JavaではNullableというタイプアノテーションがありますが、これはプリミティブ型(値型)には対応しておらず、C#のNullableは逆に値型にのみ対応しています。Swiftではどちらの種類の型のオプション型を作ることができるため、IntやDoubleなどの型や、独自のクラスのためのオプション型を作ることができます。

1.宣言


var optionalName: String? = "John Appleseed"

オプション型の宣言は、ただ単に型の最後に「?」を追加するのみです。これをつけることにより、optionalNameにnilを代入することができるようになります。
Swiftでは、オプション型でない方にはnilを代入することができず、コンパイルエラーとなるので、逆を言えばオプション型出なければ必ず値は存在しており、nilかどうかチェックする必要は全くありません。

2.オプション型のアンラップ


オプション型からオプション型でない元の型をアンラップ(unwrap)ためには、いくつかの方法があります。

2.1 if let 構文によるアンラップ

if let name = optionalName {
    greeting = "Hello, \(name)"

}

このように、if文の条件の中にlet文を書くことで、「optionalNameが値を持っていた場合には、その値をnameという変数に代入して { ... } 内を実行する」という意味のコードがかけます。optionalNameが値を持たなかったときの処理を書きたいときには、elseの中に書くこともできます。

2.2 「!」演算子によるアンラップ

また、オプション型として宣言したけれども、nilではない値を代入した直後等のような、プログラマの目から見て明らかな場合に関しては、

greeting = "Hello, \(optionalName!)"

のように、変数の末尾に「!」をつけることで強制的に元の型に変換することができます。
ただし、この方法を利用して取り出した値がnilであった場合にはランタイムエラーが起きてしまうので、使う際には注意が必要です。しかしその分いちいちnilかどうかのチェックを行わないので、nilでないことが明らかな場合には、オーバーヘッドを少なくすることが可能です。

2.3 「?」演算子によるアンラップ

メソッドチェーン等を行う際に重宝する書き方ですが、

optionalName?.isEmpty

と書くことで、optionalNameに値があった場合は全体としてisEmptyの戻り値に、nilであった場合は全体としてnilになるという書き方もできます。

この式が評価されるときには、内部的にoptionalNameがnilかどうかを判定して、nilであればその場でnilと評価し、そうでなければそのまま計算を続けるような処理がなされます。

もともとObjective-Cではnilに対するメソッド呼び出しは無視されるという仕組みが備わっていたので、その機構とほぼ同一のものとしてみてよいでしょう。

3.暗黙的にアンラップされるオプション型


元の名はImplicitly Unwapped Optionalsですが、すなわち勝手にアンラップを行ってしまうようなオプション型、ということです。挙動としてはObjective-Cの通常の変数のように、nilも入るし値もはいる型で、nilをアンラップしてしまったときはクラッシュするという型です。挙動的にはJavaの参照型のようなイメージですが、これは何のために必要なのでしょうか。

宣言方法は、「?」のかわりに「!」を型の最後に書くだけです。

  • let assumedString: String! = "An implicitly unwrapped optional string."

使い方としての違いは、値を利用する際に今まで必要だったアンラップの処理がいらないことです。

println(assumedString)

もちろん明示的にif let文を使ってアンラップすることも可能です。

  • if let definiteString = assumedString {
  • println(definiteString)
  • }

このタイプのオプション型の場合には、「!」によるアンラップと同様に、逐一nilのチェックを行わないため、オーバーヘッドが「?」のオプション型よりも小さくなります。

これだけでは使いどころがさっぱり不明ですが、これは主にARCのリファレンスカウンタにおける循環参照の問題を処理するために導入された仕様のようで、あえて「!」にすることによりアクセスのためのオーバーヘッドを減らしつつ、初期化の際に一瞬nilの瞬間を作り出すことによって、循環参照の問題を起こさなくすることができるそうです。詳細はまた別の回にて行いたいと思います。

総括

  • オプション型は全ての型に対して作れる
  • 例外的値等を処理する際に重宝する
  • 「?」はnilだったら無視される
  • 「!」はオーバーヘッドが無いかわりにnilだとクラッシュ
いらない情報
オプション型のオプション型も作れるようで、String!?ともかけたりするようです。

Swift関連記事

2014年6月4日水曜日

Swift言語の入門 (5) 構造体編

Swift言語の構造体は、C言語の構造体からパワーアップし、クラスとも異なる新しい機構へと変化しました。クラスと少々紛らわしいですが、大きな違いは変数へ代入するときの挙動で、クラスは参照渡し、構造体は値渡しです。その特徴も相まって、一度作った構造体の値を変更する関数を書くときには、mutating修飾子を指定しなければならない等、今までに無かったルールが定められています。

1.宣言

  • struct Point {
  • var x: Double
  • var y: Double
  • func norm() -> Double {
  • return norm(x * x + y * y)
  • }
  • }

C言語の構造体とは異なり、メソッドも定義できるようになりました。これでわざわざ第一引数に構造体をとって何かするような関数を大量に生成する必要がなくなりました。

2.初期化

var p = Point(x:1, y:2);

構造体を実際に利用するには、上記のような書き方をします。メンバーの名前とコロンと値をカンマで繋げていく方式です。newなどのキーワードは特にありません。

現状このPoint構造体には、フィールドの初期値が指定されていないため、引数無しでPointを作ろうとするとエラーになります。しかしながら、構造体の宣言の際に、各フィールドに初期値をあたえておけば、引数無しでも初期値を使って構造体を初期化することが可能です。

3.フィールドへのアクセス

フィールドへのアクセスは通常通り、ドット演算子を用いて行います。

println(p.x);

また、フィールドへの代入も同様です。

p.x = 10;

構造体が値渡しである証拠に、一度別の変数に代入してしまうと、その後元の変数が書き換えられても、代入先には影響を及ぼしません。

var p = Point(x:1, y:2);
var q = p;
p.x = 10;

println("p.x = \(p.x), q.x = \(q.x)") //p.x = 10.0, q.x = 1.0


4.mutating

構造体の値を書き換える関数を書くときには、mutating修飾子をつける必要があります。
例えば、Point構造体の位置をY軸にそって反転させるflipY関数を例にとります。

func flipY() {
    x = -x //この = でエラー

}

このままではエラーが表示され、コンパイルできません。なので、funcの前のmutating修飾子をつけて再度コンパイルをします。

mutating func flipY() {
    x = -x

}

総括
  • 構造体は値渡し
  • 関数も持てる
  • 構造体を書き換える関数にはmutating修飾子
Swift関連記事