讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 5.3 隱私性 >

5.3 隱私性

隱私性(也叫作訪問控制)指的是對正常的作用域規則的顯式修改。第1章曾介紹過下面這個示例:


class Dog {
    var name = \"\"
    private var whatADogSays = \"woof\"
    func bark {
        print(self.whatADogSays)
    }
}
  

這裡的意圖是限制其他對像訪問Dog屬性whatADogSays的方式。它是個私有屬性,主要供Dog類自身內部使用:Dog可以使用self.whatADogSays,不過其他對像甚至都意識不到它的存在。

Swift提供了3級私有性:

internal

默認規則為聲明都是內部的,這意味著它們對所在模塊中的所有文件中的所有代碼可見。這正是相同模塊中的Swift文件能夠自動看到彼此頂層內容的原因所在,你無須為此做任何額外的處理工作(這與C和Objective-C不同,在這兩種語言中,除非顯式通過include或import語句將一個文件展示給別的文件,否則它們之間是看不到彼此的)。

private(比internal範圍要小)

聲明為private的內容只在包含它的文件中可見。這個規則可能與你想像的不太一樣。在某些語言中,private表示對對像聲明是私有的。在Swift中,private並不是這個意思;如果一個文件包含了兩個類,這兩個類都聲明為了private,但它們還是可以看到彼此。因此,我們可以將代碼劃分到多個文件中,遵循一個文件一個類這一通用規則。

public(比internal範圍要大)

聲明為public的內容可在模塊外可見。另一個模塊要先導入這個模塊才能看到其中的內容。不過,一旦另一個模塊導入了這個模塊,那麼它還是看不到這個模塊中沒有顯式聲明為public的內容。如果沒有編寫過任何模塊,那麼你可能並不需要將任何東西聲明為公開的。如果編寫過模塊,那就需要將某些東西聲明為公開的,否則模塊將會毫無作用。

5.3.1  Private聲明

通過將對像成員聲明為private,你也就間接指定了該對像會有哪些公開API。如下示例來自於我所編寫的代碼:


class CancelableTimer: NSObject {
    private var q = dispatch_queue_create(\"timer\",nil)
    private var timer : dispatch_source_t!
    private var firsttime = true
    private var once : Bool
    private var handler :  -> 
    init(once:Bool, handler:->) {
        // ...
    }
    func startWithInterval(interval:Double) {
        // ...
    }
    func cancel {
        // ...
    }
}
  

初始化器init(once:handler:)、startWithInterval:與cancel方法沒有被標記為private,它們都是這個類的公開API。也就是說,你可以隨意調用它們。不過,屬性都是私有的;其他代碼(即該文件外面的代碼)都看不到它們,也無法獲取其值和設置其值。它們純粹都是用於該類方法的內部。它們維護著狀態,不過這些狀態是外部代碼所不知道的。

值得注意的是,隱私性只限於當前文件的級別。比如:


class Cat {
    private var secretName : String?
}
class Dog {
    func nameCat(cat:Cat) {
        cat.secretName = \"Lazybones\"
    }
}
  

為何說上述代碼是合法的呢?Cat的secretName是私有的,那為什麼Dog可以修改它呢?這是因為,私有性並不是單個對象類型級別的;它是文件級別的。我在同一個文件中定義了Cat與Dog,因此它們可以看到彼此的私有成員。

在實際開發中,我們常常會將每個類定義在自己的文件中。事實上,文件名常常是在裡面的類名定義好之後才確定下來的;包含了ViewController類聲明的文件常常會命名為ViewController.swift。不過,這僅僅是個約定而已,並不強求。在Swift中,文件名並沒有什麼意義,同一個模塊中的Swift文件能夠自動看到其他文件中的內容,不必指定其他文件的名字。文件名只不過是為了程序員的方便而已:在Xcode中,我會看到程序文件的列表;因此,當我想要尋找ViewController類聲明時,通過文件名會很方便,我可以查找ViewController.swift文件。

將代碼劃分到不同文件中的真實原因在於確保私有性能夠起作用。比如,我有兩個文件,Cat.swift與Dog.swift:


// Cat.swift:
class Cat {
    private var secretName : String?
}

// Dog.swift:
class Dog {
    func nameCat(cat:Cat) {
        cat.secretName = \"Lazybones\" // compile error
    }
}
  

上述代碼將無法編譯通過:編譯器會報錯「Cat does not have a member named secretName.」。現在,代碼被放到了不同的文件中,因此私有性起作用了。

有些時候,你想將設置變量和讀取變量時的私有性區分開。為了實現這種差別,請將單詞set放在私有性聲明後面的圓括號中。這樣,private(set)var myVar就表示對該變量的設置局限在了同一個文件的代碼中。它並沒有對變量的獲取作任何限制,因此取默認值。與之類似,你可以寫成public private(set)var myVar來使得變量讀取變成公開的,同時保持變量的設置為私有的(可以對subscript函數使用相同的語法)。

5.3.2  Public聲明

如果編寫模塊,那就至少需要將一些對像聲明為公開的,否則導入你的模塊的代碼就無法看到它們。未聲明為公開的其他聲明是內部的,這意味著它們對於模塊來說是私有的。因此,請合理使用public聲明來配置模塊的公開API。

比如,在我編寫的Zotz應用(一個紙牌遊戲)中,用於發牌和描述牌的對象類型與將紙牌形成一副牌的對象被綁定到了名為ZotzDeck的框架中。其中很多類型(如Card和Deck)都被聲明為公開的。不過,很多輔助對像類型則不是;ZotzDeck模塊中的類可以看到並使用它們,不過模塊外的代碼就完全不需要知道它們的存在。

公開的對象類型中的成員本身不會自動變成公開的。如果希望讓某個方法是公開的,你需要將其聲明為公開的。這是個非常棒的默認行為,因為這意味著除非你想這麼做,否則這些成員不會在模塊外被共享(正如Apple所做的,你必須「顯式發佈」對像成員)。

比如,在ZotzDeck模塊中,Card類被聲明為了公開的,但其初始化器卻不是這樣。為什麼呢?因為沒必要。獲得牌的方式是通過初始化Deck來實現的(這意味著要導入ZotzDeck模塊);Deck的初始化器被聲明為了公開的,因此你可以這麼做。沒有任何理由讓牌脫離Deck單獨存在,這都歸功於隱私性規則,你做不到這一點。

5.3.3 隱私性規則

在語言發佈早期,Apple花了不少時間向Swift添加訪問控制,很大程度上是因為編譯器需要知道非常多的規則才能確保相關內容的隱私級別是一致的。比如:

·如果類型是私有的,那麼變量就不能是公開的,因為其他代碼無法使用這種變量。

·如果父類不是公開的,那麼子類也不能是公開的。

·子類可以改變重寫成員的訪問級別,但它看不到父類的私有成員,除非它們聲明在同一個文件中。

諸如此類。我可以列出所有的規則,但不會這麼做,因為沒必要。Swift手冊對此有詳盡的介紹,如果需要可以參考。一般來說,你可能用不上;這本身都是很直觀的,如果違背了規則,編譯器會通過有用的錯誤消息提示你。