ちょっと話題の記事

[Swift 4] privateとfileprivateの挙動が変わりますよというお話

2017.09.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

モバイルアプリサービス部の中安です。

Swift3でスコープの改変が行われ、「private」に加えて、「fileprivate」というスコープが加わりました。 Swift4ではさらにスコープの改変が行われるそうで、fileprivate の役割がまた少し変わってきます。

自分も忘れてしまわぬよう、しっかりと予習復習をしておこうとブログにしたためることにしました。

Swift3での挙動

fileprivate は、簡単にいうと「同一ファイル内であればアクセスできる」というスコープとして登場しました。 ファイルひとつをモジュールとして考えたときに、その内部では隠蔽されなくてよいだろうという考え方なんだろうと思います。 つまりは、別のクラスや構造体などであっても、その同一ファイル内であれば見ることができるということですね。

具体的にソースで見てみます。

class Hoge {
    
    private var privateText = ""
    fileprivate var fileprivateText = ""
    
    func doSomethingInDeclaration() {
        print(privateText)
        print(fileprivateText)
    }
}

// Hogeの拡張
extension Hoge {
    
    func doSomethingInExtension() {
        print(privateText)
        print(fileprivateText)
    }
}

// Hogeの継承クラス
class HogeChild: Hoge {
    
    func doSomethingInInheritance() {
        print(privateText)
        print(fileprivateText)
    }
}

// Hogeとは無関係のクラス
class Fuga {
    
    func doSomethingInOtherClass() {
        let hoge = Hoge()
        print(hoge.privateText)
        print(hoge.fileprivateText)
    }
}

Hoge というクラスを用意し、private なプロパティと、 fileprivate なプロパティを用意しました。 そして、同一ファイル内に Hoge に関わる3つの異なる定義をしています。

  • Hoge のextension
  • Hoge の継承クラス
  • Hoge とは関係のない別クラス

これらの各定義から Hoge のストアドなプロパティ値にアクセスしようとしてるのが分かると思います。

こういった場合にはどこでスコープエラーが起きるでしょうか。結果は下記のようになります。

private fileprivate
Hoge
Hoge(extension)
HogeChild
Fuga

上表のように private は Hoge 自身以外すべてアクセス不可でエラーになります。 すなわち、Hoge の privateなプロパティは Hoge の定義内でのみしかアクセスできません。

しかし、fileprivateは軒並みすべてアクセスすることができます。 Hoge とは無関係である Fuga クラスにおいても、Hoge の中身を覗けることになります。

Swift4での挙動

では、Swift4ではどのように変更されたでしょうか。 先程の例と見比べてやってみます。

private fileprivate
Hoge
Hoge(extension)
HogeChild
Fuga

異なる箇所は、Hoge の extension で privateプロパティにアクセスできるようになっているというところです。

Swift4では何故このような変更が行われたのでしょうか。その詳しい内容は、こちらの Improve Interaction Between private Declarations and Extensions ほうに載ってはいますが、 「拡張を多く用いるのがSwiftのはずなのに、これじゃ fileprivate ばかりになっちゃうよ」というところなのでしょうか。たしかにSwift2からSwift3になったとき、自動コンバートをすると軒並み fileprivate に切り替えられた記憶があります。

この変更により、Swift4では同一ファイル内の extension は下記のようにできます。

class Hoge {
    
    private var data = [String : Any]()
    
    init(data: [String : Any]) {
        self.data = data
    }
}

extension Hoge {
    
    func value(of key: String) -> Any? {
        return data[key] // ← Swift3だとエラーだが、Swift4ならOK
    }
}

let hoge = Hoge(data: ["name" : "勇者", "hp" : 20])

print(hoge.value(of: "hp"))
print(hoge.value(of: "exp"))

これをSwift3では fileprivate var data = [String : Any]() と宣言してやらなくてはなりませんでした。

この変更で、private の意味からしても分かりやすくなったように思えます。

以上です。

関連記事

[iOS 11] Swift 4は前バージョンから何が変わったか比較した