Мова програмування Swift. Російська версія

imageПривіт, Хабр! 2 червня всі ми на власні очі могли спостерігати, як компанія Apple почала творити революцію в таборі Objective-C розробників, представивши світу свій новий мову програмування — Swift. Разом з цим, вона виклала у відкритий доступ невелику документацію по мові, яку ми вирішили перевести, якщо на те буде попит. Пропонуємо вашій увазі переклад першої глави. Якщо тема буде цікава, то ми продовжимо публікувати переклад щотижня.
 
 

Зміст

 Ласкаво просимо в Swift
Про Swift
Введення в Swift

 
Language guide
The Basics
Basic Operators
String and Characters
Collection Types
Control Flow
Functions
Closures
Enumerations
Classes and Structures
Properties
Methods
Subscripts
Inheritance
Initialization
Deinitialization
Automatic Reference Counting
Optional Chaining
Type Casting
Nested Types
Extensions
Protocols
Generics
Advanced Operators
 
Language Reference
About the Language Reference
Lexical Structure
Types
Expressions
Statements
Declarations
Attributes
Patterns
Generic Parameters and Arguments
Summary of the Grammar
Trademarks
 
 

Ласкаво просимо в Swift

 
Про мову Swift
Swift — це новий мова програмування для розробки iOS і OS X додатків, який поєднує в собі все краще від C і Objective-C, але позбавлений обмежень, накладених на догоду сумісності з C. У Swift використовуються патерни безпечного програмування і додані сучасні функції, що перетворюють створення додатка в простій, більш гнучкий і захоплюючий процес. Swift, створений нами з чистого аркуша, — це можливість заново уявити собі, як розробляються програми.
 
Swift розроблявся нами кілька років. Основою нової мови програмування послужили існуючі компілятор, відладчик і фреймворки. Ми спростили процес управління пам'яттю за допомогою механізму автоматичного підрахунку посилань — Automatic Reference Counting (ARC). Наші фреймворки також зазнали серйозної модернізації. Objective-C почав підтримувати блоки, літерали і модулі — все це створило сприятливі умови для впровадження сучасних технологій. Саме ця підготовча робота послужила фундаментом для нової мови програмування, який буде застосовуватися для розробки майбутніх програмних продуктів для Apple.
 
Розробникам Objective-C Swift здасться знайомим. Він поєднує в собі читабельність іменованих параметрів і міць динамічної об'єктної моделі Objective-C. Він відкриває доступ до вже існуючих фреймворками Cocoa і сумісний з кодом, написаним на Objective-C. Побудований на цій загальній основі мову пропонує безліч нових можливостей і уніфікує процедурні та об'єктно-орієнтовані аспекти мови програмування.
 
Swift не відлякає і початківців програмістів. Це перший потужний мову програмування, такий же зрозумілий і захоплюючий, як скриптова мова. Він підтримує так звані playground-и, які дозволяють програмістам експериментувати з кодом, бачачи результат в режимі реального часу без необхідності компілювати і запускати додаток.
 
Swift увібрав в себе все краще від сучасних мов і розроблений з урахуванням великого досвіду компанії Apple. Наш компілятор — синонім продуктивності, наша мова оптимізований для розробки без оглядки на компроміси. Він спроектований таким чином, щоб ви змогли легко розробити і ваше перше додаток «hello, world!», І навіть цілу операційну систему. Все це робить Swift важливим інструментом для розробників і для самої компанії Apple.
 
Swift — це новий фантастичний спосіб створювати додатки для iOS і OS X, і ми продовжимо розвивати його, додаючи новий функціонал і представляючи нові можливості. Наша мета — амбіційна. І ми з нетерпінням чекаємо, щоб побачити, що ви зумієте створити за допомогою нього.
 
 
Введення в Swift
За давньою традицією перша програма на новій мові повинна виводити на екран слова
“Hello, world”
. За допомогою Swift це робиться так:
 
 
println("Hello, world")

Якщо ви коли-небудь розробляли на C або Objective-C цей синтаксис повинен здаватися вам до болю знайомим — в Swift ця строчка коду є закінченою програмою. Вам більше не потрібно імпортувати окремі бібліотеки для забезпечення базового функціоналу начебто введення / виводу в консоль або роботи з рядками. Код, написаний в глобальній області видимості, є точкою входу в програму, таким чином функція
main
більше не потрібна. Також зверніть увагу на відсутність крапки з комою в кінці кожного рядка.
 
Це введення містить достатньо інформації, щоб почати писати код на Swift. Не переживайте, якщо вам буде щось незрозуміло — ми все детально пояснимо в наступних розділах.
 
 
Зауваження
Для кращого розуміння матеріалу ми рекомендуємо використовувати режим playground в Xcode. Playground дозволяє вам бачити результат відразу в процесі редагування коду без необхідності компілювати і запускати додаток.
 
Прості типи даних
Використовуйте
let
для створення константи і
var
для створення змінної. Тип константи вказувати не потрібно, ви можете присвоїти їй значення лише одного разу.
 
 
var myVariable = 42
myVariable = 50
let myConstant = 42

Типи константи і змінної повинні збігатися з типами привласнюються їм відповідних значень. Однак це не означає, що ви повинні прямо вказувати їх тип. Компілятор автоматично визначить тип константи і змінної при присвоєнні їм значення. Так, у наведеному прикладі компілятор визначить, що
myVariable
має цілочисельний тип.
 
Якщо ж ініціалізатор відсутній або не надає достатньої інформації, ви можете вказати тип самостійно після змінної, розділивши назву і тип двокрапкою:
 
 
let implicitInteger = 70
let inplicitDouble = 70.0
let inplicitDouble: Double = 70

 
Давайте поекспериментуємо
Створіть константу з типом Float і проініціалізіруйте її числом 4.
Значення ніколи не конвертуються в інший тип неявно. Якщо вам необхідно конвертувати значення в інший тип, робіть це явно:
 
let label = "The width is "
let width = 94
let widthLabel = label + String(width)

 
Давайте поекспериментуємо
Спробуйте видалити явне перетворення до типу String в останньому рядку. Яку помилку ви отримаєте?
Мається простіший спосіб включення значень в рядки: для цього укладіть вираження в дужки і поставте перед ними зворотний слеш (
\
). Приклад:
 
 
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

 
Давайте поекспериментуємо
Спробуйте використовувати конструкцію
\()
і виведіть на екран рядок, що включає результат суми двох цілочисельних змінних і чиєсь ім'я.
При роботі з масивами і асоціативними масивами (словниками, dictionary) використовуються квадратні дужки (
[]
):
 
 
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
 
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

Щоб створити порожній масив або dictionary, використовуйте наступний синтаксис:
 
 
let emptyArray = String[]()
let emptyDictionary = Dictionary<String, Float>()

Для створення порожніх масивів і словників використовуйте
[]
і
[:]
відповідно, — наприклад, коли ви привласнюєте нове значення змінної або передаєте аргумент у функцію.
 
 
shoppingList = []   // Went shopping and bought everything.

 
 
Умови та цикли
Для створення умов використовуються оператори
if
і
switch
, для створення циклів —
for-in
,
for
,
while
і
do-while
. При цьому виділяти круглими дужками умови і ініціалізували вираження необов'язково, тоді як фігурні дужки обов'язкові.
 
 
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
teamScore

Умова всередині оператора
if
має бути логічним, це зокрема означає, що вираз
if score {…}
є помилковим, оскільки тут немає явного порівняння (наприклад, з нулем).
 
Умовний оператор
if
можна використовувати спільно з
let
і
var
для роботи з константами та змінними, які можуть мати значення
nil
. Такі константи і змінні називаються опциональнимі (тобто вони можуть або приймати будь-яке значення, або бути рівні
nil
). Щоб створити опциональную змінну або константу додайте знак питання (
?
) після вказівки типу.
 
 
var optionalString: String? = "Hello"
optionalString == nil
 
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

 
Давайте поекспериментуємо
Змініть
optionalName
на
nil
. Що ви бачите на екрані? Додайте блок
else
для обробки випадку, коли
optionalName
дорівнює
nil
.
Якщо опциональное значення дорівнює
nil
, умова буде помилковим і код у фігурних дужках після
if
виконаний не буде. В іншому випадку змінної
greeting
буде присвоєно нове значення.
 
Оператор множинного вибору
switch
підтримує всередині себе безліч інших операторів порівняння і не обмежений лише простими порівняннями:
 
 
let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
    let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
    let vegetableComment = "Is it a spicy \(x)?"
default:
    let vegetableComment = "Everything tastes good in soup."
}

 
Давайте поекспериментуємо
Спробуйте видалити умова за замовчуванням. Яку помилку ви отримаєте?
Після виконання відповідного блоку коду, програма залишає оператор
switch
, не перевіряючи наступні умови. Таким чином вам не потрібно вручну додавати оператори переривання (
break
) наприкінці кожного блоку
case
.
 
Для перебирання елементів асоціативного масиву використовуйте оператор
for-in
спільно із зазначенням пари імен для кожної пари ключ-значення.
 
 
let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
largest

 
Давайте поекспериментуємо
Додайте ще одну змінну, яка дозволить з'ясувати, до якого з трьох типів належить знайдене максимальне число.
Оператор циклу
while
дозволяє виконувати блок коду всередині нього доти, поки умова не стане хибним. Умова також може бути зазначено після блоку, який у такому випадку буде виконаний принаймні один раз.
 
 
var n = 2
while n < 100 {
    n = n * 2
}
n
 
var m = 2
do {
    m = m * 2
} while m < 100
m

Оператор
for
можна використовувати для перебору послідовності чисел за допомогою двох точок (
..
) або за допомогою ініціалізатора, умови і инкремента. Подивіться, ці два циклу роблять одне і те ж:
 
 
var firstForLoop = 0
for i in 0..3 {
    firstForLoop += i
}
firstForLoop
 
var secondForLoop = 0
for var i = 0; i < 3; ++i {
    secondForLoop += 1
}
secondForLoop

При створенні циклу використовуйте дві точки (
..
), якщо не хочете включати більше значення в діапазон, і три крапки (
), щоб включити як менше, так і більше значення.
 
 
Функції і замикання.
Для оголошення функцій використовуйте ключове слово
func
. Виклик функції проводиться через вказівку її імені і списку аргументів в круглих дужках. Повертається тип слід відокремити від переліку формальних аргументів за допомогою
->
.
 
 
func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)."
}
greet("Bob", "Tuesday")

 
Давайте поекспериментуємо
Видаліть параметр day. Замість нього додайте змінну, що позначає найменування подається на обід страви.
Якщо функція повертає безліч значень, слід використовувати кортеж:
 
 
func getGasPrices() -> (Double, Double, Double) {
    return (3.59, 3.69, 3.79)
}
getGasPrices()

Функції також можуть мати невизначене число аргументів:
 
 
func sumOf(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}
sumOf()
sumOf(42, 597, 12)

 
Давайте поекспериментуємо
Напишіть функцію, що дозволяє знаходити середнє арифметичне довільного числа своїх аргументів.
Функції можуть вкладатися один в одного. Вкладена функція може звертатися до змінних, оголошеним у зовнішній функції. Використовуйте вкладені функції, щоб привести в порядок код складною або великий функції.
 
 
func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

Функції є об'єктами першого класу (first-class type), іншими словами, функція в якості свого результату може повертати іншу функцію.
 
 
func makeIncrementer() -> (Int -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

Функція також може приймати іншу функцію як одного з аргументів.
 
 
func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, lessThanTen)

Функції є окремим випадком замикань. Ви можете створити замикання, що не вказуючи його імені і оточивши тіло замикання фігурними дужками (
{}
). Для відділення аргументів і типу значення, що повертається від тіла замикання використовуйте оператор
in
.
 
 
numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
    })

 
Давайте поекспериментуємо
Перепишіть замикання таким чином, щоб воно повертало нуль для всіх зайвих чисел.
Існує декілька технік, що дозволяють робити замикання більш лаконічними. Якщо тип замикання апріорі відомий (наприклад, це callback делегата), можна опустити вказівку типу його параметрів і / або типу, що повертається. Замикання, що складаються з єдиного вираження, неявно повертають результат цього виразу.
 
 
numbers.map({ number in 3 * number })

У замиканні замість вказівки імені змінної, ви можете використовувати її порядковий номер — це особливо корисно при написанні коротких замикань. Замикання, що є останнім аргументом функції, може бути передано в неї відразу після круглих дужок з переліком інших параметрів.
 
 
sort([1, 5, 3, 12, 2]) { $0 > $1 }

 
 
Об'єкти і класи
Для створення класу використовується зарезервоване слово
class
. Члени класу оголошуються точно так само, як і звичайні константи і змінні. Більш того, методи класу оголошуються як звичайні функції.
 
 
class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

 
Давайте поекспериментуємо
Додайте константу-член класу і метод класу, приймаючу її в якості свого аргументу.
Щоб створити екземпляр (об'єкт) класу, досить додати круглі дужки після назви класу. Доступ до методів і членам класу здійснюється через крапку.
 
 
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

У цьому прикладі ми упустили одну важливу деталь — конструктор класу, метод
init
.
 
 
class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Зверніть увагу, як член класу name за допомогою
self
відділений від аргументу конструктора
name
. Аргументи передаються в конструктор звичайним чином, як і в будь-який інший метод класу. Зверніть увагу на те, що кожен член класу повинен бути ініціалізованим — або при оголошенні (як, наприклад,
numberOfSides
), або в конструкторі (як
name
).
 
Деструктор класу — метод
deinit
, який можна переписати у разі потреби.
 
Щоб наслідувати клас від вже існуючого класу, після вказівки імені дочірнього класу слід поставити кому і вказати назву батьківського. У Swift немає ніяких обмежень по обов'язковому спадкоємства-якого стандартного класу.
 
Перевизначені дочірнім класом методи повинні бути позначені ключовим словом
override
— перевизначення методів без override призведе до помилки. Компілятор також виявляє методи, марковані
override
, але не переважають будь-які методи свого батьківського класу.
 
class Square: NamedShape {
    var sideLength: Double
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }
    
    func area() ->  Double {
        return sideLength * sideLength
    }
    
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

 
Давайте поекспериментуємо
Створіть клас
Circle
і наслідуйте його від класу
NamedShape
. Конструктор класу
Circle
приймає два аргументи — радіус і назву. Перевизначите методи
area
і
describe
цього класу.
Члени класу можуть також мати власні
getter
і
setter
.
 
 
class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    
    var perimeter: Double {
    get {
        return 3.0 * sideLength
    }
    set {
        sideLength = newValue / 3.0
    }
    }
    
    override func simpleDescription() -> String {
        return "An equilateral triagle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
triangle.perimeter
triangle.perimeter = 9.9
triangle.sideLength

У
setter
-е змінної
perimeter
нове присваиваемое значення неявно називається
newValue
. Ви можете змінити назву цієї змінної, вказавши його в дужках відразу після
set
.
 
Зверніть увагу на структуру конструктора класу
EquilateralTriangle
. Цей метод включає в себе три послідовних кроки:
 
     
ініціалізація членів дочірнього класу;
 виклик конструктора батьківського класу;
 зміна значень членів батьківського класу.
 
Якщо вам необхідно виконати певний код до або після присвоювання нового значення змінної, ви можете перевизначити методи
willSet
і
didSet
потрібним вам чином. Наприклад, у наведеному нижче класі гарантується, що довжина сторони трикутника завжди буде дорівнює довжині сторони квадрата.
 
 
class TriangleAndSquare {
    var triangle: EquilateralTriangle {
    willSet {
        square.sideLength = newValue.sideLength
    }
    }
    var square: Square {
    willSet {
        triangle.sideLength = newValue.sideLength
    }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
triangleAndSquare.square.sideLength
triangleAndSquare.triangle.sideLength
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
triangleAndSquare.triangle.sideLength

У методів класів мається одна важлива відмінність від функцій. Назви аргументів функції використовуються тільки в межах цієї функції, тоді як у методі класу параметри також використовуються при виклику цього методу (крім першого параметра). За замовчуванням метод класу має однакові назви параметрів як при виклику, так і всередині себе. Однак ви можете вказати іншу назву (у прикладі нижче —
times
), яке буде використано тільки всередині цього методу. При цьому для виклику цього методу необхідно використовувати перша назва (
numberOfTimes
).
 
 
class Counter {
    var count: Int = 0
    func incrementBy(amount: Int, numberOfTimes times: Int) {
        count += amount * times
    }
}
var counter = Counter()
counter.incrementBy(2, numberOfTimes: 7)

При роботі з опциональнимі значеннями додайте знак питання (
?
) перед методами, членами класу і т.д. Якщо значення перед знаком питання одно
nil
, все, що треба після (
?
) ігнорується і значення всього виразу одно
nil
. В іншому випадку вираз обчислюється звичайним чином. В обох випадках результатом всього виразу буде опциональное значення.
 
 
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

 
 
Перерахування та Структури
Для створення перерахувань використовується ключове слово
enum
. Відзначимо, що перерахування також можуть мати у своєму складі методи.
 
 
enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
        case .Ace:
            return "ace"
        case .Jack:
            return "jack"
        case .Queen:
            return "queen"
        case .King:
            return "king"
        default:
            return String(self.toRaw())
        }
    }
}
let ace = Rank.Ace
let aceRawValue = ace.toRaw()

 
Давайте поекспериментуємо
Напишіть функцію, яка порівнює 2 перерахування типу
Rank
за їх значенням.
У наведеному вище прикладі елементи перерахування спочатку мають цілочисельний тип, і вам достатньо вказати значення тільки першого елемента — значення інших елементів будуть визначені у відповідності з порядком їх слідування. В якості вихідного типу (raw value) значень елементів ви також можете вибрати строковий або речові типи.
 
Для перетворення початкового типу значення в тип перерахування використовуйте функції
toRaw
і
fromRaw
.

if let convertedRank = Rank.fromRaw(3) {
    let threeDescription = convertedRank.simpleDescription()
}

Відзначимо, що значення елементів перерахування є фактичними, а не просто інший записом своїх вихідних значень. Взагалі кажучи, ви можете і не вказувати їх вихідні значення.

enum Suit {
    case Spades, Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
        case .Spades:
            return "spades"
        case .Hearts:
            return "hearts"
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()

Давайте поекспериментуємо
Додайте метод
Color
, який повертає рядок
“black”
для
Spades
і
Clubs
і
“red”
для
Hearts
і
Diamonds
.
Зверніть увагу на те, як здійснюється доступ до члена
Hearts
перерахування
Suit
. При присвоєнні значення константі
Hearts
використовується повне ім'я
Suit.Hearts
, оскільки ми явно не вказуємо тип цієї константи. А в
switch
ми використовуємо скорочену форму
.Hearts
, оскільки тип значення
self
апріорі відомий. Ви можете використовувати скорочену форму повсюдно, якщо тип змінної явно вказаний.

Для створення структур використовується ключове слово
struct
. Структури мають безліч схожих рис з класами, включаючи методи і конструктори. Одним з найбільш істотних відмінностей структур від класів є те, що екземпляри структур, на відміну від примірників класів, передаються у функції за значенням (тобто попередньо створюється їх локальна копія), тоді як екземпляри класів передаються за посиланням.

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

Давайте поекспериментуємо
Додайте в структуру Card метод, який створює повну колоду карт.
Примірник члена перерахування може мати власні значення і вони можуть бути різними. Ви привласнюєте ці значення при створенні екземпляра перерахування (константа
success
у прикладі). Пов'язані і вихідні значення це різні речі: початкове значення члена перерахування завжди постійно для всіх примірників перерахування і вказується при його оголошенні.

Розглянемо приклад отримання з сервера часу сходу і заходу Сонця. Сервер відправляє у відповідь або відповідну інформацію, або повідомлення про помилку.

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}
 
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")
 
switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

Давайте поекспериментуємо
Додайте третій варіант в оператор множинного вибору
switch
Зверніть увагу, яким чином з об'єкта
ServerResponse
«витягуються» час сходу і заходу.

Протоколи та Розширення.
Для оголошення протоколу використовуйте ключове слово
protocol
.

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

Протоколи можуть підтримуватися класами, перерахуваннями та структурами.

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
 
struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

Давайте поекспериментуємо
Створіть перерахування, яке реалізовуватиме цей протокол.
Зверніть увагу на ключове слово
mutating
у визначенні структури
SimpleStructure
, яке інформує компілятор про те, що відповідний метод піддає структуру змінам. На противагу цьому методи класу
SimpleClass
не потрібно маркувати як
mutating
, оскільки методи класу завжди можуть безперешкодно його змінювати.

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

extension Int: ExampleProtocol {
    var simpleDescription: String {
    return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
7.simpleDescription

Давайте поекспериментуємо
Створіть розширення типу
Double
із змінною-членом
absoluteValue
.
Ви можете використовувати назву протоколу як і будь-який інший тип — наприклад, щоб створити масив об'єктів різного типу, але реалізують спільний протокол. Зауважте, що при роботі з об'єктами такого типу методи, оголошені поза протоколом, будуть недоступні.

let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
// protocolValue.anotherProperty  // Uncomment to see the error

Незважаючи на те, що під час виконання програми змінна
protocolValue
має тип
SimpleClass
, компілятор вважає, що її тип —
ExampleProtocol
. Це означає, що ви не зможете випадково отримати доступ до методів або членам класу, які реалізуються поза протоколом
ExampleProtocol
.

Узагальнені типи (generics)
Для створення узагальненого типу, укладіть ім'я в кутові дужки (
<>
).

func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {
    var result = ItemType[]()
    for i in 0..times {
        result += item
    }
    return result
}
repeat("knock", 4)

Створюйте узагальнені функції, класи, перерахування та структури.

// Reimplement the Swift standard library's optional type
enum OptionalValue<T> {
    case None
    case Some(T)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)

Якщо ви хочете задати узагальнені типом певні вимоги, такі, як, наприклад, реалізація протоколу чи вимога бути успадкованим від певного класу, використовуйте
where
.

func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool {
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])

Давайте поекспериментуємо
Змініть функцію
anyCommonElements
таким чином, щоб вона повертала масив спільних елементів.
У простих випадках ви можете опустити
where
і написати ім'я протоколу або класу після двокрапки. Вираз
<T: Equatable>
еквівалентно висловом
<T where T: Equatable>
.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.