swift.md

Личный сайт Go-разработчика из Казани

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"