Swift – Access Control

Xcode 5 beta 4 přinesl dlouho očekávánou fičuru zvanou Access Control neboli pro Javisty obdoba private , protected  a public . Hurá 🙂

Různé úrovně přístupu je možné nastavit u tříd, struktur, výčtů a samozřejmě u properties, metod, inicializátoru a subscriptů. To samé platí také pro protokoly, globální konstanty, proměnné a funkce.

Než se do toho ponoříme, nadefinujeme si dva pojmy.

Modul –  modul je framework nebo aplikace. Jinak řečeno, každý build target je separátní modul. Pokud v aplikaci použiju framework, dohromady to netvoří jeden modul, ale pořád jsou to dva separátní moduly – framework a aplikace – bez ohledu na to, že framework je v aplikaci používán.

Soubor – fakticky je to opravdu jeden soubor, který je součástí frameworku nebo aplikace, tj. modulu.

Swift nabízí tři modifikátory přístupu:

  • public  – viditelné v rámci modulu i mimo něj,
  • internal  – viditelné pouze v rámci modulu a to z jakéhokoli souboru, který je součástí stejného modulu,
  • private  – viditelné pouze v rámci jednoho souboru.

By default, většina věcí je internal . To z toho důvodu, abychom nemuseli tolik přepisovat již existující kód. Nejen, ale to se dozvíte níže.

Syntax

public class PublicClass {}
internal class InternalClass {}
private class PrivateClass {}

public var publicVariable = 0
internal let internalConstant = 0
private func privateFunction() {}

A protože by default je většina věcí internal , InternalClass  a internalConstant  můžeme zkrátit na:

class InternalClass {}
let internalConstant = 0

Vlastní typy

A hned je tu první výjimka. Member (= property, metoda, …) má by default stejný modifikátor přístupu jako celý typ (třída, struktura, výčet). Až na typ, který je public . Tam je všechno internal :

  • public  typ má všechny membery by default internal ,
  • internal  typ má všechny membery by default internal ,
  • private  typ má všechny membery by default private .

Proč je to u public  jinak? Aby nedocházelo k nechtěnému zveřejnění API, tzn. je to opt-in ne opt-out. Neboli to funguje takto:

public class PublicClass {
  var implicitlyInternal = 0
  public var explicitlyPublic = 0
  private var explicitlyPrivate = 0
}

internal class InternalClass { // internal muzeme vynechat
  var implicitlyInternal = 0
  private var explicitlyPrivate = 0
}

private class PrivateClass {
  var implicitlyPrivate = 0
}

Tuples

V tomto se případě se použije více restriktivní přístup. Kombinace internal  a private  vyústí v private .

Funkce

Stejně jako u tuples se odvozuje z kontextu – typy vstupních parametrů a návratové hodnoty.

internal struct InternalStruct {
}

private struct PrivateStruct {
}

func hey() -> ( InternalStruct, PrivateStruct ) { 
}

Výše uvedený příklad nejde přeložit. Protože úroveň přístupu funkce hey()  je by default internal , ale z kontextu (typy parametrů, návratové hodnoty) byla odvozena úroveň private  a proto musíme explicitně uvést modifikátor private . Správně je to tedy takto:

private func hey() -> ( InternalStruct, PrivateStruct ) { 
}

Výčty

Přístup k výčtu je možné nastavit jako k celku. Nelze nastavovat jednotlivé case . A typy pro raw & asociované hodnoty musí mít minimálně stejnou úroveň přístupu jako výčet. Jinak řečeno, internal  výčet nemůže mít typ asociované hodnoty s úrovní přístupu private .

Vnořené typy

Tady to funguje stejně jako u memberů. Neboli private  -> private , internal  -> internal , public  -> internal .

public class PublicClass {
  class ImplicitlyInternalClass {
  }
}

Pokud chci aby byl měl vnořený typ úroveň přístupu public , musím tak explicitně učinit.

public class PublicClass {
  public class ExplicitlyPublicClass {
  }
}

Dědičnost

Potomka třídy mohu vytvořit v případě, že třída je v daném kontextu viditelná. Zároveň potomek nemůže mít méně restriktivní viditelnost než má samotná třída. Tzn. nemůžu udělat potomka s úrovní přístupu public  pokud má třída úroveň přístupu internal . Dále je možné přetížit membery, které jsou viditelné v daném kontextu.

Při přetěžování memberů neplatí pravidlo s méně restriktivní viditelností. Například v jednom souboru mohu přetížit privátní metodu třídy a udělat z ní třeba internal .

public class BaseController {
  private func help() {
  }
}

internal class LoginController: BaseController {
  override internal func help() {
  }
}

A pokud je to v daném kontextu možné, může internal  funkce volat private  funkci předka.

public class BaseController {
  private func help() {
  }
}

internal class LoginController: BaseController {
  override internal func help() {
    super.help()
  }
}

Obě třídy jsou definovány v jednom souboru, proto LoginController  vidí privátní metody třídy BaseController .

Konstanty, proměnné, properties a subscripts

Modifikátor přístupu musí být stejný a nebo restriktivnější než modifikátor přístupu použitého typu. Neboli nemohu vytvořit public  proměnnou jejíž typ má modifikátor přístupu internal  nebo private . V případě private  tak musím explicitně učinit:

private class PrivateClass {
}

private var c = PrivateClass()

Pokud bych před var c = …  nepřidal private , nešlo by to přeložit.

Setter u properties může být restriktivnější než getter. To platí i u subscriptu. Dělá se to tak, že se před var  / subscript  napíše private(set)  nebo internal(set) .

// implicitne internal
struct Game {
  // getter "dedi" implicitni internal, ale setter je private
  private(set) var score = 0
}

Inicializátory

Inicializátory musí mít stejnou nebo restriktivnější úroveň přístupu jako třída. Výjimka je required  inicializátor, který musí mít stejnou úroveň přístupu. A opět platí to samé co u funkcí – typy parametrů nemůžou mít restriktivnější úroveň přístupu než naše třída.

Defaultní inicializátory mají stejnou úroveň přístupu jako třída samotná. Výjimkou je opět public  typ kde je default inicializátor internal . Pokud ho chceme public , musíme si ho explicitně vytvořit.

U struktur a member wise inicializátorů je to trochu jinak. Pokud je některý member private , inicializátor je private . V opačném případě je internal . Opět, pokud ho chceme public , musíme si ho explicitně vytvořit.

Protokoly

Protokol je typ a tak u něj také můžeme definovat úroveň přístupu. S jedinou výjimkou – nelze tak činit u požadavků protokolu – ty mají stejnou úroveň přístupu jako protokol samotný.

A ještě jedna menší změna – pokud je protokol public , vyžaduje se, aby implementace metod, properties, … z protokolu byla také public . Tzn. tady neplatí public  -> internal .

Při dědění u protokolů se vyžaduje, aby úroveň přístupu potomka byla stejná nebo restriktivnější než u rodiče. Tzn. nemůžu vytvořit public  protokol z rodiče, který je internal .

Implementace protokolů – public  třída může implementovat private  protokol, resp. protokol s restriktivnější úrovní přístupu. Akorát bude záležet na kontextu použití třídy. Tzn. v private  kontextu můžu castnout na typ protokolu, vidím, že protokol je implementován, ale v public  kontextu to neuvidím a nemůžu castnout, jako kdyby ho třída neimplementovala. Kontext z pohledu třídy a modulu. V souboru to uvidím, v jiném souboru / modulu už ne.

Rozšíření

Pokud explicitně neuvedu úroveň přístupu, rozšíření má stejnou úroveň přístupu jako typ, který rozšiřuju. To platí i pro přidané membery. Tzn. pokud rozšiřuju public  typ, member je implicitně internal , apod. Případě mohu explicitně uvést úroveň přístupu pro rozšíření a pro jednotlivé membery.

Výjimka – pokud rozšířením implementuju protokol, úroveň přístupu neuvádím a používá se úroveň přístupu protokolu.

Generika

U generik musí být minimální úroveň přístupu stejná jako u typu a u generických funkcí nesmí být vyšší (= méně restriktivní) než nejnižší z použitých typů v parametrech a návratové hodnotě.

Aliasy

Alias může být restriktivnější, ale ne méně restriktivní. Alias na typ internal  nemůže být public , apod.

Tak a teď už nás čeká jenom kapitola Advanced Operators a máme hotovo 🙂