Swift - это язык программирования, созданный компанией Apple, для приложений под iOS и macOS. Разработанный, чтобы сосуществовать с Objective-C и быть более устойчивым к ошибочному коду, Swift был представлен в 2014 году на конференции разработчиков Apple, WWDC. Приложения на Swift собираются с помощью LLVM-компилятора, включенного в Xcode 6+.
Официальная книга по языку программирования Swift от Apple доступна в Apple Books.
Смотрите еще начальное руководство Apple, которое содержит полное учебное пособие по Swift.
1// Версия Swift: 3.0
2
3// импорт модуля
4import UIKit
5
6//
7// MARK: Основы
8//
9
10// Xcode поддерживает маркеры, чтобы давать примечания своему коду
11// и вносить их в список обозревателя (Jump Bar)
12// MARK: Метка раздела
13// MARK: - Метка с разделителем
14// TODO: Сделайте что-нибудь вскоре
15// FIXME: Исправьте этот код
16
17// Начиная со второй версии Swift, println и print объединены в методе print.
18// Перенос строки теперь добавляется в конец автоматически.
19print("Привет, мир!") // println – теперь просто print
20print("Привет, мир!", terminator: "") // вывод текста без переноса строки
21
22// переменные (var), значение которых можно изменить после инициализации
23// константы (let), значение которых нельзя изменить после инициализации
24
25var myVariable = 42
26let øπΩ = "значение" // именование переменной символами unicode
27let π = 3.1415926
28let convenience = "Ключевое слово" // контекстное имя переменной
29let weak = "Ключевое слово"; let override = "еще ключевое слово" // операторы
30 // могут быть отделены точкой с запятой
31let `class` = "Ключевое слово" // обратные апострофы позволяют использовать
32 // ключевые слова в именовании переменных
33let explicitDouble: Double = 70
34let intValue = 0007 // 7
35let largeIntValue = 77_000 // 77000
36let label = "некоторый текст " + String(myVariable) // Приведение типа
37let piText = "Pi = \(π), Pi 2 = \(π * 2)" // Вставка переменных в строку
38
39// Сборка особых значений
40// используя ключ -D сборки конфигурации
41#if false
42 print("Не печатается")
43 let buildValue = 3
44#else
45 let buildValue = 7
46#endif
47print("Значение сборки: \(buildValue)") // Значение сборки: 7
48
49/*
50 Опционалы - это особенность языка Swift, которая допускает вам сохранять
51 `некоторое` или `никакое` значения.
52
53 Язык Swift требует, чтобы каждое свойство имело значение, поэтому даже nil
54 должен быть явно сохранен как опциональное значение.
55
56 Optional<T> является перечислением.
57*/
58var someOptionalString: String? = "опционал" // Может быть nil
59// как и выше, только ? - это постфиксный оператор (синтаксический сахар)
60var someOptionalString2: Optional<String> = "опционал"
61
62if someOptionalString != nil {
63 // я не nil
64 if someOptionalString!.hasPrefix("opt") {
65 print("содержит префикс")
66 }
67
68 let empty = someOptionalString?.isEmpty
69}
70someOptionalString = nil
71
72/*
73Использование ! для доступа к несуществующему опциональному значению генерирует
74рантайм ошибку. Всегда проверяйте, что опционал содержит не пустое значение,
75перед тем как раскрывать его через !.
76*/
77
78// неявная развертка опциональной переменной
79var unwrappedString: String! = "Ожидаемое значение."
80// как и выше, только ! - постфиксный оператор (с еще одним синтаксическим сахаром)
81var unwrappedString2: ImplicitlyUnwrappedOptional<String> = "Ожидаемое значение."
82
83// If let конструкции -
84// If let это специальная конструкция в Swift, которая позволяет проверить Optional
85// справа от `=` непустой, и если это так - разворачивает его и присваивает левой части.
86if let someOptionalStringConstant = someOptionalString {
87 // имеется некоторое (`Some`) значение, не `nil`
88 if !someOptionalStringConstant.hasPrefix("ok") {
89 // нет такого префикса
90 }
91}
92
93// Swift поддерживает сохранение значения любого типа
94// Для этих целей есть два ключевых слова `Any` и `AnyObject`
95// AnyObject == id
96// `Any` же, в отличие от `id` в Objective-C, `Any` работает с любым значением (Class, Int, struct и т.д.)
97var anyVar: Any = 7
98anyVar = "Изменять значение на строку не является хорошей практикой, но возможно."
99let anyObjectVar: AnyObject = Int(1) as NSNumber
100
101/*
102 Комментируйте здесь
103
104 /*
105 Вложенные комментарии тоже поддерживаются
106 */
107*/
108
109//
110// MARK: Коллекции
111//
112
113/*
114 Массив (Array) и словарь (Dictionary) являются структурами (struct). Так
115 `let` и `var` также означают, что они изменяются (var) или не изменяются (let)
116 при объявлении переменных этих типов.
117*/
118
119// Массив
120var shoppingList = ["сом", "вода", "лимоны"]
121shoppingList[1] = "бутылка воды"
122let emptyArray = [String]() // let == неизменный
123let emptyArray2 = Array<String>() // как и выше
124var emptyMutableArray = [String]() // var == изменяемый
125var explicitEmptyMutableStringArray: [String] = [] // так же как и выше
126
127
128// Словарь
129var occupations = [
130 "Malcolm": "Капитан",
131 "kaylee": "Техник"
132]
133occupations["Jayne"] = "Связи с общественностью"
134let emptyDictionary = [String: Float]() // let == неизменный
135let emptyDictionary2 = Dictionary<String, Float>() // как и выше
136var emptyMutableDictionary = [String: Float]() // var == изменяемый
137var explicitEmptyMutableDictionary: [String: Float] = [:] // то же
138
139
140//
141// MARK: Поток управления
142//
143
144// С помощью "," можно указать дополнительные условия для раскрытия
145// опциональных значений.
146let someNumber = Optional<Int>(7)
147if let num = someNumber, num > 3 {
148 print("Больше 3х")
149}
150
151// цикл for для массива
152let myArray = [1, 1, 2, 3, 5]
153for value in myArray {
154 if value == 1 {
155 print("Один!")
156 } else {
157 print("Не один!")
158 }
159}
160
161// цикл for для словаря
162var dict = ["один": 1, "два": 2]
163for (key, value) in dict {
164 print("\(key): \(value)")
165}
166
167// цикл for для диапазона чисел
168for i in -1...shoppingList.count {
169 print(i)
170}
171shoppingList[1...2] = ["бифштекс", "орехи пекан"]
172// используйте ..< для исключения последнего числа
173
174// цикл while
175var i = 1
176while i < 1000 {
177 i *= 2
178}
179
180// цикл do-while
181repeat {
182 print("привет")
183} while 1 == 2
184
185// Переключатель
186// Очень мощный оператор, представляйте себе операторы `if` с синтаксическим
187// сахаром
188// Они поддерживают строки, объекты и примитивы (Int, Double, etc)
189let vegetable = "красный перец"
190switch vegetable {
191case "сельдерей":
192 let vegetableComment = "Добавьте немного изюма, имитируя муравьев на бревнышке."
193case "огурец", "кресс-салат":
194 let vegetableComment = "Было бы неплохо сделать бутерброд с чаем."
195case let localScopeValue where localScopeValue.hasSuffix("перец"):
196 let vegetableComment = "Это острый \(localScopeValue)?"
197default: // обязательный (чтобы предусмотреть все возможные вхождения)
198 let vegetableComment = "В супе все овощи вкусные."
199}
200
201
202//
203// MARK: Функции
204//
205
206// Функции являются типом первого класса, т.е. они могут быть вложены в функциях
207// и могут передаваться между собой
208
209// Функция с документированным заголовком Swift (формат Swift-модифицированный Markdown)
210
211/**
212 Операция приветствия
213
214 - Маркер в документировании
215 - Еще один маркер в документации
216
217 - Parameter name : Это имя
218 - Parameter day : Это день
219 - Returns : Строка, содержащая значения name и day.
220*/
221func greet(name: String, day: String) -> String {
222 return "Привет \(name), сегодня \(day)."
223}
224greet(name: "Боб", day: "вторник")
225
226// как и выше, кроме обращения параметров функции
227func greet2(name: String, externalParamName localParamName: String) -> String {
228 return "Привет \(name), сегодня \(localParamName)"
229}
230greet2(name: "Иван", externalParamName: "Воскресенье")
231
232// Функция, которая возвращает множество элементов в кортеже
233func getGasPrices() -> (Double, Double, Double) {
234 return (3.59, 3.69, 3.79)
235}
236let pricesTuple = getGasPrices()
237let price = pricesTuple.2 // 3.79
238// Пропускайте значения кортежей с помощью подчеркивания _
239let (_, price1, _) = pricesTuple // price1 == 3.69
240print(price1 == pricesTuple.1) // вывод: true
241print("Цена газа: \(price)")
242
243// Именованные параметры кортежа
244func getGasPrices2() -> (lowestPrice: Double, highestPrice: Double, midPrice: Double) {
245 return (1.77, 37.70, 7.37)
246}
247let pricesTuple2 = getGasPrices2()
248let price2 = pricesTuple2.lowestPrice
249let (_, price3, _) = pricesTuple2
250print(pricesTuple2.highestPrice == pricesTuple2.1) // вывод: true
251print("Самая высокая цена за газ: \(pricesTuple2.highestPrice)")
252
253// guard утверждения
254func testGuard() {
255 // guards обеспечивают прерывание дальнейшего выполнения функции,
256 // позволяя держать обработчики ошибок рядом с проверкой условия
257 // Объявляемая переменная находится в той же области видимости, что и guard.
258 guard let aNumber = Optional<Int>(7) else {
259 return
260 }
261
262 print("число равно \(aNumber)")
263}
264testGuard()
265
266// Переменное число аргументов
267func setup(numbers: Int...) {
268 // это массив
269 let number = numbers[0]
270 let argCount = numbers.count
271}
272
273// Передача и возврат функций
274func makeIncrementer() -> ((Int) -> Int) {
275 func addOne(number: Int) -> Int {
276 return 1 + number
277 }
278 return addOne
279}
280var increment = makeIncrementer()
281increment(7)
282
283// передача по ссылке
284func swapTwoInts(a: inout Int, b: inout Int) {
285 let tempA = a
286 a = b
287 b = tempA
288}
289var someIntA = 7
290var someIntB = 3
291swapTwoInts(a: &someIntA, b: &someIntB)
292print(someIntB) // 7
293
294
295//
296// MARK: Замыкания
297//
298var numbers = [1, 2, 6]
299
300// Функции - это частный случай замыканий ({})
301
302// Пример замыкания.
303// `->` отделяет аргументы и возвращаемый тип
304// `in` отделяет заголовок замыкания от тела замыкания
305numbers.map({
306 (number: Int) -> Int in
307 let result = 3 * number
308 return result
309})
310
311// Когда тип известен, как и выше, мы можем сделать так
312numbers = numbers.map({ number in 3 * number })
313// Или даже так
314//numbers = numbers.map({ $0 * 3 })
315
316print(numbers) // [3, 6, 18]
317
318// Хвостовое замыкание
319numbers = numbers.sorted { $0 > $1 }
320
321print(numbers) // [18, 6, 3]
322
323// Суперсокращение, поскольку оператор < выполняет логический вывод типов
324
325numbers = numbers.sorted(by: <)
326
327print(numbers) // [3, 6, 18]
328
329//
330// MARK: Структуры
331//
332
333// Структуры и классы имеют очень похожие характеристики
334struct NamesTable {
335 let names: [String]
336
337 // Пользовательский индекс
338 subscript(index: Int) -> String {
339 return names[index]
340 }
341}
342
343// У структур автогенерируемый (неявно) инициализатор
344let namesTable = NamesTable(names: ["Иван", "Яков"])
345let name = namesTable[1]
346print("Имя :\(name)") // Имя: Яков
347
348//
349// MARK: Обработка ошибок
350//
351
352// Протокол `Error` используется для перехвата выбрасываемых ошибок
353enum MyError: Error {
354 case badValue(msg: String)
355 case reallyBadValue(msg: String)
356}
357
358// фунции помеченные словом `throws` должны вызываться с помощью `try`
359func fakeFetch(value: Int) throws -> String {
360 guard 7 == value else {
361 throw MyError.reallyBadValue(msg: "Действительно плохое значение")
362 }
363
364 return "тест"
365}
366
367func testTryStuff() {
368 // предполагается, что не будет выброшено никаких ошибок,
369 // в противном случае мы получим рантайм исключение
370 let _ = try! fakeFetch(value: 7)
371
372 // Если возникает ошибка, то выполнение продолжится. Но если значение равно nil,
373 // то результат будет опционалом
374 let _ = try? fakeFetch(value: 7)
375
376 do {
377 // обычно try оператор, позволяющий обработать ошибку в `catch` блоке
378 try fakeFetch(value: 1)
379 } catch MyError.badValue(let msg) {
380 print("Ошибка: \(msg)")
381 } catch {
382 // все остальное
383 }
384}
385testTryStuff()
386
387//
388// MARK: Классы
389//
390
391// Классы, структуры и их члены имеют трехуровневый контроль доступа
392// Уровни: internal (по умолчанию), public, private
393
394public class Shape {
395 public func getArea() -> Int {
396 return 0
397 }
398}
399
400// Все методы и свойства класса являются открытыми (public).
401// Если вам необходимо содержать только данные
402// в структурированном объекте, вы должны использовать `struct`
403
404internal class Rect: Shape {
405 var sideLength: Int = 1
406
407 // Пользовательский сеттер и геттер
408 private var perimeter: Int {
409 get {
410 return 4 * sideLength
411 }
412 set {
413 // `newValue` - неявная переменная, доступная в сеттере
414 sideLength = newValue / 4
415 }
416 }
417
418 // Вычисляемые свойства должны быть объявлены с помощью `var`, ведь они могут меняться
419 var smallestSideLength: Int {
420 return self.sideLength - 1
421 }
422
423 // Ленивая загрузка свойства
424 // свойство subShape остается равным nil (неинициализированным),
425 // пока не вызовется геттер
426 lazy var subShape = Rect(sideLength: 4)
427
428 // Если вам не нужны пользовательские геттеры и сеттеры,
429 // но все же хотите запустить код перед и после вызовов геттера или сеттера
430 // свойств, вы можете использовать `willSet` и `didSet`
431 var identifier: String = "defaultID" {
432 // аргумент у `willSet` будет именем переменной для нового значения
433 willSet(someIdentifier) {
434 print(someIdentifier)
435 }
436 }
437
438 init(sideLength: Int) {
439 self.sideLength = sideLength
440 // последним всегда вызывается super.init, когда init с параметрами
441 super.init()
442 }
443
444 func shrink() {
445 if sideLength > 0 {
446 sideLength -= 1
447 }
448 }
449
450 override func getArea() -> Int {
451 return sideLength * sideLength
452 }
453}
454
455// Простой класс `Square` наследует `Rect`
456class Square: Rect {
457 convenience init() {
458 self.init(sideLength: 5)
459 }
460}
461
462var mySquare = Square()
463print(mySquare.getArea()) // 25
464mySquare.shrink()
465print(mySquare.sideLength) // 4
466
467// преобразование объектов
468let aShape = mySquare as Shape
469
470// сравнение экземпляров, в отличие от ==, которая проверяет эквивалентность
471if mySquare === mySquare {
472 print("Ага, это mySquare")
473}
474
475// Опциональная инициализация (init)
476class Circle: Shape {
477 var radius: Int
478 override func getArea() -> Int {
479 return 3 * radius * radius
480 }
481
482 // Поместите постфиксный знак вопроса после `init` - это и будет опциональная инициализация,
483 // которая может вернуть nil
484 init?(radius: Int) {
485 self.radius = radius
486 super.init()
487
488 if radius <= 0 {
489 return nil
490 }
491 }
492}
493
494var myCircle = Circle(radius: 1)
495print(myCircle?.getArea()) // Optional(3)
496print(myCircle!.getArea()) // 3
497var myEmptyCircle = Circle(radius: -1)
498print(myEmptyCircle?.getArea()) // "nil"
499if let circle = myEmptyCircle {
500 // не будет выполняться, поскольку myEmptyCircle равен nil
501 print("circle не nil")
502}
503
504
505//
506// MARK: Перечисления
507//
508
509// Перечисления могут быть определенного или своего типа.
510// Они могут содержать методы подобно классам.
511
512enum Suit {
513 case spades, hearts, diamonds, clubs
514 func getIcon() -> String {
515 switch self {
516 case .spades: return "♤"
517 case .hearts: return "♡"
518 case .diamonds: return "♢"
519 case .clubs: return "♧"
520 }
521 }
522}
523
524// Значения перечислений допускают сокращенный синтаксис, нет необходимости
525// указывать тип перечисления, когда переменная объявляется явно
526var suitValue: Suit = .hearts
527
528// Значения нецелочисленных перечислений должны быть указаны явно
529// или могут выводится с помощью функции `rawValue` из имени
530enum BookName: String {
531 case john
532 case luke = "Лука"
533}
534print("Имя: \(BookName.john.rawValue)")
535
536// Перечисление (enum) со связанными значениями
537enum Furniture {
538 // Связать с типом Int
539 case desk(height: Int)
540 // Связать с типами String и Int
541 case chair(String, Int)
542
543 func description() -> String {
544 switch self {
545 case .desk(let height):
546 return "Письменный стол высотой \(height) см."
547 case .chair(let brand, let height):
548 return "Стул марки \(brand) высотой \(height) см."
549 }
550 }
551}
552
553var desk: Furniture = .desk(height: 80)
554print(desk.description()) // "Письменный стол высотой 80 см."
555var chair = Furniture.chair("Foo", 40)
556print(chair.description()) // "Стул марки Foo высотой 40 см."
557
558
559//
560// MARK: Протоколы
561//
562
563// `protocol` может потребовать, чтобы у соответствующих типов
564// были определенные свойства экземпляра, методы экземпляра, тип методов,
565// операторы и индексы.
566
567protocol ShapeGenerator {
568 var enabled: Bool { get set }
569 func buildShape() -> Shape
570}
571
572// Протоколы, объявленные с @objc, допускают необязательные функции,
573// которые позволяют вам проверять на соответствие. Для функций также необходимо указать @objc
574@objc protocol TransformShape {
575 @objc optional func reshape()
576 @objc optional func canReshape() -> Bool
577}
578
579class MyShape: Rect {
580 var delegate: TransformShape?
581
582 func grow() {
583 sideLength += 2
584
585 // Размещайте знак вопроса перед опционным свойством, методом
586 // или индексом, чтобы не учитывать nil-значение и возвратить nil
587 // вместо выбрасывания ошибки выполнения (т.н. "опционная цепочка")
588 if let reshape = self.delegate?.canReshape?(), reshape {
589 // проверка делегата на выполнение метода
590 self.delegate?.reshape?()
591 }
592 }
593}
594
595
596//
597// MARK: Прочее
598//
599
600// `extension`s: Добавляет расширенную функциональность к существующему типу
601
602// Класс Square теперь "соответствует" протоколу `CustomStringConvertible`
603extension Square: CustomStringConvertible {
604 var description: String {
605 return "Площадь: \(self.getArea()) - ID: \(self.identifier)"
606 }
607}
608
609print("Объект Square: \(mySquare)")
610
611// Вы также можете расширить встроенные типы
612extension Int {
613 var customProperty: String {
614 return "Это \(self)"
615 }
616
617 func multiplyBy(num: Int) -> Int {
618 return num * self
619 }
620}
621
622print(7.customProperty) // "Это 7"
623print(14.multiplyBy(num: 3)) // 42
624
625// Обобщения: Подобно языкам Java и C#. Используйте ключевое слово `where`,
626// чтобы определить условия обобщений.
627
628func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
629 for (index, value) in array.enumerated() {
630 if value == valueToFind {
631 return index
632 }
633 }
634 return nil
635}
636let foundAtIndex = findIndex(array: [1, 2, 3, 4], valueToFind: 3)
637print(foundAtIndex == 2) // вывод: true
638
639// Операторы:
640// Пользовательские операторы могут начинаться с символов:
641// / = - + * % < > ! & | ^ . ~
642// или
643// Unicode- знаков математики, символов, стрелок, декорации и линий/кубов,
644// нарисованных символов.
645prefix operator !!!
646
647// Префиксный оператор, который утраивает длину стороны, когда используется
648prefix func !!! (shape: inout Square) -> Square {
649 shape.sideLength *= 3
650 return shape
651}
652
653// текущее значение
654print(mySquare.sideLength) // 4
655
656// Используя пользовательский оператор !!!, изменится длина стороны
657// путем увеличения размера в 3 раза
658!!!mySquare
659print(mySquare.sideLength) // 12
660
661// Операторы также могут быть обобщенными
662infix operator <->
663func <-><T: Equatable> (a: inout T, b: inout T) {
664 let c = a
665 a = b
666 b = c
667}
668
669var foo: Float = 10
670var bar: Float = 20
671
672foo <-> bar
673print("foo это \(foo), bar это \(bar)") // "foo = 20.0, bar = 10.0"