Unetway

Swift - Контроль доступа

Контроль доступа ограничивает доступ к частям вашего кода из кода в других исходных файлах и модулях. Эта функция позволяет скрыть подробности реализации вашего кода и указать предпочтительный интерфейс, через который можно получить доступ к этому коду и использовать его.

Вы можете назначить определенные уровни доступа отдельным типам (классам, структурам и перечислениям), а также свойствам, методам, инициализаторам и индексам, принадлежащим этим типам. Протоколы могут быть ограничены определенным контекстом, как и глобальные константы, переменные и функции.

В дополнение к предложению различных уровней контроля доступа, Swift уменьшает необходимость указывать явные уровни контроля доступа, предоставляя уровни доступа по умолчанию для типичных сценариев. Действительно, если вы пишете одноцелевое приложение, вам, возможно, вообще не нужно указывать явные уровни контроля доступа.

ЗАМЕТКА

Для краткости различные аспекты вашего кода, к которым может применяться управление доступом (свойства, типы, функции и т. д.), Называются «сущностями» в следующих разделах.

Модули и исходные файлы

Модель управления доступом Swift основана на концепции модулей и исходных файлов.

Модуль представляет собой единый блок кода распределения-основы или приложения, построен и поставляется как единое целое , и который может быть импортирован другим модулем с Свифта importключевого слова.

Каждая цель сборки (например, комплект приложений или инфраструктура) в XCode рассматривается как отдельный модуль в Swift. Если вы сгруппируете аспекты кода своего приложения в автономную среду - возможно, для инкапсуляции и повторного использования этого кода в нескольких приложениях - тогда все, что вы определите в этой среде, будет частью отдельного модуля, когда он будет импортирован и использован в приложении, или когда это используется в другой структуре.

Исходный файл является единственным Swift файл исходного кода в модуле (в сущности, один файл в приложении или рамки). Хотя обычно отдельные типы определяются в отдельных исходных файлах, один исходный файл может содержать определения для нескольких типов, функций и т. Д.

Уровни доступа

Swift предоставляет пять различных уровней доступа для объектов в вашем коде. Эти уровни доступа относятся к исходному файлу, в котором определена сущность, а также к модулю, к которому принадлежит исходный файл.

  • Открытый доступ и публичный доступ позволяют использовать сущности в любом исходном файле из их определяющего модуля, а также в исходном файле из другого модуля, который импортирует определяющий модуль. Обычно вы используете открытый или публичный доступ при указании открытого интерфейса к платформе. Разница между открытым и публичным доступом описана ниже.
  • Внутренний доступ позволяет использовать сущности в любом исходном файле из их определяющего модуля, но не в любом исходном файле за пределами этого модуля. Обычно вы используете внутренний доступ при определении внутренней структуры приложения или фреймворка.
  • Частный доступ к файлу ограничивает использование объекта собственным определяющим исходным файлом. Используйте частный доступ к файлу, чтобы скрыть детали реализации определенной части функциональности, когда эти детали используются во всем файле.
  • Частный доступ ограничивает использование объекта прилагаемой декларацией и расширениями этой декларации, которые находятся в том же файле. Используйте частный доступ, чтобы скрыть детали реализации определенной части функциональности, когда эти детали используются только в пределах одной декларации.

Открытый доступ - самый высокий (наименее ограничивающий) уровень доступа, а частный доступ - самый низкий (наиболее ограничивающий) уровень доступа.

Открытый доступ применяется только к классам и членам класса и отличается от открытого доступа следующим образом:

  • Классы с открытым доступом или любым более ограниченным уровнем доступа могут быть разделены на подклассы только в том модуле, в котором они определены.
  • Члены класса с открытым доступом или любым более ограниченным уровнем доступа могут быть переопределены подклассами только внутри модуля, в котором они определены.
  • Открытые классы могут быть разделены на подклассы внутри модуля, в котором они определены, и внутри любого модуля, который импортирует модуль, в котором они определены.
  • Открытые члены класса могут быть переопределены подклассами в модуле, в котором они определены, и в любом модуле, который импортирует модуль, в котором они определены.

Пометка класса как открытого явно указывает на то, что вы рассматривали влияние кода из других модулей, использующих этот класс в качестве суперкласса, и что вы соответствующим образом разработали код своего класса.

Руководящий принцип уровней доступа

Уровни доступа в Swift следуют общему руководящему принципу: ни один объект не может быть определен в терминах другого объекта, который имеет более низкий (более ограничительный) уровень доступа.

Например:

  • Публичная переменная не может быть определена как имеющая внутренний, файловый или закрытый тип, поскольку этот тип может быть недоступен везде, где используется открытая переменная.
  • Функция не может иметь более высокий уровень доступа, чем ее типы параметров и тип возвращаемого значения, потому что функция может использоваться в ситуациях, когда ее составные типы недоступны для окружающего кода.

Конкретные последствия этого руководящего принципа для различных аспектов языка подробно описаны ниже.

Уровни доступа по умолчанию

Все сущности в вашем коде (с некоторыми конкретными исключениями, как описано далее в этой главе) имеют уровень доступа по умолчанию внутренний, если вы сами не указали явный уровень доступа. В результате во многих случаях вам не нужно указывать явный уровень доступа в вашем коде.

Уровни доступа для одноцелевых приложений

Когда вы пишете простое одноцелевое приложение, код в вашем приложении, как правило, самодостаточен в приложении и не должен быть доступен вне модуля приложения. Уровень доступа по умолчанию для внутреннего уже соответствует этому требованию. Поэтому вам не нужно указывать пользовательский уровень доступа. Тем не менее, вы можете пометить некоторые части вашего кода как закрытые или закрытые для того, чтобы скрыть детали их реализации от другого кода в модуле приложения.

Уровни доступа для фреймворков

Когда вы разрабатываете фреймворк, пометьте общедоступный интерфейс к этой фреймворк как открытый или публичный, чтобы его могли просматривать и получать к нему доступ другие модули, например приложение, которое импортирует фреймворк. Этот общедоступный интерфейс является интерфейсом прикладного программирования (или API) для платформы.

ЗАМЕТКА

Любые внутренние детали реализации вашей инфраструктуры могут по-прежнему использовать уровень доступа по умолчанию для внутренней или могут быть помечены как закрытые или закрытые для файлов, если вы хотите скрыть их от других частей внутреннего кода платформы. Вам нужно пометить сущность как открытую или общедоступную, только если вы хотите, чтобы она стала частью API вашей платформы.

Уровни доступа для целей модульного тестирования

Когда вы пишете приложение с целью модульного тестирования, код в вашем приложении должен быть доступен этому модулю для тестирования. По умолчанию только объекты, помеченные как открытые или открытые, доступны для других модулей. Однако цель модульного теста может получить доступ к любому внутреннему объекту, если вы пометите объявление импорта для модуля продукта @testableатрибутом и скомпилируете этот модуль продукта с включенным тестированием.

Синтаксис контроля доступа

Определение уровня доступа для объекта путем помещения одного из openpublicinternalfileprivate, или privateмодификаторов перед интродьюсером юридического лица:

public class SomePublicClass {}

internal class SomeInternalClass {}

fileprivate class SomeFilePrivateClass {}

private class SomePrivateClass {}



public var somePublicVariable = 0

internal let someInternalConstant = 0

fileprivate func someFilePrivateFunction() {}

private func somePrivateFunction() {}

Если не указано иное, уровень доступа по умолчанию является внутренним . Это означает, что SomeInternalClassи someInternalConstantможет быть написано без явного модификатора уровня доступа, и все равно будет иметь внутренний уровень доступа:

class SomeInternalClass {} // implicitly internal

let someInternalConstant = 0 // implicitly internal

Пользовательские типы

Если вы хотите указать явный уровень доступа для пользовательского типа, сделайте это в точке, в которой вы определяете тип. Новый тип может затем использоваться везде, где позволяет его уровень доступа. Например, если вы определяете класс закрытого файла, этот класс можно использовать только как тип свойства или как параметр функции или тип возврата в исходном файле, в котором определен класс закрытого файла.

Уровень управления доступом типа также влияет на уровень доступа по умолчанию этого типа в члены (его свойства, методы, Инициализаторы и индексов). Если вы определяете уровень доступа типа как частный или как частный файл, уровень доступа его членов по умолчанию также будет частным или частным. Если вы определяете уровень доступа типа как внутренний или общедоступный (или используете уровень доступа по умолчанию для внутреннего без указания уровня доступа явно), уровень доступа по умолчанию для членов типа будет внутренним.

ВАЖНО

Открытый тип по умолчанию имеет внутренних участников, а не открытых. Если вы хотите, чтобы член типа был общедоступным, вы должны явно пометить его как таковой. Это требование гарантирует, что общедоступный API для типа - это то, что вы выбираете для публикации, и избегает ошибочного представления внутренней работы типа как публичного API.

public class SomePublicClass { // explicitly public class

public var somePublicProperty = 0 // explicitly public class member

var someInternalProperty = 0 // implicitly internal class member

fileprivate func someFilePrivateMethod() {} // explicitly file-private class member

private func somePrivateMethod() {} // explicitly private class member

}



class SomeInternalClass { // implicitly internal class

var someInternalProperty = 0 // implicitly internal class member

fileprivate func someFilePrivateMethod() {} // explicitly file-private class member

private func somePrivateMethod() {} // explicitly private class member

}



fileprivate class SomeFilePrivateClass { // explicitly file-private class

func someFilePrivateMethod() {} // implicitly file-private class member

private func somePrivateMethod() {} // explicitly private class member

}



private class SomePrivateClass { // explicitly private class

func somePrivateMethod() {} // implicitly private class member

}

Типы кортежей

Уровень доступа для типа кортежа является наиболее ограничивающим уровнем доступа из всех типов, используемых в этом кортеже. Например, если вы составляете кортеж из двух разных типов, один с внутренним доступом, а другой с частным доступом, уровень доступа для этого составного типа кортежа будет закрытым.

ЗАМЕТКА

Типы кортежей не имеют отдельного определения, как классы, структуры, перечисления и функции. Уровень доступа типа кортежа определяется автоматически при использовании типа кортежа и не может быть указан явно.

Типы функций

Уровень доступа для типа функции рассчитывается как наиболее ограничивающий уровень доступа для типов параметров функции и типа возвращаемого значения. Вы должны явно указать уровень доступа как часть определения функции, если вычисленный уровень доступа функции не совпадает с контекстным значением по умолчанию.

В приведенном ниже примере определяется глобальная функция, вызываемая someFunction()без предоставления конкретного модификатора уровня доступа для самой функции. Вы можете ожидать, что эта функция будет иметь уровень доступа по умолчанию «внутренний», но это не так. Фактически, someFunction()не будет компилироваться как написано ниже:

func someFunction() -> (SomeInternalClass, SomePrivateClass) {

// function implementation goes here

}

Возвращаемый тип функции - это тип кортежа, состоящий из двух пользовательских классов. Один из этих классов определяется как внутренний, а другой - как закрытый. Следовательно, общий уровень доступа составного типа кортежа является частным (минимальный уровень доступа для составных типов кортежа).

Поскольку возвращаемый тип функции является закрытым, вы должны пометить общий уровень доступа функции privateмодификатором, чтобы объявление функции было действительным:

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {

// function implementation goes here

}

Это не действует , чтобы отметить определение someFunction()с publicили internalмодификаторов, или использовать настройки по умолчанию внутренней, так как государственные или внутренние пользователи функции не могут иметь соответствующий доступ к закрытому класса , используемого в функции возвращаемого типа.

Типы перечисления

Отдельные случаи перечисления автоматически получают тот же уровень доступа, что и перечисление, к которому они принадлежат. Вы не можете указать другой уровень доступа для отдельных случаев перечисления.

В приведенном ниже примере CompassPointперечисление имеет явный уровень доступа public. Случаи перечисления northsoutheast, и , westследовательно , также имеют уровень доступа общественности:

public enum CompassPoint {

case north

case south

case east

case west

}

Сырые ценности и связанные ценности

Типы, используемые для любых необработанных значений или связанных значений в определении перечисления, должны иметь уровень доступа, по крайней мере, такой же, как уровень доступа перечисления. Например, вы не можете использовать закрытый тип в качестве необработанного типа перечисления с внутренним уровнем доступа.

Вложенные типы

Вложенные типы, определенные в закрытом типе, имеют автоматический уровень доступа private. Вложенные типы, определенные внутри закрытого типа файла, имеют автоматический уровень доступа к закрытому файлу. Вложенные типы, определенные в открытом или внутреннем типе, имеют автоматический уровень доступа к внутреннему. Если вы хотите, чтобы вложенный тип в открытом типе был общедоступным, вы должны явно объявить вложенный тип как открытый.

Наследование

Вы можете создать подкласс любого класса, к которому можно получить доступ в текущем контексте доступа. Подкласс не может иметь более высокий уровень доступа, чем его суперкласс - например, вы не можете написать открытый подкласс внутреннего суперкласса.

Кроме того, вы можете переопределить любой член класса (метод, свойство, инициализатор или индекс), видимый в определенном контексте доступа.

Переопределение может сделать унаследованный член класса более доступным, чем его версия суперкласса. В приведенном ниже примере class Aявляется открытым классом с методом file-private someMethod(). Класс Bявляется подклассом Aс пониженным уровнем доступа «внутренний». Тем не менее, класс Bобеспечивает переопределение someMethod()с уровнем доступа «внутренний», который выше, чем первоначальная реализация someMethod():

public class A {

fileprivate func someMethod() {}

}



internal class B: A {

override internal func someMethod() {}

}

Даже член подкласса может вызвать члена суперкласса, который имеет более низкие права доступа, чем член подкласса, при условии, что вызов члена суперкласса происходит в контексте разрешенного уровня доступа (то есть в том же исходном файле, что и суперкласс для вызова закрытого файла или внутри того же модуля, что и суперкласс для внутреннего вызова члена):

public class A {

fileprivate func someMethod() {}

}



internal class B: A {

override internal func someMethod() {

super.someMethod()

}

}

Поскольку суперкласс Aи подкласс Bопределены в одном и том же исходном файле, это допустимо для Bреализации someMethod()вызова super.someMethod().

Константы, переменные, свойства и подписки

Константа, переменная или свойство не могут быть более публичными, чем их тип. Например, нельзя писать публичное свойство с закрытым типом. Точно так же индекс не может быть более открытым, чем его тип индекса или тип возвращаемого значения.

Если константа, переменная, свойство или индекс используют частный тип, константа, переменная, свойство или индекс также должны быть помечены как private:

private var privateInstance = SomePrivateClass()

Добытчики и сеттеры

Методы получения и установки констант, переменных, свойств и индексов автоматически получают тот же уровень доступа, что и константа, переменная, свойство или индекс, к которому они принадлежат.

Вы можете дать установщику более низкий уровень доступа, чем его соответствующий получатель, чтобы ограничить область чтения-записи этой переменной, свойства или индекса. Вы назначаете более низкий уровень доступа, написав fileprivate(set)private(set)или internal(set)до varили subscriptинтродьюсером.

ЗАМЕТКА

Это правило применяется к сохраненным свойствам, а также к вычисляемым свойствам. Даже если вы не пишете явные методы получения и установки для хранимого свойства, Swift все же синтезирует неявные методы получения и установки для обеспечения доступа к резервному хранилищу хранимого свойства. Используйте fileprivate(set)private(set)и, internal(set)чтобы изменить уровень доступа этого синтезированного установщика точно так же, как для явного установщика в вычисляемом свойстве.

В приведенном ниже примере определяется структура с именем TrackedString, которая отслеживает количество изменений свойства строки:

struct TrackedString {

private(set) var numberOfEdits = 0

var value: String = "" {

didSet {

numberOfEdits += 1

}

}

}

TrackedStringСтруктура определяет сохраненную строку свойство value, с начальным значением ""(пустая строка). Структура также определяет сохраненное целочисленное свойство с именем numberOfEdits, которое используется для отслеживания количества valueмодификаций. Это отслеживание изменений реализовано с помощью didSetнаблюдателя valueсвойства для свойства, которое увеличивается numberOfEditsкаждый раз, когда для valueсвойства устанавливается новое значение.

TrackedStringСтруктура и valueсвойство не дают явный модификатор доступ на уровень, и поэтому они оба получают уровень доступа по умолчанию из внутренних. Однако уровень доступа к numberOfEditsсвойству помечен private(set)модификатором, чтобы указать, что метод получения свойства по-прежнему имеет внутренний уровень доступа по умолчанию, но свойство можно установить только из кода, являющегося частью TrackedStringструктуры. Это позволяет внутренне TrackedStringизменять numberOfEditsсвойство, но представлять свойство как свойство только для чтения, когда оно используется вне определения структуры.

Если вы создадите TrackedStringэкземпляр и измените его строковое значение несколько раз, вы увидите numberOfEditsобновление значения свойства, соответствующее количеству модификаций:

var stringToEdit = TrackedString()

stringToEdit.value = "This string will be tracked."

stringToEdit.value += " This edit will increment numberOfEdits."

stringToEdit.value += " So will this one."

print("The number of edits is \(stringToEdit.numberOfEdits)")

// Prints "The number of edits is 3"

Хотя вы можете запросить текущее значение numberOfEditsсвойства из другого исходного файла, вы не можете изменить свойство из другого исходного файла. Это ограничение защищает детали реализации TrackedStringфункции отслеживания редактирования, в то же время обеспечивая удобный доступ к аспекту этой функции.

Обратите внимание, что при необходимости вы можете назначить явный уровень доступа как для получателя, так и для установщика. В приведенном ниже примере показана версия TrackedStringструктуры, в которой структура определена с явным уровнем доступа public. Поэтому члены структуры (включая numberOfEditsсвойство) по умолчанию имеют внутренний уровень доступа. Вы можете сделать метод получения numberOfEditsсвойств структуры общедоступным, а метод установки свойств - частным, комбинируя модификаторы уровня доступа publicи private(set):

public struct TrackedString {

public private(set) var numberOfEdits = 0

public var value: String = "" {

didSet {

numberOfEdits += 1

}

}

public init() {}

}

Инициализаторы

Пользовательским инициализаторам может быть назначен уровень доступа, меньший или равный типу, который они инициализируют. Единственное исключение - для обязательных инициализаторов. Требуемый инициализатор должен иметь тот же уровень доступа, что и класс, к которому он принадлежит.

Как и с параметрами функций и методов, типы параметров инициализатора не могут быть более закрытыми, чем собственный уровень доступа инициализатора.

Инициализаторы по умолчанию

Swift автоматически предоставляет инициализатор по умолчанию без каких-либо аргументов для какой-либо структуры или базового класса, который предоставляет значения по умолчанию для всех своих свойств и не предоставляет хотя бы одного самого инициализатора.

Инициализатор по умолчанию имеет тот же уровень доступа, что и инициализируемый тип, если только этот тип не определен как public. Для типа, который определен как public, инициализатор по умолчанию считается внутренним. Если вы хотите, чтобы открытый тип был инициализируемым с инициализатором без аргументов при использовании в другом модуле, вы должны явно предоставить открытый инициализатор без аргументов самостоятельно как часть определения типа.

Поэлементные инициализаторы по умолчанию для типов структуры

Поэлементный инициализатор по умолчанию для типа структуры считается закрытым, если любое из сохраненных свойств структуры является закрытым. Аналогично, если какое-либо из сохраненных свойств структуры является закрытым файлом, инициализатор является закрытым файлом. В противном случае инициализатор имеет внутренний уровень доступа.

Как и в случае с инициализатором по умолчанию, описанным выше, если вы хотите, чтобы публичный тип структуры мог инициализироваться с помощью членного инициализатора, когда он используется в другом модуле, вы должны предоставить общедоступный членский инициализатор самостоятельно как часть определения типа.

Протоколы

Если вы хотите назначить явный уровень доступа для типа протокола, сделайте это в тот момент, когда вы определяете протокол. Это позволяет вам создавать протоколы, которые могут быть приняты только в определенном контексте доступа.

Уровень доступа каждого требования в определении протокола автоматически устанавливается на тот же уровень доступа, что и протокол. Вы не можете установить требование протокола на другой уровень доступа, чем протокол, который он поддерживает. Это гарантирует, что все требования протокола будут видны на любом типе, который принимает протокол.

ЗАМЕТКА

Если вы определяете публичный протокол, требования протокола требуют уровня публичного доступа для этих требований, когда они реализуются. Это поведение отличается от других типов, где определение открытого типа подразумевает внутренний уровень доступа для членов типа.

Наследование протокола

Если вы определяете новый протокол, который наследует от существующего протокола, новый протокол может иметь максимально тот же уровень доступа, что и протокол, от которого он наследуется. Например, вы не можете написать открытый протокол, который наследуется от внутреннего протокола.

Соответствие протокола

Тип может соответствовать протоколу с более низким уровнем доступа, чем сам тип. Например, вы можете определить открытый тип, который может использоваться в других модулях, но чье соответствие внутреннему протоколу может использоваться только в определяющем модуле внутреннего протокола.

Контекст, в котором тип соответствует определенному протоколу, является минимумом уровня доступа типа и уровня доступа протокола. Если тип является общедоступным, но протокол, которому он соответствует, является внутренним, то соответствие типа этому протоколу также является внутренним.

Когда вы пишете или расширяете тип для соответствия протоколу, вы должны убедиться, что реализация типа каждого требования протокола имеет как минимум тот же уровень доступа, что и соответствие типа этому протоколу. Например, если открытый тип соответствует внутреннему протоколу, реализация типа каждого требования протокола должна быть как минимум «внутренней».

ЗАМЕТКА

В Swift, как и в Objective-C, соответствие протокола является глобальным - тип не может соответствовать протоколу двумя разными способами в рамках одной и той же программы.

Расширения

Вы можете расширить класс, структуру или перечисление в любом контексте доступа, в котором доступен класс, структура или перечисление. Все члены типа, добавленные в расширение, имеют тот же уровень доступа по умолчанию, что и члены типа, объявленные в расширяемом исходном типе. Если вы расширяете открытый или внутренний тип, все добавляемые вами новые члены по умолчанию имеют уровень доступа по умолчанию - внутренний. Если вы расширяете закрытый тип файла, все добавляемые вами новые члены имеют уровень доступа по умолчанию к закрытому файлу. Если вы расширяете закрытый тип, все добавляемые вами новые члены имеют уровень доступа по умолчанию: закрытый.

Кроме того, вы можете пометить расширение явным модификатором уровня доступа (например, ), чтобы установить новый уровень доступа по умолчанию для всех членов, определенных в расширении. Это новое значение по умолчанию может быть переопределено в расширении для отдельных членов типа.private extension

Вы не можете предоставить явный модификатор уровня доступа для расширения, если вы используете это расширение для добавления соответствия протокола. Вместо этого собственный уровень доступа протокола используется для обеспечения уровня доступа по умолчанию для каждой реализации требования протокола в расширении.

Частные члены в расширениях

Расширения, которые находятся в том же файле, что и класс, структура или перечисление, которые они расширяют, ведут себя так, как будто код в расширении был написан как часть объявления исходного типа. В результате вы можете:

  • Объявите закрытого члена в исходном объявлении и получите доступ к этому члену из расширений в том же файле.
  • Объявите закрытого члена в одном расширении и получите доступ к этому члену из другого расширения в том же файле.
  • Объявите закрытый член в расширении и получите доступ к этому члену из исходного объявления в том же файле.

Такое поведение означает, что вы можете использовать расширения таким же образом для организации своего кода, независимо от того, имеют ли ваши типы частные объекты. Например, дан следующий простой протокол:

protocol SomeProtocol {

func doSomething()

}

Вы можете использовать расширение для добавления соответствия протокола, например так:

struct SomeStruct {

private var privateVariable = 12

}



extension SomeStruct: SomeProtocol {

func doSomething() {

print(privateVariable)

}

}

Дженерики

Уровень доступа для универсального типа или универсальной функции - это минимум уровня доступа к самому универсальному типу или функции и уровню доступа любых ограничений типа на его параметры типа.

Введите псевдонимы

Все псевдонимы типов, которые вы определяете, рассматриваются как отдельные типы для целей контроля доступа. Псевдоним типа может иметь уровень доступа меньше или равный уровню доступа типа псевдонима. Например, псевдоним закрытого типа может быть псевдонимом закрытого, закрытого, внутреннего, открытого или открытого типа, но псевдоним открытого типа не может иметь псевдоним внутреннего, закрытого или закрытого типа.

ЗАМЕТКА

Это правило также применяется к псевдонимам типов для связанных типов, используемых для соответствия требованиям протокола.