Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 460 - アクセス・コントロール(Access Control)

2017年6月8日:Swift 4から「private」の扱いが少し変更

Beginning iPhone Development with Swift 3: Exploring the iOS SDK

Beginning iPhone Development with Swift 3: Exploring the iOS SDK

  • 作者: Molly Maskrey,Kim Topley,David Mark,Fredrik Olsson,JEFF LAMARCHE
  • 出版社/メーカー: Apress
  • 発売日: 2016/11/18
  • メディア: ペーパーバック
  • この商品を含むブログを見る

シングルトン・パターンのコーディングの途中ですが、プロパティに次のアクセスレベルが付けられていました。

private(set) var favorites: [String] 

「private(set)」の勉強をしていなかったので、The Swift Programming Language本で調べたところ、私がまだ読んでいなかった「Access Control」に説明がありました。

The Swift Programming Language本も大部分のところを読み終えていたつもりでしたが、まだまだ抜けている箇所があります。

Access Control(アクセスコントロール)

正本を読めば新しい事が学べるので調べるって重要です。

まず初心者はちゃんと「モジュール」と「ソースファイル」の意味を理解する必要があります。モジュールとは、独立完成した1つのプログラムコード単位であり、販売できるアプリケーションや、Swiftの「import」キーワードを使って取り込めるフレームワークなどがこれに当たります。

ソースファイルは、モジュールに含まれる単独のSwiftファイルのことで、幾つも定義や型、そしてメソッドが含まれます。

アクセスレベル

  1. open
  2. public
  3. internal(デフォルト)
  4. fileprivate
  5. private

まず「open」は、基本的にclassとclassメンバーで設定できます。Swift 3から加わった新しいアクセスレベルになっていますが、Swift 2までの「public」と同等です。最もアクセスレベルが低く、モジュール外の他のモジュールからアクセス可能で、定義の書き換えもできます。

public」は、Swift 2から定義が変わりました。アクセスレベルはサブクラス限定になり、サブクラス内であればクラスメンバの書き換え(override)も可能です。

API提供のためのフレームワーク開発をしない限り、要するにiOSアプリの閉じられた環境を開発するだけなら、モジュール内だけに制限されている「internal」に設定していればいいんです。

しかし、今まで「internal」というキーワードを使ったことがありません。どうしてなのでしょう? デフォルトで、何も付けないと勝手に「internal」になっていたんです。そんなことも知りませんでした(^^;) デフォルトのアクセスレベルを変更することが可能です。

そして新しく新設された「fileprivate」は、Swift 2までの「private」と同等になります。有効性はソースファイル内に限定されます。Swift 2までの言語使用であれいば、playgroundで「private」設定しても同一ファイル内のため制限をかけることができませんでした。

private」は定義が変わりました。1つの宣言内( { } )と、同一ソースファイルにある「extension」宣言内に限られます。Swift 4から少し扱いが変わっているので最後に説明します。

playgroundを使って「fileprivate(set)」の例で説明します。

struct TrackedString {
    fileprivate(set) var numberOfEdits = 0
    var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
}

「fileprivate」設定の定義は以下の通りです。

Swiftにおけるfileprivateアクセスは、ソースファイル全体まで及びます。同一ソースファイル内であれば、どのようなfileprivateメンバーにもアクセスできますが、別のソースファイルに定義されているextensionファイルからfileprivateメンバにアクセスはできないということです。

先ず、上記の例文全体を説明してから解説します。

構造体TrackedStringは、「struct」に何もキーワードがついていないので、アクセスレベルは「internal」です。

numbersOfEdits変数は、プロパティ・オブザーバー。実は、計算型プロパティでも同様ですが、この手のプロパティは willSet、didSetもしくは、get、setのように「getter」と「setter」を持ちます。

「fileprivate(set)」を考えます。これはプロパティ・オブザーバーと計算型プロパティにしか使えないアクセス・コントロールです。「setter」だけプライベートで、「getter」はデフォルトのアクセス制限できるんです、この場合は「internal」です。ということはプロジェクト内でnumberOfEditsを参照することはできても、プロジェクト内でもファイル外からnumberOfUnitsを変更することができないという意味です。

plyagorundでコードを試す場合、同一ファイル(ソースファイル)内ということで、fileprivateのアクセスレベルだと制限はかかりません。

f:id:yataiblue:20161202152319j:plain

text.numberOfEditsプロパティにアクセスして変更しても問題はありません。しかし、このプロパティのアクセス制限を「private」に切り替えると...

f:id:yataiblue:20161202152444j:plain

このようにスコープ外からアクセスしようとするとエラーが発生します。

Swift 4でprivateの扱いに変更

struct Date {
    fileprivate let secondsSinceReferenceDate: Double
    // Swift 3まではこういうケースで「fileprivate」の設定
    // にしないとエラーになっていました。
}

extension Date: Equatable {
    static func ==(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate 
                   == rhs.secondsSinceReferenceDate
    }
}

extension Date: Comparable {
    static func <(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate 
                   < rhs.secondsSinceReferenceDate
    }
}

昨今のプロトコール指向プログラミングの流れから、「extension」を多用するプログラミングスタイルに変化しているためSwift 3時代の非常に制限された「private」の設定ではコーディングが煩雑になります。ということでSwift 4から少し制限が緩くなり同一ソースファイル内なら「extension」宣言内でも使用できるようになりました。

struct Date {
    private let secondsSinceReferenceDate: Double
    // Xcode 8ではエラーになりますが、
    // Xcode 9から問題なしです!
}

extension Date: Equatable {
    static func ==(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate 
                   == rhs.secondsSinceReferenceDate
    }
}

extension Date: Comparable {
    static func <(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate 
                   < rhs.secondsSinceReferenceDate
    }
}