2016年11月13日

[開発者向け] Swiftおぼえがき(というより愚痴)

 思えば会社員時代は、ゲーム機を相手に C++ だけ組んでれば生きていけてたんですが、フリーになってからというもの、急に C# やら Java やら必要になって、もう柔らかくもない頭にムチ打って必死に勉強したもんです。
 そして最近、また新しい言語、Swift を触りはじめた訳なんですが、C# と Java はまだ、基本構文が C と一緒なのでほとんど混乱もなかったのですが、Swift は一見似てるようでだいぶ違うので、慣れるまで結構大変でしたね。
 てことで、せっかくだし、ハマった罠や、ちょっといいなと思った事くらいはメモしとく事にしましょう。小ネタばっかなので、溜まるたびにたまにまとめて書く事にします。

 ちなみに、基本的な事はほぼ以下のページで学ばせていただきました。文量もそんな多くないし、C# や Java を経験してる人なら一気に読めば大方把握できるんじゃないかと思います。
switch 大躍進!
 Swift の構文をひととおり眺めてたら、なんか switch がやたら高機能化されてて笑った。どんだけ switch 好きなんだよと。
switch ( a )
{
case 1:          // {} で囲まなくても、case 内はローカルスコープ扱い
    処理
    // ←break がなくてもここで終わる
case 2:
    処理
    fallthrough  // 下の case に続行したい場合は明示的にこう書く
case 3, 4:       // 複数の場合は、case を並べるのではなく、カンマで並べる
    処理
case b:          // なんと変数もアリ!。結果が被った場合は、上の物が勝つ
    処理
default:         // 何もしなくても default 必須なのはウザイ
    break        // 何も処理しない場合のみ break 必須
}
 変数アリは驚愕だったなぁ。まあ、冷静に考えてみれば、なんで今まで変数がダメだったのかの方が不思議な気もしないでもない。
 上記の他にも、範囲指定とかまで書けたり、さらに予想外な記法もあるみたいなので、以下とか読んでみるといろいろ役に立つかも。
 あとそういや、Swift には if での範囲判定が1文で書ける構文 があるらしく、それはちょっといいねと一瞬心躍ったのですが、
if (value >= 10) && (value <= 20)
// ↓これがこう書ける。Swift ならね!
if 10...20 ~= value
あのゴメン。何したいのか全然わかんない
 ... だけでだいたい分かるのに、~= の意図が全然分からん。どこの文化だ?
配列がなんかキモイんですけど
 元はといえば Swift ではオブジェクトの clone はどう実装するのかとかを調べてたんですが、なんか恐ろしい記事を見つけてしまった。  と、Swift では配列は参照型ではなく値型で、引数渡しも含め、他の変数に代入しただけでディープコピーのように振舞う みたいですね。まあ実際には、配列が変化した時点で初めてディープコピーされる感じみたいですけど、裏でコソコソそういうのされるのキモイわー。
 上の記事では一生懸命いろいろ書いてくださってましたが、どう考えても罠以上のメリットがあるとは思えないけどなぁ。数ある言語の中で敢えてごく少数派の、しかもコンピュータの仕組み的に非合理な挙動をする事に何の意味があるんか。まあ慣れるしかないんだけどさ。

 あとそうだ、もひとつ Swift の配列のキモイポイントといったら、配列を指定数だけ確保するのがやたらめんどい んですよ。何を言ってるのか分か(以下略)
// サイズ10の配列を確保するのにイチイチこれ書かないといけない
var array = [Int]( repeating: 0, count: 10 )

// ちなみに、Swift2 まではこう。心底どっちでもいいし変えんな!
var array = [Int]( count: 10, repeatedValue: 0 )
 とにかく冗長なうえに、無益な仕様変更、ファックだね!。なお、クラス等のオブジェクトの配列を作ろうとすると、Swift の仕様も相まってさらにウザい事に
// これだと、配列全部に同じインスタンスが入る。何に使うんだよソレ...
var array = [MyClass]( repeating: MyClass(), count: 10 )

// とりあえず nil で確保しようとしても、Swift のポリシー的に不可
// オプショナルでもいいけど、直後に全部入れる前提なら無駄すぎ
var array = [MyClass!]( repeating: nil, count: 10 )
var array = [MyClass?]( repeating: nil, count: 10 )

// 結局こうなのかなぁ。なんか2度手間でイヤ
var array = [MyClass]( repeating: MyClass(), count: 10 )
for i in 0..<array.count
    { array[i] = MyClass() }

// もしくはこう?。すげえ遅そう
var array = [MyClass]()
for _ in 0..<10
    { array.append( MyClass() ) }
 repeating: で別のインスタンス入れてくれるとありがたいんだけどなぁ。C# や Java もだけど、オブジェクトの配列を作るために for 回すの、泥臭くてキライ。
protocol にデフォルト実装できますやん
 Java から初めて interface を駆使しするようになったんですが (※C# の頃は interface より delegate ばっか使ってた)、interface って必ず全メソッドを実装しないといけないのがとても面倒でイヤ だったんですよね。

 で、Swift にも interface と同等の概念、protocol ってのがあるんですが、でもまあこれも当然全メソッドを実装しないといけないのでガッカリしてたのですが、UITableView を使おうとした時に、protocol のメソッドを全て実装しなくても動いてる事に気づきました。えっ何これ?
 そんで調べてみたところ、やり方が2種類あって、
  1. Objective-C には optional という概念があって、必要ないメソッドは省略できたのだが、Swift でもクラスを Objective-C 互換にすれば、それを無理矢理使える。ただし、メソッドを呼ぶ前に実装済みかのチェックが必要になるし、Swift の機能に制約がかかるらしい。
  2. protocol の extension を作れば、デフォルト実装を書ける!
 うわ、どっちも邪道な感じw!。とりあえず今更1はないすね。2は馬鹿馬鹿しくも単純明快で噴きましたが、まあできるならよろし。でもメソッドと引数を2度書く事になるのが地味にダルイ。もうここまで許してるなら protocol に直接デフォルト実装を書かせてよ。
static class は enum にオマカセ
 アプリ全体で共有する定数や、便利関数だけを寄せ集めたユーティリティクラスとか、インスタンスを作る必要が全くないクラス の場合、c# では static class ってすればインスタンスが作れなくなって、ちょっぴり清々しかったのですが、Swift にはそんな機能はありません。
 まあそもそもチーム開発でもないし、そんな事気にする必要もないのですが、Swift では代わりにこんな書き方ができるそうな。
enum AppUtil
{
    // 共通の定数詰め合わせとか
    static let    ConstValue1 : Int = 10
    static let    ConstValue2 : Int = 10

    // 共通の便利機能詰め合わせとか
    static func utilFunction1() { なにがし }
    static func utilFunction2() { なにがし }
}
 ああ、うん、仕様の裏をかいた裏技のような、「便利関数を列挙してミターノ!」 とか無理矢理言い張れば納得できるような、うん。  そういや便利関数っていえば、Swift で久々にグローバル関数とか変数って概念を思い出しましたが、やっぱアレはダメすね滅びるべき。特に Swift はグローバル関数もキャメルケースなので、よくローカル変数とぶつかって腹立つわー。min() とか max() とか最低。

 あと enum といえば、Swift といい Java といい、なんで無駄に enum を高機能化したがるんですかね。皆はアレ便利便利言うけど、コレなんか無駄にインスタンス作ってね?とか、Int から変換時にいちいち全検索してね?とか疑心暗鬼になって、どうにも素直に使えん。
 こっちとしては、enum は int の連番定数を楽に作れるだけの機能でいいんだけどなぁ。個数管理するのにも重宝してたし。
無名のスコープはダメゼッタイ
 嫌悪する人もいるかもしれませんが、僕は初期化メソッドとかが肥大化しちゃった時とかに、よくこんな書き方をします。
func initialize()
{
    // ある機能の初期化
    {
        :
    }

    // 他の機能の初期化
    {
        :
    }
}
 まあ関数に分けろよって話ですが、他に流用しないし、関数化して意図せず個別使用されるとムカツクのでさせないって意思表示でもあります。

 でも Swift だと、無名のスコープを作るとエラーなんですよね。なんでやねんと思ったら、こう書くんだそうです。
do {
    ...
}
え、じゃあ do〜while はどうするの?って思ったら
repeat {
    ...
} while( 条件式 )
 だそうです。しかも 元々 do〜while だったのが Swift2 から突然変わった らしいですよ。いつものながら、やりたい放題で楽しそうですね。

 ちなみに、無名スコープがエラーだと実際に知る前からイヤな予感はしてました。Swift では、関数の引数の末尾がクロージャの場合、クロージャをメソッドの外に書ける んですが、
// こんな風に末尾がクロージャのメソッドは
func myFunction( param: Int, callback: (Void) -> Int )

// 正式な呼び方はこうだけど
myFunction( param: 0, callback: { return 10 } )

// こう書いても同じ意味になる
myFunction( param: 0 ) { return 10 }

// もし引数がクロージャのみの場合は、()まで省略可能
// もう関数なのかすらよく分かんねえ
myFunction { return 10 }
という書き方になるので、これって無名のスコープと紛らわしくねえ?、とは思ってましたが、なるほど、そもそもエラーだったんですね。まあそうですよね。

 そういやまた余談ですが、 Swift のクロージャの引数って、なんであんな不気味な記法になったんですかね。なんで中括弧の中に引数書くんだよ。in てなんだよ。
C スタイルの for やめましたって言うけどさ
 Swift3 で、前から非推奨だった C スタイルの for 文がとうとう廃止されましたね。
// Cスタイルの記法は Swift3 で廃止
for var i = 0; i < 10; i++

// Swift では本来こう書く
// なんでか ..< の左右にスペース入れるとエラーなのがムカツク
for i in 0..<10
 で、まあ馴染みにくいとはいえ大して困らんかと思ってましたが、配列を末尾から処理したい場合とか、2づつ足したい場合とかにどうするのかと思ったら、なんかウザすぎて笑えた。詳しくは以下ででも。記法のウザさよりも、無駄な配列作ってんじゃないかと気になる。  それよりも地味に困ったのは、カウンタを引き継ぎたい場合ですかね。まあそんな多用する事でもないし、他の書き方はいくらでもあるんだけど。
ここだけC
int    i = 0, count = 10;
for ( ; i < count; i++ )
{
    // なにかあったら途中で止める
    if ( ... ) break;
}

// 続きは他の処理をする
for ( ; i < count; i++ )
{
    ...
}
 これをこのまま移植したいとして、もちろん while で末尾に i++ するでもいいんですけど。もし while 内で continue を多用してたりしたらダルイっスよね〜。とか考えてたら、Swift にはなんか楽しげな構文がありました。
var i = 0
while ( i < 10 )
{
    // このスコープから出る時、以下が呼ばれる
    defer { i+= 1 }

    // あとは途中で continue でも何でもやり放題ヨロレイヒー
}
 本来は例外処理と組み合わせて使う、他の言語で言うところの finally なんですけど、Swift はスコープのあるところならどこでも使えるんですね。もちろん、ロードやセーブ処理の後始末とかにも使えますよ。地味に便利そう。
 コレ面白いのが、スコープから出る時呼ばれるっても、defer 文より上で return とかで抜けた場合には呼ばれない ようにできてるんですよね。器用な作りだなぁ。
 という事で、for の愚痴と見せかけて、残念、実は defer のステマでした。  なお、ホントに上の例みたいなコードを移植するとしたら、よほど急いでるでもなければ、ちゃんと Swift として読みやすく書き直しますよ。最悪 defer { i += 1 } という手もあるということで。

posted by ひこざ at 23:59| Comment(0) | 開発 - iPhone
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。