Swift тур
Традиция предлагает, чтобы первая программа в новом языке распечатала слова “Привет, мир!” на экране. В Swift это может быть сделано в одной строке:
println("Hello, world!")
Если Вы записали код в C или Objective C, этот синтаксис выглядит знакомым Вам — в Swift, эта строка кода является полной программой. Вы не должны импортировать отдельную библиотеку для функциональности как строковая обработка или ввод/вывод. Код, записанный в глобальной области видимости, используется в качестве точки входа для программы, таким образом, Вам не нужен a main
функция. Вы также не должны писать точки с запятой в конце каждого оператора.
Этот тур дает Вам достаточно информации, чтобы начать писать код в Swift путем показа Вам, как выполнить множество программирования задач. Не волнуйтесь, не понимаете ли Вы что-то — все представленное на этом туре объяснено подробно в остальной части этой книги.
Простые значения
Использовать let
сделать константу и var
сделать переменную. Значение константы не должно быть известно во время компиляции, но необходимо присвоить его значение точно один раз. Это означает, что можно использовать константы для именования значения, которое Вы определяете один раз, но используете во многих местах.
var myVariable = 42
myVariable = 50
let myConstant = 42
Константа или переменный должна иметь тот же тип как значение, которое Вы хотите присвоить ему. Однако необходимо не всегда писать тип явно. Обеспечение значения при создании константы или переменный позволяет компилятору вывести свой тип. В примере выше, компилятор выводит это myVariable
целое число, потому что его начальное значение является целым числом.
Если начальное значение не предоставляет достаточно информации (или если нет никакого начального значения), укажите тип путем записи его после переменной, разделенной двоеточием.
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
Значения неявно никогда не преобразовываются в другой тип. Если необходимо преобразовать значение в другой тип, явно сделайте экземпляр желаемого типа.
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
Существует еще более простой способ включать значения в строки: Запишите значение в круглых скобках и запишите наклонную черту влево (\
) перед круглыми скобками. Например:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
Создайте массивы и словари с помощью скобок ([]
), и доступ их элементы путем записи индекса или ключа в скобках.
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
Для создания пустого массива или словаря используйте синтаксис инициализатора.
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
Если информация о типе может быть выведена, можно записать пустой массив как []
и пустой словарь как [:]
— например, когда Вы устанавливаете новое значение для переменной или передаете параметр функции.
shoppingList = []
occupations = [:]
Поток управления
Использовать 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
}
}
println(teamScore)
В if
оператор, условное выражение должно быть булевым выражением — это означает что код такой как if score { ... }
ошибка, не неявное сравнение с нулем.
Можно использовать if
и let
вместе для работы со значениями, которые могли бы отсутствовать. Эти значения представлены как optionals. Дополнительное значение или содержит значение или содержит nil
указать, что отсутствует значение. Запишите вопросительный знак (?
) после типа значения для маркировки значения как дополнительное.
var optionalString: String? = "Hello"
println(optionalString == nil)
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
Если дополнительное значение nil
, условное выражение false
и код в фигурных скобках пропускается. Иначе, дополнительное значение развернуто и присвоено константе после let
, который делает развернутое значение доступным в блоке кода.
Переключатели поддерживают любой вид данных и большое разнообразие операций сравнения — они не ограничиваются целыми числами и тестами для равенства.
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."
}
Заметьте как let
может использоваться в образце для присвоения значения, соответствовавшего ту часть образца к константе.
После выполнения кода в случае переключателя, соответствовавшем, выходы программы от оператора переключения. Выполнение не продолжается к следующему случаю, таким образом, нет никакой потребности явно убежать из переключателя в конце кода каждого случая.
Вы используете 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
}
}
}
println(largest)
Использовать while
для повторения блока кода до, условие изменяется. Условие цикла может быть в конце вместо этого, гарантировав, что цикл выполняется, по крайней мере, один раз.
var n = 2
while n < 100 {
n = n * 2
}
println(n)
var m = 2
do {
m = m * 2
} while m < 100
println(m)
Можно сохранить индекс в цикле — любой при помощи ..<
сделать диапазон индексов или путем записи явной инициализации, условия и инкремента. Эти два цикла делают ту же вещь:
var firstForLoop = 0
for i in 0..<4 {
firstForLoop += i
}
println(firstForLoop)
var secondForLoop = 0
for var i = 0; i < 4; ++i {
secondForLoop += i
}
println(secondForLoop)
Использовать ..<
сделать диапазон, опускающий его верхнее значение и использование ...
сделать диапазон, включающий оба значения.
Функции и закрытия
Использовать func
объявить функцию. Вызовите функцию следующим ее имя со списком параметров в круглых скобках. Использовать ->
разделить названия параметра и типы от типа возврата функции.
func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
greet("Bob", "Tuesday")
Используйте кортеж, чтобы сделать составное значение — например, возвратить многократные значения из функции. Элементы кортежа могут быть отнесены или в по имени или числом.
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics([5, 3, 100, 3, 9])
println(statistics.sum)
println(statistics.2)
Функции могут также взять переменное число параметров, собрав их в массив.
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()
Функции являются первоклассным типом. Это означает, что функция может возвратить другую функцию как свое значение.
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
})
У Вас есть несколько опций для записи закрытий более кратко. Когда тип закрытия уже известен, такие как обратный вызов для делегата, можно опустить тип его параметров, его тип возврата или обоих. Закрытия отдельного оператора неявно возвращают значение своего единственного оператора.
let mappedNumbers = numbers.map({ number in 3 * number })
println(mappedNumbers)
Можно обратиться к параметрам числом вместо по имени — этот подход особенно полезен в очень коротких закрытиях. Закрытие, переданное как последний параметр функции, может сразу появиться после круглых скобок.
let sortedNumbers = sorted(numbers) { $0 > $1 }
println(sortedNumbers)
Объекты и классы
Использовать 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()
Эта версия Shape
класс пропускает что-то важное: инициализатор для установки класса, когда создается экземпляр. Использовать 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."
}
}
Заметьте как self
используется для различения name
свойство от name
параметр инициализатору. Параметры инициализатору передаются как вызов функции при создании экземпляра класса. Каждому свойству нужно присвоенное значение — любой в его объявлении (как с numberOfSides
) или в инициализаторе (как с name
).
Использовать deinit
для создания deinitializer, если необходимо выполнить некоторую очистку, перед, объект освобожден.
Подклассы включают свое суперимя класса после их имени класса, разделенного двоеточием. Нет никакого требования для классов для разделения на подклассы любого стандартного корневого класса, таким образом, можно включать или опустить суперкласс по мере необходимости.
Методы на подклассе, переопределяющие реализацию суперкласса, отмечены с 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()
В дополнение к простым свойствам, которые сохранены, свойства могут иметь метода get и метод set.
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 triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
println(triangle.perimeter)
triangle.perimeter = 9.9
println(triangle.sideLength)
В методе set для perimeter
, новое значение имеет неявное имя newValue
. Можно обеспечить явное имя в круглых скобках после set
.
Заметьте что инициализатор для EquilateralTriangle
класс имеет три различных шага:
Установка значения свойств, которые объявляет подкласс.
Вызов инициализатора суперкласса.
Изменение значения свойств определяется суперклассом. Любая дополнительная работа установки, использующая методы, методов get или методы set, может также быть выполнена в этой точке.
Если Вы не должны вычислять свойство, но все еще должны обеспечить код, выполняющийся прежде и после установки нового значения, использовать 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")
println(triangleAndSquare.square.sideLength)
println(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
println(triangleAndSquare.triangle.sideLength)
Методы на классах имеют одно важное различие от функций. Названия параметра в функциях используются только в функции, но имена параметров в методах также используются при вызове метода (за исключением первого параметра). По умолчанию метод имеет то же имя для своих параметров при вызове его и в самом методе. Можно указать второе имя, использующееся в методе.
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.rawValue)
}
}
}
let ace = Rank.Ace
let aceRawValue = ace.rawValue
В примере выше, тип необработанного значения перечисления Int
, таким образом, только необходимо указать первое необработанное значение. Остальная часть необработанных значений присваивается в порядке. Можно также использовать строки или числа с плавающей точкой как необработанный тип перечисления. Используйте rawValue
свойство для доступа к необработанному значению элемента перечисления.
Используйте init?(rawValue:)
инициализатор для создания экземпляра из перечисления от необработанного значения.
if let convertedRank = Rank(rawValue: 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()
Заметьте два пути который Hearts
элемент перечисления является вышеуказанным: При присвоении значения hearts
постоянный, элемент перечисления Suit.Hearts
именуется его полным именем, потому что константа не имеет явного типа указанным. В переключателе элемент перечисления именуется сокращенной формой .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()
Экземпляр элемента перечисления может иметь значения, связанные с экземпляром. Экземплярам того же элемента перечисления можно было связать различные значения с ними. Вы обеспечиваете присваиваемые значения при создании экземпляра. Присваиваемые значения и необработанные значения отличаются: необработанное значение элемента перечисления является тем же для всех его экземпляров, и Вы обеспечиваете необработанное значение при определении перечисления.
Например, рассмотрите случай запроса восхода солнца и время заката от сервера. Сервер или отвечает информацией, или это отвечает некоторой информацией об ошибке.
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)"
}
Заметьте, как восход солнца и времена заката извлечен из 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
не нуждается ни в одном из его методов, отмеченных как видоизменение, потому что методы на классе могут всегда изменять класс.
Использовать extension
добавить функциональность к существующему типу, такому как новые методы и вычисленные свойства. Можно использовать расширение для добавления соответствия протокола к типу, объявляющемуся в другом месте, или даже к типу, который Вы импортировали из библиотеки или платформы.
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
println(7.simpleDescription)
Можно использовать имя протокола точно так же, как любой другой именованный тип — например, для создания набора объектов, имеющих различные типы, но что все соответствуют отдельному протоколу. Когда Вы работаете со значениями, тип которых является типом протокола, методы вне определения протокола не доступны.
let protocolValue: ExampleProtocol = a
println(protocolValue.simpleDescription)
// println(protocolValue.anotherProperty) // Uncomment to see the error
Даже при том, что переменная protocolValue
имеет тип выполнения SimpleClass
, компилятор обрабатывает его как данный тип ExampleProtocol
. Это означает, что Вы не можете случайно методы доступа или свойства, которые класс реализует в дополнение к его соответствию протокола.
Обобщения
Напишите имя в угловых скобках для создания родовой функции или типа.
func repeat<Item>(item: Item, times: Int) -> [Item] {
var result = [Item]()
for i in 0..<times {
result.append(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: SequenceType, U: SequenceType, T.Generator.Element: Equatable, T.Generator.Element == U.Generator.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])
В простых случаях можно опустить where
и просто запишите протокол или имя класса после двоеточия. Запись <T: Equatable>
совпадает с записью <T where T: Equatable>
.