LoginSignup
149
147

More than 5 years have passed since last update.

#Swiftならこう書くシリーズ

Last updated at Posted at 2016-03-25

John さんの Swiftならこう書くシリーズ 10選 を拝見して、こう書いたらもっと楽しくなりそうと思ったところがあったので、補足もしつつ紹介してみようと思います。

この記事は上記のブログみたいに「Objective-C と対比して分かりやすく」みたいには書いていないので少し難しい感も漂いますけど、ブログを読んだ後の次のステップとして読んでもらえたら幸いです。

10. 配列の操作ならSwiftの SequenceType メソッドを使用する

Swift の配列は SequenceType という仕組みに則って作られています。それと同時により配列らしい性質を提供する CollectionType という仕組みにも則っています。せっかくの配列なら CollectionType も合わせて注目すると、もっと楽しくなりそうです。例題に出てくる indicesCollectionType が担う機能のひとつです。

すべての index をループする

すべてのインデックスを取得したい時は、ブログに書かれていた通り、次のようにすることができます。とてもきれいな書き方ですよね。

for index in array.indices {

}

enumerate を使う方法

そしてこのとき、多くの場合、インデックスから配列の要素を取り出して使うと思います。すべての要素をインデックスを加味して処理したい場合は、次のように enumerate メソッドを使う方法があります。この例では、インデックスと要素のセットをタプル型で item 変数に取得し、ループないでそれを使っています。

for item in array.enumerate() {

    let index = item.index
    let element = item.element

}

このとき注意したいのが enumerate メソッドの場合、取得できるインデックス番号は 0 から始まる通し番号 になるところです。

通常の Array であれば開始インデックスは 0 になるのが普通なので問題ないですが、たとえば array[3..<5] みたいに ArraySlice を切り出したりすると開始インデックスが 0 以外になるので、それに対して indices で回したときと、今回みたいに enumerate で回したときとで、得られるインデックス番号に違いが出てくるところは注意する必要があるかもしれません。

enumerate を使う方法(改)

そして enumerate で取得できるのは『インデックスと要素のタプル』なので @usatie さんが教えてくださったみたいに for ループ内の処理に移る前にタプルを展開するともっとスマートに書けます。

for (index, element) in array.enumerate() {

}

Array を直接使う方法

ループ内でインデックス番号を考慮した処理をしないのであれば enumerate を使わずに、シンプルに配列を in の後に指定してあげれば、要素を順番に取得して処理をすることができます。

for element in array {

}

個人的な印象では、配列の順次操作は案外これで済んでしまう場合が多いので、まずは『インデックス番号を取得しなくても目的を達成できそうか』を検討してみると良いかもしれません。

最初・最後の index を取得するには

配列の最初と最後のインデックスを取得するのは、ブログにあった方法だと Int? なオプショナル型で取得できてしまうので、それを扱おうとした時に面倒ごとが少し増えそうに思います。

let firstIndex: Int? = array.indices.first
let lastIndex: Int? = array.indices.last

配列の場合 CollectionType が(厳密には Indexable が)持っている startIndexendIndex を使うと、普通の Int 型で目的のインデックスを取得できるのでオススメです。

コードも何をしているのかも最初の例より明確になるので嬉しいです。ただし注意点として、最後の要素を取得できる有効なインデックス番号として lastIndex を取得したい場合、配列の endIndex最後の次のインデックスを取得する性質があるので predecessor メソッドを使ってひとつ手前のインデックスを取得する必要があります。

let firstIndex = array.startIndex
let lastIndex = array.endIndex.predecessor()

endIndex の扱いに注意

ちなみに Swift ではインデックスを startIndex ..< endIndex のように 左側を含んで、右側は含まない みたいに扱うのが一般的なので、最後のインデックスとした時に 実際に有効な値が取れるインデックスの次 が取れることが一般的に思うので、その辺りも注意しておきたいところです。

自分も最初、ここを勘違いしたままこの記事を書いていて @bricklife さんに間違いを教えてもらって気がつきました。うっかりすると間違ってしまって、しかも気づきにくいところに思うので、怖いところかもしれません。

last メソッドで最後の値を取得する方法

インデックスに着目しなくても、最後の要素を取得したいという場面なら、配列にも last メソッドがあるので、それを使って値をとることもできます。

if let element = array.last {

}

ただしこのとき、空の配列みたいに最後の要素が取得できない(そもそも要素がない)場合もあるので、結果は Optional 型で返ってきます。そのため、値が取れたかを判断して使う必要が出てきます。これについては array.indices.last で、配列のインデックスが揃えられた Range 型から、最後の値を取り出すときと同じです。

最後の n の index 以外を取得するには

最後の要素以外を取得する方法はいくつかありそうですね。ブログに紹介されていた方法はインデックスに着目した方法です。

for index in array.indices.dropLast(n) {

}

インデックスの計算なら処理的に軽そうな気もして、もしかして大きい配列の時に処理効率が良かったりするのかもしれません。

enumerate と dropLast を合わせた書き方

そして、自分が上で紹介してみた array のループ方法も加味してみると、たとえばインデックスと要素が欲しい時には、次のようなループの書き方もできたりします。

for item in array.enumerate().dropLast(n) {

}

もちろん enumerate を使うので、先ほど挙げたように開始インデックスが 0 以外のものに対して使うと、思っているのと違うインデックス番号が取得されるかもしれないところは注意です。

Array に直接 dropLast を使う書き方

配列の要素さえあれば目的を達成できるなら、次のように書けたりもします。コード的にはこれがいちばんわかりやすいかなと思います。内部的には dropLast メソッドによって、最後から n 個の要素を捨てた配列を作って、それに対してループを行っています。

for item in array.dropLast(n) {

}

特にこれで支障がなければ、この書き方がいちばん直感的でわかりやすいかなと感じました。

偶数番目の要素を処理するには [NEW]

せっかくなので『配列の先頭から偶数番目の要素を取得して処理する』みたいなことをする方法も紹介します。Objective-C で偶数ごとにの場合は、次のように書けていました。

for (NSInteger i = 0; i < array.length; i+=2) {

}

ストライドで刻み幅を指定する方法

これを Swift で書く場合、まず、インデックスに着目するなら次のような書き方ができます。

for index in array.startIndex.stride(to: array.endIndex, by: 2) {

}

この方法は、配列のインデックス RandomAccessIndexTypeStrideable に対応していることを利用して、任意の刻みで取得できる SequenceType を取得して、それを for ... in ループで順番に処理する流れになります。

こうしてみると、なんかすごく長くなってて読みにくいかもとか感じるんですけど、もしかしてもっとスマートな書き方があったりするでしょうか。そんなことを思いながら、とりあえず紹介してみました。でもこの書き方、コードの意味という観点でみれば、Swift に慣れてくると最初の例より汲み取りやすく感じられてくるから興味深いです。

ループ条件で限定する方法

他の書き方としては、ループ内で要素も使うなら、先ほど紹介した enumerate メソッドと、for ... in ループで条件指定ができる where を使って、次のように書く方法もあります。

インデックスが index % 2 == 0 である場合、つまり 2 で割った余りが 0、つまり偶数の場合に限って繰り返し処理を行っています。

for item in array.enumerate() where item.index % 2 == 0 {

    let value = item.element

}

こちらも enumerate を使ってますけど、今回の場合は enumerate が 0 から始まる通し番号でインデックスを返す性質が功を奏して、どんなインデックス番号で始まる配列であっても同じ計算式で偶数番目か奇数番目かを判定できるようになります。

ただこの方法、個人的には繰り返し条件に指定している式が具体的すぎて読みにくい感じもします。これよりは先ほどの stride(to:,by:) を使った方法の方が動き的には捉えやすいような気もしますけど、今回の方がコード自体はスマートで嬉しいのかなみたいなことも思ったりします。

ループ条件で限定する方法(改)

条件式の部分が明瞭でないと感じる時には、次のように、関数をローカルにひとつ自分で作って使ってみても良いかもしれません。Swift は関数の中に関数を定義できるので、最初に『どんな場合をどう表現するか』を関数で書いて、それを用いてコードを読みやすくする、ということもできたりしそうです。

// 偶数番目であるというのは、こういうこと。
func isIndexEven(index: Int) -> Bool {

    return index % 2 == 0
}

// 配列の要素を繰り返す。ただし偶数番目の要素に限って処理する。
for item in array.enumerate() where isIndexEven(item.index) {

    let element = item.element

}

9. シングルトンの代わりに staticの関数やプロパティを使用する enum で作成する

静的プロパティの集約場所として、ブログでは次のようなコードが紹介されていました。enum を選ぶ着目点が面白いと思ったのですけど、実際のところはどうなのでしょう。その辺りに興味が湧いて考えてみたくなりました。

enum GlobalSettings {

    static var isICloudEnabled = false;
    static func saveSettings() {
    }
}

列挙型に『複数の候補の中からどれかを選択する型』みたいに想像したとすると、このような使われ方が、一瞬なんのことかわからないみたいな、可読性を下げる要因にもなりそうな気もしたりしました。

ブログにあった『空の構造体やクラスだとインスタンス化ができてしまう』という観点で考えたときには、たとえばイニシャライザを private にするという方法もあります。

struct GlobalSettings {

    static var isICloudEnabled = false;
    static func saveSettings() {
    }

    private init() {
    }
}

構造体であれば、データを集約している場所という意味合いが出てきて enum より可読性が上がりそうな気がするのと、こうしてイニシャライザを private に限定することで、外からのインスタンス化を意図的に防げるメリットも出てきます。クラスについても同様のことができて、データを集約管理しているような印象になって良さそうです。

シングルトンとグローバル変数を使う方法

もう少し思いを巡らせてみると、設定を保存するストレージが欲しい場面なのだから、静的プロパティにこだわらないで、クラスで作ってシングルトンでインスタンス化しても良いのかなとも思いました。ついでに、グローバル設定なのだからインスタンスをグローバルにおいて、イニシャライザを private で保護してあげれば設定用のインスタンスを2重に作ったり差し替えられたりされなくなるので良さそうです。

var settings = GlobalSettings()

final class GlobalSettings {

    var isICloudEnabled = false;
    func saveSettings() {
    }

    private init() {
    }
}

こんな感じでグローバル変数を使っても、Swift の場合はモジュール単位で名前空間が作られるので、他と衝突しないところも「こんな書き方もありなのかな?」と感じたところです。

シングルトンを構造体で扱うことの問題点

さて、上の例なのですけど、自分は最初、構造体を使って次のように記載していました。

var settings = GlobalSettings()

struct GlobalSettings {

    var isICloudEnabled = false;
    func saveSettings() {
    }

    private init() {
    }
}

これについて @yashigani さんから『構造体をグローバル変数に持たせた場合、それを別変数に代入したときに値の複製が作られてしまうから、うっかりすると破綻しかねない。クラスの方が適切だろう』という趣旨の指摘を頂きました。

なるほど、たしかにそうですね。

構造体で定義していたときは、次のように、どんなところからもグローバル変数の settings を直接操作することを期待していました。

@IBAction func pushICloudEnableButton(sender: AnyObject) {

    settings.isICloudEnabled = true
    settings.saveSettings()
}

ただし、例えば、次のような何の変哲もないコードを書いたとき、たぶん期待した通りに動きません。

// 設定を更新したつもりが、反映されない。
func saveSettings(settings: GlobalSettings) {

    settings.saveSettings()
}

saveSettings(settings)

同じようにこのようなコードも、たぶん期待通りに動きません。

func iCloudEnable(enable: Bool) {

    var _settings = settings

    _settings.isICloudEnabled = enable
    _settings.saveSettings()
}

これは Swift の構造体が、別の変数に代入したときに同一の値を持った別のインスタンスが作られるという性質のためです。たしかに、ここまで考えていくと、シングルトンという観点でいえばシングルトンを保証できていないですね。

ということは、シングルトンを作ろうと思った時に構造体を持ち出すのは根本的に間違っていそうです。グローバルに置いて各々の設定項目を管理するという観点からもクラスが適切なように思いました。クラスであれば、Swift のクラスは他の変数に代入しても必ず同じインスタンスを保持するので、上で書いた、構造体では期待通りに動かなかったコードも、期待通りに動いてくれるので、それを扱う側にとっても間違いのないコードが書けることにつながりそうです。

8. isKindOfClass() の代わりに is を使用する

isKindOfClass: を使うメリット

if dictionary["value"] is String {
}

こちらは補足ですけれど、Swift で isKindOfClass が使用できるのは NSObject を継承しているクラスだけに限られるので、原則的には is 演算子を使うことになると思います。

ただし、Swift の is 演算子と NSObjectisKindOfClass とは互いに関係しているわけではないようなので、滅多にないこととは思いますけど、もし Objective-C 互換クラスで isKindOfClass をオーバーライドしてたりすると is 演算子との挙動が変わってきたりする可能性はあるみたいでした。

switch–case にも is 条件が使える

ブログで紹介されていた通り、こうやって switch で型に応じてふるい分けられるのは手軽で嬉しいところですね。if 文をこれだけ条件分岐をしようとすると読みづらくなってくるので、こんな風に switch を活用できると嬉しいことが増える気がします。

let dictionary: NSDictionary = ["value" : 100]

switch dictionary["value"] {

case is String:
    print("It's a string!")

case is Int:
    print("It's a number!")

case is NSObject:
    print("It's an object!")

default:
    print("others")
}

そしてこのとき、値の型を判定したら、その型として値を使いたいことって多いと思うので、そのようなときには次のように書くとさらに嬉しくなれるかもしれません。

switch dictionary["value"] {

case let value as String:
    print("\(value) is a string!")

case let value as Int:
    print("\(value) is a number!")

case let value as NSObject:
    print("\(value) is an object!")

default:
    print("others")
}

このようにすると、型の判定と合わせて値を取得して、その値を使って case 文内で処理が書けます。このときに嬉しいのが、取得した value 変数が判定した型になってくれるので、判定が終わって直ぐに使い始められるところです。

7. self はクロージャー内でも宣言できる

ブログで挙げられていたこれについては、個人的にはちょっと怖いかなって思いました。

session.requestWithCompletion { [weak self] in

    guard let `self` = self else {
        return
    }

    self.showDialog()
}

扱い的に self は予約語で、対して `self` は予約語と同じ名前の変数なので、本来はそれぞれ別もののはずに感じます。それをどちらも同じものとして扱えているこのパターンが、どうにも自然なものには見えないのが理由です。

これをするなら、次のように self ではなく `self` を使って showDialog を呼び出すのが適切かなと思います。これなら仕様的にも妥当なはずです。

session.requestWithCompletion { [weak self] in

    guard let `self` = self else {
        return
    }

    `self`.showDialog()
}

3. respondsToSelector: の使用をやめて、Swift の Availability APIを使用する

これは Swift の嬉しいところですね。実装の有無チェックを型に依存することがなくなったので、より確実に区別できるようになった気がします。

それと respondsToSelector:isKindOfClass: と同じように NSObject が持つ機能なので NSObject を継承したクラスに限ってしか使えないというのもあります。それ以外の場面では使えないので、そういう面でも Swift の #available がとても大事な存在になります。

Swift で Objective-C を活かす

そして respondsToSelector: も、Swift で Objective-C ならではの真価を存分に発揮したい時には便利なように思います。

たとえば Objective-C でお馴染みの respondsToSelector:performSelector: のセットであれば、バージョンに依らずメソッドの有無を判断して対応できるので、OS が目的のバージョンを満たしていなくても、互換機能をカテゴリ拡張や派生クラスで提供されていれば処理を継続できるみたいなメリットもありそうです。

そんな、それぞれの性質の違いを意識して使い分けてみると、さらに楽しいことになるかもわかりません。

2. 可能な限りジェネリクスやプロトコルで実装する

この辺りは、個人的に今すごく興味のあるところで、ジェネリックはすごく便利なんですけど、プロトコル拡張と相まった時に、その辺りの世界観をよく知っていないと思わぬ動きをするかもしれないところがあるので、そこをおさえないまま『可能な限り』使っていくと、思わぬ誤動作を招く恐れもあるかもしれません。型に実装するということについても、大切にしていきたいなと思うところもあったりします。

でもそういうのって、そうやって積極的に使っていって初めて分かるところでもあると思うので、積極的にむしろブログの本題にあるくらいの気概で望むことには超賛同です。ぜひぜひ積極的に、たとえどんな困難があっても、ジェネリックとプロトコルに戯れてみてくれると自分は嬉しいです。

clamp 定義の脱字

さて、ブログの中の clamp 関数ですけれど、たぶん HTML かエディターの仕様の都合で、肝心の型制約が抜けているみたいなので追記しておきます。おそらく <T:Comparable> が抜けているので、それを補うと次の通りの定義になります。

func clamp<T:Comparable>(minValue: T, _ value: T, _ maxValue: T) -> T {

    return min(max(value, minValue), maxValue)
}

ジェネリック関数の引数の場合は『どんな型でもいいけれど、受け取る』みたいな意味合いになるので、それだけだと何もできない型、もうちょっというと何ができるかわからない型になります。今回の例では、せめて minmax で大小比較できる型を使いたいので、せめて大小比較が可能な Comparable 対応の型を受け取るみたいに書いてあげる必要があります。

ちなみに min 関数と max 関数は、次のように Comparable に対応した型に対して動作するように作られています。そのため、少なくとも clamp 関数では Comparable に対応した型を受け取りさえすれば、これらを使った処理を滞りなく遂行できます。

func min<T : Comparable>(x: T, _ y: T) -> T
func max<T : Comparable>(x: T, _ y: T) -> T

おしまい

そんな感じで Swiftならこう書くシリーズ 10選 を拝見して、思うことを綴ってみました。

Swift ならではの書き方みたいなところってたくさんあって面白いので、元ブログでも模索されていたみたいに、ぜひ Swift に慣れてきたら『Objective-C で書くとこうだけど Swift でそれらしく書くとどんな風になるんだろう』みたいなことも意識しながら Swift と戯れてみると、いろんなものが見えてきて Swift がもっと楽しくなると思います。

そんな風にして Swift と遊んで、いろんな人の『Swift ならこう書くシリーズ』を眺められたら、楽しそうに思いました。

149
147
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
149
147