learnxinyminutes-docs/ru/swift.md
2024-12-08 23:20:53 -07:00

698 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
contributors:
- ["Grant Timmerman", "http://github.com/grant"]
- ["Christopher Bess", "http://github.com/cbess"]
- ["Joey Huang", "http://github.com/kamidox"]
- ["Alexey Nazaroff", "http://github.com/rogaven"]
filename: learnswift-ru.swift
translators:
- ["Dmitry Bessonov", "https://github.com/TheDmitry"]
- ["Alexey Nazaroff", "https://github.com/rogaven"]
---
Swift - это язык программирования, созданный компанией Apple, для приложений
под iOS и macOS. Разработанный, чтобы сосуществовать с Objective-C и
быть более устойчивым к ошибочному коду, Swift был представлен в 2014 году на
конференции разработчиков Apple, WWDC. Приложения на Swift собираются
с помощью LLVM-компилятора, включенного в Xcode 6+.
Официальная книга по [языку программирования Swift](https://itunes.apple.com/us/book/swift-programming-language/id881256329) от Apple доступна в Apple Books.
Смотрите еще [начальное руководство](https://developer.apple.com/library/prerelease/ios/referencelibrary/GettingStarted/RoadMapiOS/index.html) Apple, которое содержит полное учебное пособие по Swift.
```swift
// Версия Swift: 3.0
// импорт модуля
import UIKit
//
// MARK: Основы
//
// Xcode поддерживает маркеры, чтобы давать примечания своему коду
// и вносить их в список обозревателя (Jump Bar)
// MARK: Метка раздела
// MARK: - Метка с разделителем
// TODO: Сделайте что-нибудь вскоре
// FIXME: Исправьте этот код
// Начиная со второй версии Swift, println и print объединены в методе print.
// Перенос строки теперь добавляется в конец автоматически.
print("Привет, мир!") // println теперь просто print
print("Привет, мир!", terminator: "") // вывод текста без переноса строки
// переменные (var), значение которых можно изменить после инициализации
// константы (let), значение которых нельзя изменить после инициализации
var myVariable = 42
let øπΩ = "значение" // именование переменной символами unicode
let π = 3.1415926
let convenience = "Ключевое слово" // контекстное имя переменной
let weak = "Ключевое слово"; let override = "еще ключевое слово" // операторы
// могут быть отделены точкой с запятой
let `class` = "Ключевое слово" // обратные апострофы позволяют использовать
// ключевые слова в именовании переменных
let explicitDouble: Double = 70
let intValue = 0007 // 7
let largeIntValue = 77_000 // 77000
let label = "некоторый текст " + String(myVariable) // Приведение типа
let piText = "Pi = \(π), Pi 2 = \(π * 2)" // Вставка переменных в строку
// Сборка особых значений
// используя ключ -D сборки конфигурации
#if false
print("Не печатается")
let buildValue = 3
#else
let buildValue = 7
#endif
print("Значение сборки: \(buildValue)") // Значение сборки: 7
/*
Опционалы - это особенность языка Swift, которая допускает вам сохранять
`некоторое` или `никакое` значения.
Язык Swift требует, чтобы каждое свойство имело значение, поэтому даже nil
должен быть явно сохранен как опциональное значение.
Optional<T> является перечислением.
*/
var someOptionalString: String? = "опционал" // Может быть nil
// как и выше, только ? - это постфиксный оператор (синтаксический сахар)
var someOptionalString2: Optional<String> = "опционал"
if someOptionalString != nil {
// я не nil
if someOptionalString!.hasPrefix("opt") {
print("содержит префикс")
}
let empty = someOptionalString?.isEmpty
}
someOptionalString = nil
/*
Использование ! для доступа к несуществующему опциональному значению генерирует
рантайм ошибку. Всегда проверяйте, что опционал содержит не пустое значение,
перед тем как раскрывать его через !.
*/
// неявная развертка опциональной переменной
var unwrappedString: String! = "Ожидаемое значение."
// как и выше, только ! - постфиксный оператор (с еще одним синтаксическим сахаром)
var unwrappedString2: ImplicitlyUnwrappedOptional<String> = "Ожидаемое значение."
// If let конструкции -
// If let это специальная конструкция в Swift, которая позволяет проверить Optional
// справа от `=` непустой, и если это так - разворачивает его и присваивает левой части.
if let someOptionalStringConstant = someOptionalString {
// имеется некоторое (`Some`) значение, не `nil`
if !someOptionalStringConstant.hasPrefix("ok") {
// нет такого префикса
}
}
// Swift поддерживает сохранение значения любого типа
// Для этих целей есть два ключевых слова `Any` и `AnyObject`
// AnyObject == id
// `Any` же, в отличие от `id` в Objective-C, `Any` работает с любым значением (Class, Int, struct и т.д.)
var anyVar: Any = 7
anyVar = "Изменять значение на строку не является хорошей практикой, но возможно."
let anyObjectVar: AnyObject = Int(1) as NSNumber
/*
Комментируйте здесь
/*
Вложенные комментарии тоже поддерживаются
*/
*/
//
// MARK: Коллекции
//
/*
Массив (Array) и словарь (Dictionary) являются структурами (struct). Так
`let` и `var` также означают, что они изменяются (var) или не изменяются (let)
при объявлении переменных этих типов.
*/
// Массив
var shoppingList = ["сом", "вода", "лимоны"]
shoppingList[1] = "бутылка воды"
let emptyArray = [String]() // let == неизменный
let emptyArray2 = Array<String>() // как и выше
var emptyMutableArray = [String]() // var == изменяемый
var explicitEmptyMutableStringArray: [String] = [] // так же как и выше
// Словарь
var occupations = [
"Malcolm": "Капитан",
"kaylee": "Техник"
]
occupations["Jayne"] = "Связи с общественностью"
let emptyDictionary = [String: Float]() // let == неизменный
let emptyDictionary2 = Dictionary<String, Float>() // как и выше
var emptyMutableDictionary = [String: Float]() // var == изменяемый
var explicitEmptyMutableDictionary: [String: Float] = [:] // то же
//
// MARK: Поток управления
//
// С помощью "," можно указать дополнительные условия для раскрытия
// опциональных значений.
let someNumber = Optional<Int>(7)
if let num = someNumber, num > 3 {
print("Больше 3х")
}
// цикл for для массива
let myArray = [1, 1, 2, 3, 5]
for value in myArray {
if value == 1 {
print("Один!")
} else {
print("Не один!")
}
}
// цикл for для словаря
var dict = ["один": 1, "два": 2]
for (key, value) in dict {
print("\(key): \(value)")
}
// цикл for для диапазона чисел
for i in -1...shoppingList.count {
print(i)
}
shoppingList[1...2] = ["бифштекс", "орехи пекан"]
// используйте ..< для исключения последнего числа
// цикл while
var i = 1
while i < 1000 {
i *= 2
}
// цикл do-while
repeat {
print("привет")
} while 1 == 2
// Переключатель
// Очень мощный оператор, представляйте себе операторы `if` с синтаксическим
// сахаром
// Они поддерживают строки, объекты и примитивы (Int, Double, etc)
let vegetable = "красный перец"
switch vegetable {
case "сельдерей":
let vegetableComment = "Добавьте немного изюма, имитируя муравьев на бревнышке."
case "огурец", "кресс-салат":
let vegetableComment = "Было бы неплохо сделать бутерброд с чаем."
case let localScopeValue where localScopeValue.hasSuffix("перец"):
let vegetableComment = "Это острый \(localScopeValue)?"
default: // обязательный (чтобы предусмотреть все возможные вхождения)
let vegetableComment = "В супе все овощи вкусные."
}
//
// MARK: Функции
//
// Функции являются типом первого класса, т.е. они могут быть вложены в функциях
// и могут передаваться между собой
// Функция с документированным заголовком Swift (формат Swift-модифицированный Markdown)
/**
Операция приветствия
- Маркер в документировании
- Еще один маркер в документации
- Parameter name : Это имя
- Parameter day : Это день
- Returns : Строка, содержащая значения name и day.
*/
func greet(name: String, day: String) -> String {
return "Привет \(name), сегодня \(day)."
}
greet(name: "Боб", day: "вторник")
// как и выше, кроме обращения параметров функции
func greet2(name: String, externalParamName localParamName: String) -> String {
return "Привет \(name), сегодня \(localParamName)"
}
greet2(name: "Иван", externalParamName: "Воскресенье")
// Функция, которая возвращает множество элементов в кортеже
func getGasPrices() -> (Double, Double, Double) {
return (3.59, 3.69, 3.79)
}
let pricesTuple = getGasPrices()
let price = pricesTuple.2 // 3.79
// Пропускайте значения кортежей с помощью подчеркивания _
let (_, price1, _) = pricesTuple // price1 == 3.69
print(price1 == pricesTuple.1) // вывод: true
print("Цена газа: \(price)")
// Именованные параметры кортежа
func getGasPrices2() -> (lowestPrice: Double, highestPrice: Double, midPrice: Double) {
return (1.77, 37.70, 7.37)
}
let pricesTuple2 = getGasPrices2()
let price2 = pricesTuple2.lowestPrice
let (_, price3, _) = pricesTuple2
print(pricesTuple2.highestPrice == pricesTuple2.1) // вывод: true
print("Самая высокая цена за газ: \(pricesTuple2.highestPrice)")
// guard утверждения
func testGuard() {
// guards обеспечивают прерывание дальнейшего выполнения функции,
// позволяя держать обработчики ошибок рядом с проверкой условия
// Объявляемая переменная находится в той же области видимости, что и guard.
guard let aNumber = Optional<Int>(7) else {
return
}
print("число равно \(aNumber)")
}
testGuard()
// Переменное число аргументов
func setup(numbers: Int...) {
// это массив
let number = numbers[0]
let argCount = numbers.count
}
// Передача и возврат функций
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
// передача по ссылке
func swapTwoInts(a: inout Int, b: inout Int) {
let tempA = a
a = b
b = tempA
}
var someIntA = 7
var someIntB = 3
swapTwoInts(a: &someIntA, b: &someIntB)
print(someIntB) // 7
//
// MARK: Замыкания
//
var numbers = [1, 2, 6]
// Функции - это частный случай замыканий ({})
// Пример замыкания.
// `->` отделяет аргументы и возвращаемый тип
// `in` отделяет заголовок замыкания от тела замыкания
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
// Когда тип известен, как и выше, мы можем сделать так
numbers = numbers.map({ number in 3 * number })
// Или даже так
//numbers = numbers.map({ $0 * 3 })
print(numbers) // [3, 6, 18]
// Хвостовое замыкание
numbers = numbers.sorted { $0 > $1 }
print(numbers) // [18, 6, 3]
// Суперсокращение, поскольку оператор < выполняет логический вывод типов
numbers = numbers.sorted(by: <)
print(numbers) // [3, 6, 18]
//
// MARK: Структуры
//
// Структуры и классы имеют очень похожие характеристики
struct NamesTable {
let names: [String]
// Пользовательский индекс
subscript(index: Int) -> String {
return names[index]
}
}
// У структур автогенерируемый (неявно) инициализатор
let namesTable = NamesTable(names: ["Иван", "Яков"])
let name = namesTable[1]
print("Имя :\(name)") // Имя: Яков
//
// MARK: Обработка ошибок
//
// Протокол `Error` используется для перехвата выбрасываемых ошибок
enum MyError: Error {
case badValue(msg: String)
case reallyBadValue(msg: String)
}
// фунции помеченные словом `throws` должны вызываться с помощью `try`
func fakeFetch(value: Int) throws -> String {
guard 7 == value else {
throw MyError.reallyBadValue(msg: "Действительно плохое значение")
}
return "тест"
}
func testTryStuff() {
// предполагается, что не будет выброшено никаких ошибок,
// в противном случае мы получим рантайм исключение
let _ = try! fakeFetch(value: 7)
// Если возникает ошибка, то выполнение продолжится. Но если значение равно nil,
// то результат будет опционалом
let _ = try? fakeFetch(value: 7)
do {
// обычно try оператор, позволяющий обработать ошибку в `catch` блоке
try fakeFetch(value: 1)
} catch MyError.badValue(let msg) {
print("Ошибка: \(msg)")
} catch {
// все остальное
}
}
testTryStuff()
//
// MARK: Классы
//
// Классы, структуры и их члены имеют трехуровневый контроль доступа
// Уровни: internal (по умолчанию), public, private
public class Shape {
public func getArea() -> Int {
return 0
}
}
// Все методы и свойства класса являются открытыми (public).
// Если вам необходимо содержать только данные
// в структурированном объекте, вы должны использовать `struct`
internal class Rect: Shape {
var sideLength: Int = 1
// Пользовательский сеттер и геттер
private var perimeter: Int {
get {
return 4 * sideLength
}
set {
// `newValue` - неявная переменная, доступная в сеттере
sideLength = newValue / 4
}
}
// Вычисляемые свойства должны быть объявлены с помощью `var`, ведь они могут меняться
var smallestSideLength: Int {
return self.sideLength - 1
}
// Ленивая загрузка свойства
// свойство subShape остается равным nil (неинициализированным),
// пока не вызовется геттер
lazy var subShape = Rect(sideLength: 4)
// Если вам не нужны пользовательские геттеры и сеттеры,
// но все же хотите запустить код перед и после вызовов геттера или сеттера
// свойств, вы можете использовать `willSet` и `didSet`
var identifier: String = "defaultID" {
// аргумент у `willSet` будет именем переменной для нового значения
willSet(someIdentifier) {
print(someIdentifier)
}
}
init(sideLength: Int) {
self.sideLength = sideLength
// последним всегда вызывается super.init, когда init с параметрами
super.init()
}
func shrink() {
if sideLength > 0 {
sideLength -= 1
}
}
override func getArea() -> Int {
return sideLength * sideLength
}
}
// Простой класс `Square` наследует `Rect`
class Square: Rect {
convenience init() {
self.init(sideLength: 5)
}
}
var mySquare = Square()
print(mySquare.getArea()) // 25
mySquare.shrink()
print(mySquare.sideLength) // 4
// преобразование объектов
let aShape = mySquare as Shape
// сравнение экземпляров, в отличие от ==, которая проверяет эквивалентность
if mySquare === mySquare {
print("Ага, это mySquare")
}
// Опциональная инициализация (init)
class Circle: Shape {
var radius: Int
override func getArea() -> Int {
return 3 * radius * radius
}
// Поместите постфиксный знак вопроса после `init` - это и будет опциональная инициализация,
// которая может вернуть nil
init?(radius: Int) {
self.radius = radius
super.init()
if radius <= 0 {
return nil
}
}
}
var myCircle = Circle(radius: 1)
print(myCircle?.getArea()) // Optional(3)
print(myCircle!.getArea()) // 3
var myEmptyCircle = Circle(radius: -1)
print(myEmptyCircle?.getArea()) // "nil"
if let circle = myEmptyCircle {
// не будет выполняться, поскольку myEmptyCircle равен nil
print("circle не nil")
}
//
// MARK: Перечисления
//
// Перечисления могут быть определенного или своего типа.
// Они могут содержать методы подобно классам.
enum Suit {
case spades, hearts, diamonds, clubs
func getIcon() -> String {
switch self {
case .spades: return "♤"
case .hearts: return "♡"
case .diamonds: return "♢"
case .clubs: return "♧"
}
}
}
// Значения перечислений допускают сокращенный синтаксис, нет необходимости
// указывать тип перечисления, когда переменная объявляется явно
var suitValue: Suit = .hearts
// Значения нецелочисленных перечислений должны быть указаны явно
// или могут выводится с помощью функции `rawValue` из имени
enum BookName: String {
case john
case luke = "Лука"
}
print("Имя: \(BookName.john.rawValue)")
// Перечисление (enum) со связанными значениями
enum Furniture {
// Связать с типом Int
case desk(height: Int)
// Связать с типами String и Int
case chair(String, Int)
func description() -> String {
switch self {
case .desk(let height):
return "Письменный стол высотой \(height) см."
case .chair(let brand, let height):
return "Стул марки \(brand) высотой \(height) см."
}
}
}
var desk: Furniture = .desk(height: 80)
print(desk.description()) // "Письменный стол высотой 80 см."
var chair = Furniture.chair("Foo", 40)
print(chair.description()) // "Стул марки Foo высотой 40 см."
//
// MARK: Протоколы
//
// `protocol` может потребовать, чтобы у соответствующих типов
// были определенные свойства экземпляра, методы экземпляра, тип методов,
// операторы и индексы.
protocol ShapeGenerator {
var enabled: Bool { get set }
func buildShape() -> Shape
}
// Протоколы, объявленные с @objc, допускают необязательные функции,
// которые позволяют вам проверять на соответствие. Для функций также необходимо указать @objc
@objc protocol TransformShape {
@objc optional func reshape()
@objc optional func canReshape() -> Bool
}
class MyShape: Rect {
var delegate: TransformShape?
func grow() {
sideLength += 2
// Размещайте знак вопроса перед опционным свойством, методом
// или индексом, чтобы не учитывать nil-значение и возвратить nil
// вместо выбрасывания ошибки выполнения (т.н. "опционная цепочка")
if let reshape = self.delegate?.canReshape?(), reshape {
// проверка делегата на выполнение метода
self.delegate?.reshape?()
}
}
}
//
// MARK: Прочее
//
// `extension`s: Добавляет расширенную функциональность к существующему типу
// Класс Square теперь "соответствует" протоколу `CustomStringConvertible`
extension Square: CustomStringConvertible {
var description: String {
return "Площадь: \(self.getArea()) - ID: \(self.identifier)"
}
}
print("Объект Square: \(mySquare)")
// Вы также можете расширить встроенные типы
extension Int {
var customProperty: String {
return "Это \(self)"
}
func multiplyBy(num: Int) -> Int {
return num * self
}
}
print(7.customProperty) // "Это 7"
print(14.multiplyBy(num: 3)) // 42
// Обобщения: Подобно языкам Java и C#. Используйте ключевое слово `where`,
// чтобы определить условия обобщений.
func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let foundAtIndex = findIndex(array: [1, 2, 3, 4], valueToFind: 3)
print(foundAtIndex == 2) // вывод: true
// Операторы:
// Пользовательские операторы могут начинаться с символов:
// / = - + * % < > ! & | ^ . ~
// или
// Unicode- знаков математики, символов, стрелок, декорации и линий/кубов,
// нарисованных символов.
prefix operator !!!
// Префиксный оператор, который утраивает длину стороны, когда используется
prefix func !!! (shape: inout Square) -> Square {
shape.sideLength *= 3
return shape
}
// текущее значение
print(mySquare.sideLength) // 4
// Используя пользовательский оператор !!!, изменится длина стороны
// путем увеличения размера в 3 раза
!!!mySquare
print(mySquare.sideLength) // 12
// Операторы также могут быть обобщенными
infix operator <->
func <-><T: Equatable> (a: inout T, b: inout T) {
let c = a
a = b
b = c
}
var foo: Float = 10
var bar: Float = 20
foo <-> bar
print("foo это \(foo), bar это \(bar)") // "foo = 20.0, bar = 10.0"
```