書いて覚えるSwift入門

第9回Swift 2で、何が変わるのか

収穫の秋

毎年9月末の「秋のApple祭り」は、毎年恒例になった感があります[1]⁠。新しいiOS、新しいiPhone、そして新しいXcode。iOS 9は日本時間9月17日未明にリリースされ、Xcode 7もそれとほぼ同時にApp Storeでの配布が開始されました。同時にリリースされる予定だったwatchOS 2のリリースこそ5日遅れましたが、あとは9月25日にiPhoneが届けば祭りもクライマックスといったところでしょうか。本稿が読者のみなさんの手に届くころには、iOS 9もiPhone 6s/6s plusもすっかり馴染んでいることでしょう。

しかしXcode 7、そしてSwift 2はどうでしょうか? 以前紹介したとおり、今年中にはAppleはSwiftのオープン化を公約しています。Swift 2に習熟するには絶好の期間かもしれません。

そのXcode、本物ですか?

そのXcodeですが、気になるニュースが1つ。中国で改竄され配布された通称XcodeGhostで開発、デプロイされたアプリが、マルウェアに感染した状態でApp Storeに登場したのです。

Apple IDさえあればβ版も無料で入手でき、ましてや安定版がMac App Storeから無料で入手できるXcodeのパチモノをわざわざ使うなんて一見ありえなさそうですが、さすがGreat Firewall of Chinaの向こうだけあって、かの国ではAppleの公式サーバとの接続も日本のように安定しているとも言えず、よって(違法に)二次配布するサイトも点在しているようです。飛行機を乗っ取るために空港を乗っ取るようなもので、不謹慎ながら感嘆を禁じ得ませんでした。

Appleもこの件に関しては、異例の注意喚起を行っています。

そのXcodeが本物か否かは、Terminal.appでspctl --assess --verbose /Applications/Xcode.appを実行すればOKです。Xcodeの巨大さゆえ、コマンドが終了するのにしばらく待たされるのですが、問題なければ

/Applications/Xcode.app: accepted
source=Mac App Store

のようにacceptedを含んだ表示が出るはずです。

Apple「すべてのSwift 1.xプロジェクトを、生まれる前に消し去りたい」

それでは気を取り直して本題へ。iPhone 6sのキャッチコピーは、⁠唯一変わったのは、そのすべて。」だそうですが、Swiftの変貌ぶりはそれどころではありません。なにしろSwift 1.xのコードが全然そのまま動かないのですから。Python 2.xとPython 3.xは別言語ですが、Swift 1.xとSwift 2.xの別言語ぶりはそれをも凌駕します。Python 3の普及が遅れている一番の理由はまさにそこ、⁠同じ言語じゃない」という点にあるのですが、AppleはそれをXcodeでひっくり返しました。Swift 1.xで書かれたプロジェクトを開くと、XcodeがSwift 2.xに翻訳してくれるのです。その一部始終は前々第7回に紹介したとおりですが、まるで「すべてのSwift 1.xプロジェクトを、生まれる前に消し去りたい」といわんばかりのこの機能には「そんな祈りが叶うとすれば、それは時間干渉なんてレベルじゃない。因果律そのものに対する反逆だ」とインキュベーターでなくとも叫びたくもなるというものです。Python 3どころかPerl 6とか、⁠人類には早すぎる言語」との付き合いが浅からぬ筆者には、⁠さすがApple!おれたちにできない事を平然とやってのけるッそこにシビれる!あこがれるゥ!」という冷やかしすら無理で、ただ伏してメメタァするしかありませんでした。

前々回で私は「Swift 2への移行は正式版が出た後で、ただしSwift 2正式化以後はSwift 1の後方互換性サポートも捨てるのがよさそうです。筆者がGitHubに上げているプロジェクトはそうするつもりです」と述べましたが、ほぼそのようになりました。Swift 2正式版がXcodeとともにリリースされてまだ一週間も経っていないのに、筆者はすでにSwift 1がどうだったか思い出せなくなりつつあります。

What's new in Swift 2

というわけで、後方非互換性はXcode 7の力であっさりと乗り越えてしまいましたが、これからがやっと俺たちの戦い。Swift 2は何が変わったのでしょうか? 完全に書き直されたThe Swift Programming Languageでそれを追うのは、サン・ピエトロ寺院を見てコロッセオがどうだったかを思い起こすほど難しいかもしれません(コロッセオは石切場だったのです。これ豆⁠⁠。筆者の場合、Swift 1から2への変遷を追うのに一番ありがたかったのはWWDC 2015のビデオ、What's new in Swiftでした。本稿でもそれを改めて駆け足で追いかけた後、くり紹介していく予定です。逆に、println()からprint()など、Xcode 7が自動で片付けてしまう変更点に関しては、軽く触れるにとどめます。

最低限文化的なenum

Swiftのenumは、enumというよりむしろunion(共用体)に近いもので、本当はSwift 1の頃からもっとバリバリ使いたかったのですが、classstructと比べると微妙に使いづらいものでした。

デフォルトのdescription debugDescriptionが直感的に

たとえば、

enum Langs {
    case C, Cplusplu, ObjectiveC, Swift
}

というenumがあったとして、print(Langs.Swift)はそのままではそっけなく(Enum Value)と表示されるだけでした。⁠Swift⁠と表示させるためには、いちいち、

extension Langs : Printable {
  var description: String {
    switch self {
    case C:
          return "C"
        case Cplusplus:
          return "C++"
        case ObjectiveC:
          return "Objective-C"
        case Swift:
          return "Swift"
      }
  }
}

などという具合にdescriptionプロパティを定義しておく必要があったのですが、これと同様のことを自動でやってくれます。

総称型enumがまともに

Swift 1では次のコードがクラッシュしていました。

enum Either<L,R> {
    case Left(L)
    case Right(R)
}

総称型のEnumでは型変数が1つしか取れなかったので、

enum Optional<T> {
  case None
  case Some(T)
}

は定義できてもEitherのような型は作れなかったのですが、やっと作れるようになりました。

再帰的enumが可能に

Swift 1では、これもだめでした。

enum Cons<T> {
    case Nil
    case Atom(T)
    case Pair(Cons, Cons)
}

enumは値型(value type)なので、⁠固定的なサイズを持たなければならないのに、自己参照していては無限にメモリが必要になってしまう」とはSwiftのパパ、Chris Lattnerの弁。⁠今のデバイスでは無理。まあ来年あたりなら」というジョークが見事に滑っていましたが、indirectを次のようにつけることで再帰的enumが実現可能になりました図1⁠。

enum Cons<T> {
    case Nil
    case Atom(T)
    indirect case Pair(Cons, Cons)
}
図1 再帰的enum
図1 再帰的enum

もしくは、

indirect enum Cons<T> {
    case Nil
    case Atom(T)
    case Pair(Cons, Cons)
}

ここまでそろっていたなら、筆者もswiftjsonをenumベースで実装したんですがねえ……。

repeat { } while

Swift 1までの「必ず1回はブロックを実行する」ループはdo { } whileだったのですが、repeat { } whileになりました。do { }は今後後述のdo { } catch { }などでも用いられるようになるのと、ブロックの頭を見ただけで条件分岐がブロックの後ろにあることが直感的にわかるということでそうなったようです。

Option Sets

これはSwift 1.2から導入された重複なしの集合、Setとは別物です。

Swift 1ではビットマップのフラグの扱いは、たとえば、

let swiftGoals:Goals = .Refined ¦ .Safe ¦
.Expressive

のように、Bitwise命令で操作していたのですが、わかりづらいということで次のようにOption SetTypeプロトコルに適合したstructを使ってより直感的にフラグを立てたり折ったりできるようになりました。

struct Goals : OptionSetType {
    let rawValue : Int
    static let Refined = Goals(rawValue:1)
    static let Safe = Goals(rawValue:2)
    static let Expressive = Goals(rawValue:4)

}
let swiftGoals:Goals = [.Refined, .Safe,
.Expressive]
if swiftGoals.contains(.Expressive){}

関数とメソッドとラベルと

Swiftでは関数もメソッドもfuncで定義しますが……、

func f(first:String, second:Bool){ }
swtruct S {
  func m(first:String, second:Bool){ }
}

Swift 1では関数は、

f("Swift", second:true)

と最初の引数のラベルが省略可能だったのに対し、

var o = S()
o.m(first:"String", second:true)

といった具合にメソッドで最初の引数のラベルは省略不能でした。Swift 2では関数の場合と同様、どちらもデフォルトでは最初の引数のみ省略という形におさまりました。_をラベルの前につけることでそのラベルを省略可能にできるのは今までどおりです。

使用する変数に警告

これはあくまでもXcodeの機能であってSwift2の仕様ではありませんが、varで宣言したはずなのに一度も変更を加えていない変数は、letで定数にするように警告するようになりました。

@testable

ユニットテストの際に1つ困るのは、publicなシンボルにしかアクセスできなかったことですが、ユニットテスト中で、

@testable
import MyApp

と宣言することで、internalなシンボルにもアクセスできるようになりました。

Markdown in Rich Comments

リッチコメントの中でMarkdown記法が使えるようになりました。画像すら入れられます。

guardでガード

if let記法だと、条件が煩雑だとネストが深くなりがちでした。

if let name = json["name"] {
  if let year = json["year"] {
    handlePerson(name, year)
  } else {
    handleError("year is missing")
  }
} else {
  handleError("name is missing")
}

たとえばPerlだったら、同様の場合はこう書くところです。

my $name = $json->{name} or handlError("name is missing");
my $year = $json->{year} or handlError("year is missing");
handlePerson($name, $year);

どちらが読みやすいかは一目瞭然ですが、これと同様のことがguard文の導入である程度できるようになりました。

guard let name = json["name"] else {
  handleError("name is missing")
}
guard let year = json["year"] else {
  handleError("year is missing")
}
handlePerson(name, year)

else { }がまだうざくはありますが、だいぶすっきりしました。elseというのもやや違和感がありますが、これがないと

guard let something = someFunction
  { /*...*/ }

と書いてしまった場合、

guard let something = someFunction({ /*...*/ })

と解釈されてしまうおそれがあるのでこのような形になったのでしょう。

case、beyond、switch

パターンマッチは便利なものですが、switchがうざく感じませんでしたか?

switch lang {
case .Swift(let version) where version < 2.0:
  updateIt(version)
default: break
}

こういう場合に、if caseが使えるようになりました。

if case .Swift(let version) = lang where
version = < 2.0 {
  upadteIt(version)
}

caseはifだけでなくforでも使えます。

for n in numbers where numbers % 2 == 0 {
  handleEven(n);
}

if #available

APIバージョンによる条件コンパイルに、#availableが使えるようになりました。

override func awakeFromNib() {
  if #available(OSX 10.10.3, *) {
    dropButton.springLoaded = true
  }
}

今までextensionをかけられたのはstructenumclassといった実装を伴った型のみでしたが、protocolextensionをかけられるようになりました。

extension CollectionType {
  func countIf(match: Element -> Bool) -> Int {
    var count = 0
    for value in self {
      if match(value) { count++ }
    }
    return count
  }
}

これは大進歩です。大進歩なので次回以降詳しく取り上げます。

Exception、ahem、error handling

おそらく巷で最も騒がれているSwift 2の新機能が、try {} catch {}かもしれません。しかしこれはAppleが慎重にexceptionという言葉を避けているように、Javaなどの言語の「例外」からするとかなり限定的な機構です。これも解説は次回以降と、とりあえずこんな感じというものを、あえて何も解説せずに図2⁠。

図2 try-catch……例外処理?
図2 try-catch……例外処理?

String is not just a sequence of characters

String自体がSequenceTypeでなくなり、そのように扱いたければ.characters.utf8.utf16を必ず使うようになりました。

Swiftによる文字列の扱いもまたそのための記事を割くだけの意味があるものなので、次回以降あらためて取り上げます。

まとめ

そのほかにも、

  • 破壊的ソートだったsort()sortInPlace()になり、非破壊的なsorted()sort()になったり
  • find()indexOf()になったり
  • .extend().appendContentsOf()になったり

などとAppleが変えたい放題変えたSwift 2ですが、世代や性能や機能が変わってもそれがiPhoneであることは見間違えないように、一目見ればそれがSwiftのコードだという特徴をSwift 2は備えているように感じています。シンボルの改名にしても、英語的な洗練を捨ててまで非ネイティブな人でも誤解しにくい名前にしているのは日本のSwiftプログラマにとっては朗報かもしれません。

というわけで今回はSwift 2の変更点と新機能を駆け足で見てきたわけですが、ここで今年の産業界のニュースNo.0ほぼ間違いなしのビッグニュースが飛び込んできました。Appleがいよいよ自動車を再発明する? いえ。VW社のディーゼル不正事件です。EA189という「クリーンディーゼル」の制御コンピュータに、当局がガスチェックしているときだけエンジンの動作モードを変えるという通称⁠defeat device⁠が組み込まれているのを米国環境保護局(EPA)が見つけたというもので、リコール対象50万台、制裁金2兆円というのは序の口で、これまで同エンジンが搭載されたクルマは全世界で1,100万台も売れており、今年上半期トヨタを抜いて世界最大販売台数を達成した同社は、通年でそれを達成する前にそれをはるかに上回る創業以来の危機が到来しています。ソフトウェアとハードウェアの組み合わせで世界を変えるという点で自動車メーカーも実はAppleと同じような立場にあったわけで、それが負の方向に働くとどれほど恐ろしいことになるのか、一エンジニアとして恐々としています。世界を変えるなら、より良い方向に変えていきたいものです。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」⁠使いたい」⁠発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧