Расширенные операторы
Определяйте пользовательские операторы, выполняйте побитовые операции и используйте синтаксис билдера.
Помимо операторов, описанных в BasicOperators, Swift предоставляет несколько расширенных операторов, которые выполняют более сложные манипуляции со значениями. К ним относятся все побитовые и побитовые сдвиговые операторы, с которыми вы знакомы по C и Objective-C.
В отличие от арифметических операторов в C, арифметические операторы в Swift по умолчанию не переполняются. Вероятность переполнения обрабатывается и сообщается как ошибка. Чтобы включить поведение переполнения, используйте второй набор арифметических операторов Swift, которые по умолчанию переполняются, такие как оператор переполнения сложения (&+). Все эти операторы переполнения начинаются с амперсанда (&).
При определении собственных структур, классов и перечислений может быть полезно предоставить собственные реализации стандартных операторов Swift для этих пользовательских типов. Swift упрощает предоставление настраиваемых реализаций этих операторов и определение точного поведения для каждого создаваемого вами типа.
Вы не ограничены предопределенными операторами. Swift предоставляет вам свободу определения собственных пользовательских операторов с префиксом, постфиксом, инфиксом и присваивания, с настраиваемыми значениями приоритета и ассоциативности. Эти операторы могут использоваться и применяться в вашем коде как любые предопределенные операторы, и вы даже можете расширить существующие типы, чтобы они поддерживали настраиваемые операторы, которые вы определили.
Побитовые операторы
Побитовые операторы позволяют манипулировать отдельными битами данных в структуре данных. Они часто используются в программировании низкого уровня, например, в программировании графики и создании драйверов устройств. Побитовые операторы также могут быть полезны при работе с сырыми данными из внешних источников, например, при кодировании и декодировании данных для обмена по пользовательскому протоколу.
Swift поддерживает все побитовые операторы, присутствующие в C, как описано ниже.
Оператор побитового НЕ
Оператор побитового НЕ (~) инвертирует все биты в числе:
Оператор побитового НЕ является префиксным оператором и появляется непосредственно перед значением, на котором он действует, без пробелов:
let initialBits: UInt8 = 0b00001111 let invertedBits = ~initialBits // equals 11110000
UInt8 целые числа имеют восемь бит и могут хранить любое значение от 0 до 255. В этом примере целое число UInt8 инициализируется двоичным значением 00001111, у которого первые четыре бита установлены в 0, а вторые четыре бита установлены в 1. Это эквивалентно десятичному значению 15.
Затем оператор побитового НЕ используется для создания новой константы под названием invertedBits, которая равна initialBits, но со всеми инвертированными битами. Нули становятся единицами, а единицы — нулями. Значение invertedBits равно 11110000, что эквивалентно беззнаковому десятичному значению 240.
Оператор побитового И
Оператор побитового И (&) комбинирует биты двух чисел. Он возвращает новое число, биты которого установлены в 1 только если биты были равны 1 в обоих входных числах:
В приведенном ниже примере значения firstSixBits и lastSixBits оба имеют четыре средних бита, равные 1. Оператор побитового И комбинирует их, чтобы получить число 00111100, которое равно беззнаковому десятичному значению 60:
let firstSixBits: UInt8 = 0b11111100 let lastSixBits: UInt8 = 0b00111111 let middleFourBits = firstSixBits & lastSixBits // equals 00111100
Оператор побитового ИЛИ
Оператор побитового ИЛИ (|) сравнивает биты двух чисел. Оператор возвращает новое число, биты которого установлены в 1, если биты равны 1 в любом из входных чисел:
В примере ниже значения someBits и moreBits имеют разные биты, установленные в 1. Оператор побитового ИЛИ объединяет их, чтобы получить число 11111110, что равно беззнаковому десятичному числу 254:
let someBits: UInt8 = 0b10110010 let moreBits: UInt8 = 0b01011110 let combinedbits = someBits | moreBits // equals 11111110
Оператор побитового ИСКЛЮЧАЮЩЕГО ИЛИ
Оператор побитового ИСКЛЮЧАЮЩЕГО ИЛИ (^) сравнивает биты двух чисел. Оператор возвращает новое число, биты которого установлены в 1, где входные биты отличаются, и в 0, где входные биты одинаковы:
В приведенном ниже примере значения firstBits и otherBits каждый имеют бит, установленный в 1 в позиции, отсутствующей у другого. Оператор побитового ИСКЛЮЧАЮЩЕГО ИЛИ устанавливает оба этих бита в 1 в выходном значении. Все остальные биты в firstBits и otherBits совпадают и установлены в 0 в выходном значении:
let firstBits: UInt8 = 0b00010100 let otherBits: UInt8 = 0b00000101 let outputBits = firstBits ^ otherBits // equals 00010001
Операторы побитового сдвига влево и вправо
Оператор побитового сдвига влево (<<) и оператор побитового сдвига вправо (>>) перемещают все биты числа влево или вправо на определенное количество позиций в соответствии с правилами, определенными ниже.
Побитовые сдвиги влево и вправо имеют эффект умножения или деления целого числа на множитель два. Сдвиг битов целого числа влево на одну позицию удваивает его значение, а сдвиг вправо на одну позицию делит его значение пополам.
Поведение сдвига для беззнаковых целых чисел
Поведение сдвига для беззнаковых целых чисел следующее:
- Существующие биты перемещаются влево или вправо на затребованное количество позиций.
- Любые биты, перемещенные за пределы области хранения целого числа, отбрасываются.
- В оставшиеся места после перемещения исходных битов влево или вправо вставляются нули.
Этот подход известен как логический сдвиг.
На рисунке ниже показаны результаты для 11111111 << 1 (которое равно 11111111, сдвинутому влево на 1 позицию), и 11111111 >> 1 (которое равно 11111111, сдвинутому вправо на 1 позицию). Зеленые числа сдвигаются, серые отбрасываются, а розовые нули вставляются:
Вот как выглядит побитовый сдвиг на Swift:
let shiftBits: UInt8 = 4 // 00000100 in binary shiftBits << 1 // 00001000 shiftBits << 2 // 00010000 shiftBits << 5 // 10000000 shiftBits << 6 // 00000000 shiftBits >> 2 // 00000001
Вы можете использовать побитовый сдвиг для кодирования и декодирования значений в других типах данных:
let pink: UInt32 = 0xCC6699 let redComponent = (pink & 0xFF0000) >> 16 // redComponent is 0xCC, or 204 let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent is 0x66, or 102 let blueComponent = pink & 0x0000FF // blueComponent is 0x99, or 153
В этом примере константа UInt32 под названием pink хранит значение цвета в формате Cascading Style Sheets для цвета розового. Значение цвета CSS #CC6699 записывается как 0xCC6699 в шестнадцатеричной системе счисления Swift. Этот цвет затем разлагается на его компоненты красного (CC), зеленого (66) и синего (99) с помощью оператора побитового И (&) и оператора побитового сдвига вправо (>>).
Компонент красного получается путем выполнения побитового И между числами 0xCC6699 и 0xFF0000. Нули в 0xFF0000 фактически «маскируют» второй и третий байты 0xCC6699, что приводит к игнорированию 6699 и получению результата 0xCC0000.
Это число затем сдвигается вправо на 16 позиций (>> 16). Каждая пара символов в шестнадцатеричном числе использует 8 бит, поэтому сдвиг на 16 позиций вправо преобразует 0xCC0000 в 0x0000CC. Это то же самое, что и 0xCC, которое имеет десятичное значение 204.
Аналогично, компонент зеленого получается путем выполнения побитового И между числами 0xCC6699 и 0x00FF00, что дает выходное значение 0x006600. Это выходное значение затем сдвигается вправо на восемь позиций, давая значение 0x66, которое имеет десятичное значение 102.
Наконец, компонент синего получается путем выполнения побитового И между числами 0xCC6699 и 0x0000FF, что дает выходное значение 0x000099. Поскольку 0x000099 уже равно 0x99, которое имеет десятичное значение 153, это значение используется без сдвига вправо.
Поведение сдвига для знаковых целых чисел
Поведение сдвига для знаковых целых чисел сложнее, чем для беззнаковых, из-за способа представления знаковых целых чисел в двоичном формате. (Примеры ниже основаны на 8-битных знакомых целых числах для простоты, но те же принципы применимы для знаковых целых чисел любого размера.)
Знаковые целые числа используют свой первый бит (известный как бит знака) для указания, является ли целое число положительным или отрицательным. Бит знака 0 означает положительное, а бит знака 1 означает отрицательное.
Остальные биты (известные как биты значения) хранят фактическое значение. Положительные числа хранятся точно так же, как и для беззнаковых целых чисел, начиная с 0. Вот как выглядят биты внутри Int8 для числа 4:
Бит знака равен 0 (что означает «положительно»), а семь битов значения просто число 4, записанное в двоичном формате.
Отрицательные числа, однако, хранятся иначе. Они хранятся путем вычитания их абсолютного значения из 2 в степени n, где n — количество битов значения. Восьмибитное число имеет семь битов значения, поэтому это означает 2 в степени 7 или 128.
Вот как выглядят биты внутри Int8 для числа -4:
На этот раз бит знака равен 1 (что означает «отрицательно»), а семь битов значения имеют двоичное значение 124 (которое равно 128 - 4):
Эта кодировка для отрицательных чисел известна как представление в дополнениях до двух. Она может показаться необычным способом представления отрицательных чисел, но у неё есть несколько преимуществ.
Во-первых, вы можете добавить -1 к -4, просто выполнив стандартное двоичное сложение всех восьми битов (включая бит знака) и отбросив всё, что не поместится в восемь бит после завершения:
Во-вторых, представление в дополнениях до двух также позволяет сдвигать биты отрицательных чисел влево и вправо как положительных чисел и по-прежнему удваивать их при каждом сдвиге влево или делить пополам при каждом сдвиге вправо. Для достижения этого используется дополнительное правило, когда знаковые целые числа сдвигаются вправо: когда вы сдвигаете знакомые целые числа вправо, применяйте те же правила, что и для беззнаковых целых чисел, но заполните любые пустые биты слева битом знака вместо нуля.
Это действие гарантирует, что у знакомых целых чисел сохраняется тот же знак после сдвига вправо, и это известно как арифметический сдвиг.
Из-за особого способа хранения положительных и отрицательных чисел сдвиг любого из них вправо приближает их к нулю. Сохранение бита знака при этом сдвиге означает, что отрицательные целые числа остаются отрицательными по мере приближения их значения к нулю.
Операторы переполнения
Если вы попытаетесь вставить число в целочисленную константу или переменную, которая не может вместить это значение, по умолчанию Swift сообщит об ошибке, а не позволит создать недопустимое значение. Это поведение обеспечивает дополнительную безопасность при работе с слишком большими или слишком маленькими числами.
Например, тип целых чисел Int16 может хранить любое знаковое целое число между -32768 и 32767. Попытка установить константу или переменную Int16 на число, выходящее за этот диапазон, приводит к ошибке:
var potentialOverflow = Int16.max // potentialOverflow equals 32767, which is the maximum value an Int16 can hold potentialOverflow += 1 // this causes an error
Предоставление обработки ошибок, когда значения становятся слишком большими или слишком маленькими, обеспечивает большую гибкость при кодировании граничных условий.
Однако, когда вы специально хотите, чтобы условие переполнения обрезало количество доступных битов, вы можете выбрать это поведение вместо возникновения ошибки. Swift предоставляет три арифметических оператора переполнения, которые выбирают поведение переполнения для целочисленных вычислений. Все эти операторы начинаются с амперсанда (&):
- Сложение с переполнением (
&+) - Вычитание с переполнением (
&-) - Умножение с переполнением (
&*)
Переполнение значения
Числа могут переполняться как в положительном, так и в отрицательном направлении.
Вот пример того, что происходит, когда беззнаковому целому числу разрешено переполняться в положительном направлении, используя оператор сложения с переполнением (&+):
var unsignedOverflow = UInt8.max // unsignedOverflow equals 255, which is the maximum value a UInt8 can hold unsignedOverflow = unsignedOverflow &+ 1 // unsignedOverflow is now equal to 0
Переменная unsignedOverflow инициализируется максимальным значением, которое может хранить UInt8 (255 или 11111111 в двоичном формате). Затем она увеличивается на 1 с помощью оператора сложения с переполнением (&+). Это сдвигает его двоичное представление немного за пределы размера, который может хранить UInt8, вызывая переполнение за пределы границ, как показано на диаграмме ниже. Значение, которое остается в границах UInt8 после сложения с переполнением, равно 00000000, или нулю.
Нечто подобное происходит, когда беззнаковому целому числу разрешено переполняться в отрицательном направлении. Вот пример с использованием оператора вычитания с переполнением (&-):
var unsignedOverflow = UInt8.min // unsignedOverflow equals 0, which is the minimum value a UInt8 can hold unsignedOverflow = unsignedOverflow &- 1 // unsignedOverflow is now equal to 255
Минимальное значение, которое может хранить UInt8, равно нулю или 00000000 в двоичном формате. Если вы вычтите 1 из 00000000 с использованием оператора вычитания с переполнением (&-), число переполнится и вернётся к 11111111 или 255 в десятичном формате.
Переполнение также происходит для знакомых целых чисел. Все сложения и вычитания для знакомых целых чисел выполняются в двоичном формате, при этом бит знака включается в числа, складываемые или вычитаемые, как описано в <doc:AdvancedOperators#Bitwise-Left-and-Right-Shift-Operators>.
var signedOverflow = Int8.min // signedOverflow equals -128, which is the minimum value an Int8 can hold signedOverflow = signedOverflow &- 1 // signedOverflow is now equal to 127
Минимальное значение, которое может хранить Int8, равно -128 или 10000000 в двоичном формате. Вычитание 1 из этого двоичного числа с помощью оператора переполнения даёт двоичное значение 01111111, которое меняет бит знака и даёт положительное 127, максимальное положительное значение, которое может хранить Int8.
Для знакомых и беззнаковых целых чисел переполнение в положительном направлении происходит по кругу от максимального допустимого целого значения обратно к минимальному, а переполнение в отрицательном направлении происходит по кругу от минимального значения к максимальному.
Приоритет и ассоциативность
Приоритет оператора придаёт некоторым операторам более высокий приоритет, чем другим; эти операторы применяются первыми.
Ассоциативность оператора определяет, как группируются операторы с одинаковым приоритетом — либо группируются слева направо, либо справа налево. Представьте себе, что это означает «они ассоциируются с выражением слева от них» или «они ассоциируются с выражением справа от них».
При вычислении составного выражения важно учитывать приоритет и ассоциативность каждого оператора. Например, приоритет операторов объясняет, почему следующее выражение равно 17.
2 + 3 % 4 * 5 // this equals 17
Если читать строго слева направо, можно ожидать, что выражение будет вычислено следующим образом:
-
2плюс3равно5 -
5остаток от деления на4равен1 -
1умножить на5равно5
Однако фактический ответ равен 17, а не 5. Операторы с более высоким приоритетом вычисляются перед операторами с более низким приоритетом. В Swift, как и в C, оператор остатка от деления (%) и оператор умножения (*) имеют более высокий приоритет, чем оператор сложения (+). В результате они оба вычисляются до того, как рассматривается сложение.
Однако остаток от деления и умножение имеют одинаковый приоритет друг с другом. Чтобы определить точный порядок вычислений, необходимо также учесть их ассоциативность. Остаток от деления и умножение оба ассоциируются с выражением слева от них. Представьте себе добавление неявных скобок вокруг этих частей выражения, начиная слева:
2 + ((3 % 4) * 5)
(3 % 4) это 3, поэтому это эквивалентно:
2 + (3 * 5)
(3 * 5) это 15, поэтому это эквивалентно:
2 + 15
Это вычисление даёт окончательный ответ 17.
Дополнительную информацию об операторах, предоставляемых стандартной библиотекой Swift, включая полный список групп приоритетов операторов и настроек ассоциативности, см. в разделе Операторы.
Примечание: приоритеты операторов и правила ассоциативности в Swift проще и предсказуемее, чем в C и Objective-C. Однако это означает, что они не совсем такие же, как в языках, основанных на C. Будьте внимательны, чтобы убедиться, что взаимодействия операторов всё ещё работают так, как вы хотите, при переносе существующего кода в Swift.
Методы операторов
Классы и структуры могут предоставлять собственные реализации существующих операторов. Это известно как перегрузка существующих операторов.
В примере ниже показано, как реализовать арифметический оператор сложения (+) для пользовательской структуры. Арифметический оператор сложения является бинарным оператором, потому что он работает с двумя объектами, и это инфиксный оператор, потому что он появляется между этими двумя объектами.
В примере определена структура Vector2D для двухмерного вектора позиции (x, y), за которым следует определение метода оператора для сложения экземпляров структуры Vector2D:
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
} Метод оператора определен как метод типа для Vector2D, с именем метода, соответствующим перегружаемому оператору (+). Поскольку сложение не является существенной частью поведения вектора, метод типа определен в расширении Vector2D, а не в основном объявлении структуры Vector2D. Поскольку арифметический оператор сложения является бинарным оператором, этот метод оператора принимает два входных параметра типа Vector2D и возвращает одно выходное значение, также типа Vector2D.
В этой реализации входные параметры называются left и right, чтобы представить экземпляры Vector2D, которые будут слева и справа от оператора +. Метод возвращает новый экземпляр Vector2D, свойства x и y которого инициализируются суммой свойств x и y из двух экземпляров Vector2D, которые складываются.
Метод типа можно использовать как инфиксный оператор между существующими экземплярами Vector2D:
let vector = Vector2D(x: 3.0, y: 1.0) let anotherVector = Vector2D(x: 2.0, y: 4.0) let combinedVector = vector + anotherVector // combinedVector is a Vector2D instance with values of (5.0, 5.0)
В этом примере векторы (3.0, 1.0) и (2.0, 4.0) складываются, образуя вектор (5.0, 5.0), как показано ниже.
Префиксные и постфиксные операторы
В примере, показанном выше, демонстрируется пользовательская реализация бинарного инфиксного оператора. Классы и структуры также могут предоставлять реализации стандартных унарных операторов. Унарные операторы работают с одним объектом. Они являются префиксными, если предшествуют своему объекту (например, -a), и постфиксными, если следуют за своим объектом (например, b!).
Вы реализуете префиксный или постфиксный унарный оператор, написав модификатор prefix или postfix перед ключевым словом func при объявлении метода оператора:
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
} В приведенном выше примере реализован унарный оператор минус (-a) для экземпляров Vector2D. Унарный оператор минус является префиксным оператором, и поэтому этот метод должен быть квалифицирован модификатором prefix.
Для простых числовых значений унарный оператор минус преобразует положительные числа в их отрицательные эквиваленты и наоборот. Соответствующая реализация для экземпляров Vector2D выполняет эту операцию для свойств x и y:
let positive = Vector2D(x: 3.0, y: 4.0) let negative = -positive // negative is a Vector2D instance with values of (-3.0, -4.0) let alsoPositive = -negative // alsoPositive is a Vector2D instance with values of (3.0, 4.0)
Операторы составного присваивания
Операторы составного присваивания объединяют присваивание (=) с другой операцией. Например, оператор сложения присваивания (+=) объединяет сложение и присваивание в одну операцию. Вы помечаете тип левого входного параметра оператора составного присваивания как inout, поскольку значение параметра будет изменятся непосредственно изнутри метода оператора.
В примере ниже реализован метод оператора сложения присваивания для экземпляров Vector2D:
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
} Поскольку ранее был определен оператор сложения, вам не нужно повторно реализовывать процесс сложения здесь. Вместо этого метод оператора сложения присваивания использует существующий метод оператора сложения и использует его для установки левого значения, равного левому значению плюс правое значение:
var original = Vector2D(x: 1.0, y: 2.0) let vectorToAdd = Vector2D(x: 3.0, y: 4.0) original += vectorToAdd // original now has values of (4.0, 6.0)
Примечание: перегрузку оператора присваивания по умолчанию (
=) выполнить нельзя. Перегрузить можно только операторы сложного присваивания. Аналогично, перегрузить условный оператор «тернарный» (a ? b : c) нельзя.
Операторы эквивалентности
По умолчанию пользовательские классы и структуры не имеют реализации операторов эквивалентности, то есть операторов «равно» (==) и «не равно» (!=). Обычно реализуется оператор ==, а для оператора != используется стандартная реализация из библиотек Swift, которая возвращает отрицательное значение результата оператора ==. Существует два способа реализации оператора ==: самостоятельно реализовать его или попросить Swift сгенерировать реализацию для вас. В обоих случаях необходимо добавить соответствие протоколу Equatable стандартной библиотеки Swift.
Реализацию оператора == выполняйте так же, как реализуете другие инфиксные операторы:
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
} В приведенном примере реализован оператор == для проверки, имеют ли два экземпляра Vector2D эквивалентные значения. В контексте Vector2D логично считать, что «равно» означает «оба экземпляра имеют одинаковые значения x и y», поэтому именно такая логика используется в реализации оператора.
Теперь можно использовать этот оператор для проверки эквивалентности двух экземпляров Vector2D:
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
print("These two vectors are equivalent.")
}
// Prints "These two vectors are equivalent." Во многих простых случаях можно попросить Swift сгенерировать реализацию операторов эквивалентности, как описано в <doc:Protocols#Adopting-a-Protocol-Using-a-Synthesized-Implementation>.
Пользовательские операторы
В дополнение к стандартным операторам, предоставляемым Swift, вы можете объявлять и реализовывать свои пользовательские операторы. Список символов, которые можно использовать для определения пользовательских операторов, см. в <doc:LexicalStructure#Operators>.
Новые операторы объявляются на глобальном уровне с помощью ключевого слова operator и помечаются модификаторами prefix, infix или postfix:
prefix operator +++
В приведенном примере определяется новый префиксный оператор, называемый +++. Этот оператор не имеет существующего значения в Swift, поэтому ему присваивается собственное значение в контексте работы с экземплярами Vector2D. Для целей этого примера +++ обрабатывается как новый префиксный оператор «удвоения». Он удваивает значения x и y экземпляра Vector2D, добавляя вектор к самому себе с помощью ранее определенного оператора присваивания сложения. Для реализации оператора +++ необходимо добавить метод типа, называемый +++, в класс Vector2D, как показано ниже:
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0) Приоритет пользовательских инфиксных операторов
Каждый пользовательский инфиксный оператор принадлежит к группе приоритетов. Группа приоритетов определяет приоритет оператора относительно других инфиксных операторов, а также ассоциативность оператора. См. <doc:AdvancedOperators#Precedence-and-Associativity>, чтобы узнать, как эти характеристики влияют на взаимодействие инфиксного оператора с другими инфиксными операторами.
Пользовательский инфиксный оператор, который не помещен явно в группу приоритетов, получает группу приоритетов по умолчанию с приоритетом, немного выше, чем у условного тернарного оператора.
В следующем примере определяется новый пользовательский инфиксный оператор +-, который принадлежит к группе приоритетов AdditionPrecedence:
infix operator +-: AdditionPrecedence
extension Vector2D {
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0) Этот оператор складывает значения x двух векторов и вычитает значение y второго вектора из первого. Поскольку это, по сути, «аддитивный» оператор, ему присвоена та же группа приоритетов, что и у аддитивных инфиксных операторов, таких как + и -. Дополнительную информацию об операторах, предоставляемых стандартной библиотекой Swift, включая полный список групп приоритетов операторов и настроек ассоциативности, см. на странице Operator Declarations. Более подробную информацию о группах приоритетов и синтаксис определения собственных операторов и групп приоритетов см. в <doc:Declarations#Operator-Declaration>.
Примечание: при определении префиксного или постфиксного оператора приоритет не указывается. Однако если к одному операнду применены и префиксный, и постфиксный операторы, то постфиксный оператор применяется первым.
Библиотеки результатов
Библиотека результатов — это тип, который вы определяете, чтобы добавить синтаксис для создания вложенных данных, например, списка или дерева, естественным и декларативным способом. Код, использующий библиотеку результатов, может содержать обычный синтаксис Swift, такой как if и for, для обработки условных или повторяющихся элементов данных.
Ниже приведен код, определяющий несколько типов для рисования на одной строке с помощью звездочек и текста.
protocol Drawable {
func draw() -> String
}
struct Line: Drawable {
var elements: [Drawable]
func draw() -> String {
return elements.map { $0.draw() }.joined(separator: "")
}
}
struct Text: Drawable {
var content: String
init(_ content: String) { self.content = content }
func draw() -> String { return content }
}
struct Space: Drawable {
func draw() -> String { return " " }
}
struct Stars: Drawable {
var length: Int
func draw() -> String { return String(repeating: "*", count: length) }
}
struct AllCaps: Drawable {
var content: Drawable
func draw() -> String { return content.draw().uppercased() }
} Протокол Drawable определяет требование к тому, что может быть нарисовано, например, строка или фигура: тип должен реализовывать метод draw(). Структура Line представляет собой рисунок на одной строке и служит верхним контейнером для большинства рисунков. Для рисования Line структура вызывает метод draw() для каждого компонента строки, а затем конкатенирует полученные строки в одну строку. Структура Text оборачивает строку, чтобы сделать ее частью рисунка. Структура AllCaps оборачивает и изменяет другой рисунок, преобразуя любой текст в рисунке в верхний регистр.
Можно создать рисунок с помощью этих типов, вызвав их инициализаторы:
let name: String? = "Ravi Patel"
let manualDrawing = Line(elements: [
Stars(length: 3),
Text("Hello"),
Space(),
AllCaps(content: Text((name ?? "World") + "!")),
Stars(length: 2),
])
print(manualDrawing.draw())
// Prints "***Hello RAVI PATEL!**" Этот код работает, но он немного неудобен. Вложенные круглые скобки после AllCaps трудно читать. Логика по умолчанию использовать «World», когда name равно nil, должна быть реализована внутри с использованием оператора ??, что затруднит работу с чем-либо более сложным. Если потребуется включать переключатели или циклы for для построения части рисунка, это невозможно. Библиотека результатов позволяет переписать такой код так, чтобы он выглядел как обычный код Swift.
Для определения библиотеки результатов к объявлению типа добавляется атрибут @resultBuilder. Например, этот код определяет библиотеку результатов под названием DrawingBuilder, которая позволяет использовать декларативный синтаксис для описания рисунка:
@resultBuilder
struct DrawingBuilder {
static func buildBlock(_ components: Drawable...) -> Drawable {
return Line(elements: components)
}
static func buildEither(first: Drawable) -> Drawable {
return first
}
static func buildEither(second: Drawable) -> Drawable {
return second
}
} Структура DrawingBuilder определяет три метода, которые реализуют части синтаксиса библиотеки результатов. Метод buildBlock(_:) добавляет поддержку записи последовательности строк в блоке кода. Он объединяет компоненты в этом блоке в Line. Методы buildEither(first:) и buildEither(second:) добавляют поддержку if-else.
Атрибут @DrawingBuilder можно применить к параметру функции, что превращает замыкание, переданное в функцию, в значение, которое библиотека результатов создаёт из этого замыкания. Например:
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
return content()
}
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
return AllCaps(content: content())
}
func makeGreeting(for name: String? = nil) -> Drawable {
let greeting = draw {
Stars(length: 3)
Text("Hello")
Space()
caps {
if let name = name {
Text(name + "!")
} else {
Text("World!")
}
}
Stars(length: 2)
}
return greeting
}
let genericGreeting = makeGreeting()
print(genericGreeting.draw())
// Prints "***Hello WORLD!**"
let personalGreeting = makeGreeting(for: "Ravi Patel")
print(personalGreeting.draw())
// Prints "***Hello RAVI PATEL!**" Функция makeGreeting(for:) принимает параметр name и использует его для рисования персонализированного приветствия. Функции draw(_:) и caps(_:) оба принимают единственное замыкание в качестве аргумента, которое помечено атрибутом @DrawingBuilder. При вызове этих функций вы используете специальный синтаксис, который определяет DrawingBuilder. Swift преобразует это декларативное описание рисунка в серию вызовов методов в DrawingBuilder для построения значения, которое передаётся в качестве аргумента функции. Например, Swift преобразует вызов caps(_:) в этом примере в код, подобный следующему:
let capsDrawing = caps {
let partialDrawing: Drawable
if let name = name {
let text = Text(name + "!")
partialDrawing = DrawingBuilder.buildEither(first: text)
} else {
let text = Text("World!")
partialDrawing = DrawingBuilder.buildEither(second: text)
}
return partialDrawing
} Swift преобразует блок if-else в вызовы методов buildEither(first:) и buildEither(second:). Хотя вы не вызываете эти методы в собственном коде, демонстрация результата преобразования упрощает понимание того, как Swift преобразует ваш код при использовании синтаксиса DrawingBuilder.
Чтобы добавить поддержку циклов for в специальный синтаксис рисования, добавьте метод buildArray(_:).
extension DrawingBuilder {
static func buildArray(_ components: [Drawable]) -> Drawable {
return Line(elements: components)
}
}
let manyStars = draw {
Text("Stars:")
for length in 1...3 {
Space()
Stars(length: length)
}
} В приведенном коде цикл for создаёт массив рисунков, а метод buildArray(_:) преобразует этот массив в строку Line.
Полный список того, как Swift преобразует синтаксис библиотеки в вызовы методов типа библиотеки, см. в <doc:Attributes#resultBuilder>.
This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception