В первой статье этой вводной серии о Swift мы поговорили о философии Swift, сначала взглянули на его синтаксис и выделили несколько ключевых отличий с Objective-C. В этой статье мы продолжим наше исследование синтаксиса Swift. Вы также узнаете об опциях и увидите, как работает управление памятью в Swift.
1. Условные и циклы
Если
Если операторы идентичны в Swift и Objective-C, за исключением двух тонких различий:
- круглые скобки вокруг условной переменной являются необязательными
- необходимы фигурные скобки
Это единственные различия с утверждениями if в Objective-C.
Изменяется
Как мы видели в первой статье, Swift включает в себя два оператора диапазона ..< и ... чтобы указать диапазон значений. Эти два оператора являются оператором полузакрытого диапазона и оператором замкнутого диапазона .
Полузакрытый диапазон, такой как 1..<5 , представляет значения 1, 2, 3 и 4, исключая 5. Закрытый диапазон, такой как 1...5 , представляет значения 1, 2, 3, 4 и включает в себя 5.
Диапазоны могут использоваться for циклов, индекса array и даже в операторах switch . Взгляните на следующие примеры.
|
1
2
3
4
5
|
// for loop example
for i in 1..<10 {
}
// iterates from 1 to 9
|
|
1
2
3
4
5
6
|
// array subscript example
let someArray = [«apple»,»pair»,»peach»,»watermelon»,»strawberry»]
for fruit in someArray[2..<4] {
println(fruit)
}
// outputs: peach and watermelon
|
|
01
02
03
04
05
06
07
08
09
10
11
|
// switch example
switch someInt {
case 0:
// do something with 0
case 1..<5:
// do something with 1,2,3,4
case 5…10:
// do something with 5,6,7,8,9,10
default:
// everything else
}
|
переключатель
Операторы Switch являются более мощными в Swift, чем в Objective-C. В Objective-C результат выражения оператора switch должен иметь тип integer, а значения каждого оператора case должны быть константой или константным выражением. Это не правда в Swift. В Swift операторы case могут быть любого типа, включая диапазоны.
В Swift у switch нет операторов break и он автоматически не перебегает из одного дела в другое. При написании оператора switch необходимо позаботиться о том, чтобы все условия обрабатывались его операторами case, в противном случае это приведет к ошибке компилятора. Надежный способ охватить все условия — включить оператор case по default .
Вот пример оператора switch со случаями String :
|
1
2
3
4
5
6
7
8
9
|
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.»
default:
let vegetableComment = «Everything tastes good in soup.»
}
|
В Swift операторы case не проваливаются по умолчанию. Это было продуманное решение, чтобы избежать распространенных ошибок. Если требуется конкретный случай, вы можете использовать ключевое слово fallthrough чтобы указать это компилятору.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
switch someInt {
case 0:
// do something with 0
case 1:
// do something with 1
case 2:
// do something with 2
fallthrough
default:
//do something for everything else
}
// case 2 will fall through to default case
|
Это не останавливается здесь. Swift добавляет две другие функции для переключения: привязки значений и предложение where . Привязка значений используется с ключевыми словами case let для привязки константы к соответствующему регистру. Предложение where добавляет в условие case дополнительное условие, используя ключевое слово where .
Эти две концепции лучше объяснить с помощью примеров. Следующий блок кода показывает, как работает привязка значений .
|
1
2
3
4
5
6
7
8
9
|
let somePoint = (xaxis:2, yaxis:0)
switch somePoint {
case (let x, 0):
println(«on the x-axis with an x value of \(x)»)
case (0, let y):
println(«on the y-axis with ay value of \(y)»)
case let (x, y):
println(«somewhere else at (\(x), \(y))»)
}
|
Первый оператор case, case (let x, 0) , будет соответствовать значениям, где yaxis равен 0 и любому значению для xaxis , и мы привязываем xaxis к константе x которая будет использоваться внутри оператора case.
Вот пример предложения where в действии.
|
01
02
03
04
05
06
07
08
09
10
11
|
let vegetable = «red pepper»
switch vegetable {
case «celery»:
println(«Add some raisins and make ants on a log.»)
case let x where x.hasSuffix(«pepper»):
println(«I’m allergic to \(x)»)
default:
println( «Everything tastes good in soup.»)
}
// outputs: I’m allergic to red pepper
|
2. Функции и замыкания
функции
Определение функций и замыканий в Swift легко. Разработчики Objective-C знают их лучше как функции и блоки.
В Swift параметры функций могут иметь значения по умолчанию, которые напоминают языки сценариев, такие как PHP и Ruby.
Синтаксис для функций следующий:
|
1
2
3
4
|
func functionName(parameterName: Type = DefaultValue) -> returnType {
[…]
return returnType;
}
|
Чтобы написать функцию sayHello которая принимает name параметра типа String и возвращает Bool случае успеха, мы пишем следующее:
|
1
2
3
4
5
6
7
8
|
func sayHello(name: String) -> Bool {
println(«hello \(name)»);
return true;
}
sayHello(«World»)
// output
// hello World
|
Чтобы передать значение по умолчанию для параметра name , реализация функции должна выглядеть следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
func sayHello(name: String = «World») -> Bool {
println(«hello \(name)»);
return true;
}
sayHello()
// output
// hello World
sayHello(«mike»)
// output
// hello mike
|
Особенностью, которая полностью отсутствует в Objective-C, являются кортежи . В Swift функции могут возвращать несколько значений в форме кортежа. Кортежи обрабатываются как одна переменная, что означает, что вы можете передавать ее как переменную.
Кортежи очень просты в использовании. Фактически, мы уже работали с кортежами в предыдущей статье, когда мы перечислили словарь. В следующем фрагменте кода пара ключ / значение является кортежем.
|
1
2
3
|
for (key,value) in someDictionary {
println(«Key \(key) has value \(value)»
}
|
Как используются кортежи и как вы от них выигрываете? Давайте посмотрим на другой пример. Давайте sayHello вышеупомянутую функцию sayHello чтобы она возвращала логическое значение в случае успеха, а также полученное сообщение. Мы делаем это, возвращая кортеж (Bool, String) . Обновленная функция sayHello выглядит следующим образом:
|
1
2
3
4
5
6
7
8
9
|
func sayHello(name: String = «World») -> (Bool, String) {
let greeting = «hello \(name)»
return (true, greeting);
}
let (success, greeting) = sayHello()
println(«sayHello returned success:\(success) with greeting: \(greeting)»);
// output
// sayHello returned success:1 with greeting: hello World
|
Кортежи долгое время были в списке пожеланий многих программистов Objective-C.
Еще одна интересная особенность кортежей — мы можем назвать возвращаемые переменные. Если мы вернемся к предыдущему примеру и дадим имена переменным кортежа, мы получим следующее:
|
1
2
3
4
5
6
7
8
9
|
func sayHello(name: String = «World») -> (success: Bool, greeting: String) {
let greeting = «hello \(name)»
return (true, greeting);
}
let status = sayHello()
println(«sayHello returned success:\(status.success) with greeting: \(status.greeting)»);
// output
// sayHello returned success:1 with greeting: hello World
|
Это означает, что вместо определения отдельной константы для каждого возвращаемого элемента кортежа мы можем получить доступ к возвращенным элементам кортежа, используя точечную запись, как показано в примере выше, status.success и status.greeting .
Затворы
Замыкания в Swift такие же, как блоки в Objective-C. Они могут быть определены внутри строки, переданы в качестве параметра или возвращены функциями. Мы используем их точно так же, как и блоки в Objective-C.
Определение замыканий также легко. На самом деле, функция — это особый случай замыканий. Поэтому неудивительно, что определение замыкания очень похоже на определение функции.
Замыкания являются типом первого класса, что означает, что они могут передаваться и возвращаться функциями точно так же, как и любой другой тип, такой как Int , String , Bool и т. Д. Замыкания — это, по сути, блоки кода, которые можно вызывать позже и которые имеют доступ к область, в которой они были определены.
Создать безымянное замыкание так же просто, как заключить блок кода в фигурные скобки. Параметры и тип возврата замыкания отделяются от тела замыкания ключевым словом in .
Допустим, мы хотим определить замыкание, которое возвращает true если число четное, тогда это замыкание может выглядеть примерно так:
|
1
2
3
4
5
|
let isEven = {
(number: Int) -> Bool in
let mod = number % 2
return (mod==0)
}
|
Закрытие isEven принимает Int качестве единственного параметра и возвращает Bool . Тип этого замыкания (number: Int) -> Bool или (Int -> Bool) для краткости. Мы можем вызвать isEven любом месте нашего кода, как если бы мы isEven блок кода в Objective-C.
Чтобы передать замыкание этого типа в качестве параметра функции, мы используем тип замыкания в определении функции:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
let isEven = {
(number: Int) -> Bool in
let mod = number % 2;
return (mod==0);
}
func verifyIfEven(number: Int, verifier:(Int->Bool)) ->Bool {
return verifier(number);
}
verifyIfEven(12, isEven);
// returns true
verifyIfEven(19, isEven);
// returns false
|
В приведенном выше примере параметр verifyIfEven функции verifyIfEven является замыканием, которое мы передаем функции.
3. Классы и структуры
Классы
Пришло время поговорить о краеугольном камне объектно-ориентированного программирования, о классах. Классы, как упоминалось ранее, определены в одном файле реализации с расширением .swift . Объявления свойств и методы все определены в этом файле.
Мы создаем класс с ключевым словом class за которым следует имя класса. Реализация класса заключена в пару фигурных скобок. Как и в Objective-C, соглашение об именах для классов должно использовать верхний регистр верблюда для имен классов.
|
1
2
3
4
|
class Hotel {
//properties
//functions
}
|
Для создания экземпляра класса Hotel мы пишем:
|
1
|
let h = Hotel()
|
В Swift нет необходимости вызывать init для объектов, так как init вызывается автоматически для нас.
Наследование классов происходит по той же схеме, что и в Objective-C, двоеточие разделяет имя класса и имя его суперкласса. В следующем примере Hotel наследуется от класса BigHotel .
|
1
2
3
|
class BigHotel: Hotel {
}
|
Как и в Objective-C, мы используем точечную запись для доступа к свойствам объекта. Однако Swift также использует точечную нотацию для вызова методов класса и экземпляра, как вы можете видеть ниже.
|
1
2
3
4
5
6
7
|
// Objective-C
UIView* view = [[UIView alloc] init];
[self.view addSubview:view];
// Swift
let view = UIView()
self.view.addSubview(view)
|
свойства
Другое отличие Objective-C состоит в том, что Swift не различает переменные экземпляра (ivars) и свойства. Переменная экземпляра является свойством.
Объявление свойства аналогично определению переменной или константы с использованием ключевых слов var и let . Единственная разница — это контекст, в котором они определены, то есть контекст класса.
|
1
2
3
4
|
class Hotel {
let rooms = 10
var fullRooms = 0
}
|
В приведенном выше примере rooms — это неизменное значение, константа, fullRooms 10 а fullRooms — это переменная с начальным значением 0 , которую мы можем изменить позже. Правило состоит в том, что свойства должны быть инициализированы, когда они объявлены. Единственное исключение из этого правила — дополнительные, о которых мы поговорим чуть позже.
Вычисленные Свойства
Язык Swift также определяет вычисляемые свойства. Вычисленные свойства — это не более чем причудливые методы получения и установки, которые не хранят значения. Как видно из их названия, они вычисляются или оцениваются на лету.
Ниже приведен пример вычисляемого свойства. Для остальных примеров я изменил свойство rooms на var . Вы узнаете почему позже.
|
1
2
3
4
5
6
7
8
9
|
class Hotel {
var rooms = 10
var fullRooms = 0
var description: String {
get {
return «Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)»
}
}
}
|
Поскольку свойство description доступно только для чтения и имеет только оператор return , мы можем опустить ключевое слово get и фигурные скобки и оставить только оператор return . Это сокращение, и это то, что я буду использовать в оставшейся части этого урока.
|
1
2
3
4
5
6
7
|
class Hotel {
var rooms = 10
var fullRooms = 0
var description: String {
return «Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)»
}
}
|
Мы также можем определить вычисляемые свойства для чтения и записи. В нашем классе Hotel мы хотим emptyRooms свойство emptyRooms которое получает количество пустых комнат в отеле, но мы также хотим обновить fullRooms когда мы устанавливаем вычисляемое свойство emptyRooms . Мы можем сделать это, используя ключевое слово set как показано ниже.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class Hotel {
var rooms = 10
var fullRooms = 0
var description: String {
return «Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)»
}
var emptyRooms :Int {
get {
return rooms — fullRooms
}
set {
// newValue constant is available here
// containing the passed value
if(newValue < rooms) {
fullRooms = rooms — newValue
} else {
fullRooms = rooms
}
}
}
}
let h = Hotel()
h.emptyRooms = 3
h.description
// Size of Hotel: 10 rooms capacity:7/10
|
В установщике newValue константа newValue передается нам и представляет значение, переданное установщику. Также важно отметить, что вычисляемые свойства всегда объявляются как переменные с использованием ключевого слова var , потому что их вычисленное значение может изменяться.
методы
Мы уже рассмотрели функции ранее в этой статье. Методы — это не что иное, как функции, связанные с типом, таким как класс. В следующем примере мы реализуем метод экземпляра bookNumberOfRooms в классе Hotel мы создали ранее.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
class Hotel {
var rooms = 10
var fullRooms = 0
var description: String {
return «Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)»
}
var emptyRooms :Int {
get {
return rooms — fullRooms
}
set {
// newValue constant is available here
// containing the passed value
if(newValue < rooms) {
fullRooms = rooms — newValue
} else {
fullRooms = rooms
}
}
}
func bookNumberOfRooms(room:Int = 1) -> Bool
{
if(self.emptyRooms>room) {
self.fullRooms++;
return true
} else {
return false
}
}
}
let h = Hotel()
h.emptyRooms = 7
h.description
//Size of Hotel: 10 rooms capacity:3/10
h.bookNumberOfRooms(room: 2)
// returns true
h.description
//Size of Hotel: 10 rooms capacity:5/10
h.bookNumberOfRoom()
// returns true
h.description
//Size of Hotel: 10 rooms capacity:6/10
|
Инициализаторы
Инициализатором по умолчанию для классов является init . В функции init мы устанавливаем начальные значения экземпляра, который создается.
Например, если нам нужен подкласс Hotel с 100 номерами, то нам потребуется инициализатор, чтобы установить rooms свойства rooms значение 100 . Помните, что ранее я поменял rooms с постоянной на переменную в классе Hotel . Причина в том, что мы не можем изменить унаследованные константы в подклассе, могут быть изменены только унаследованные переменные.
|
01
02
03
04
05
06
07
08
09
10
|
class BigHotel: Hotel {
init() {
super.init()
rooms = 100
}
}
let bh = BigHotel()
println(bh.description);
//Size of Hotel: 100 rooms capacity:0/100
|
Инициализаторы также могут принимать параметры. В следующем примере показано, как это работает.
|
01
02
03
04
05
06
07
08
09
10
|
class CustomHotel: Hotel {
init(size:Int) {
super.init()
rooms = size
}
}
let c = CustomHotel(size:20)
c.description
//Size of Hotel: 20 rooms capacity:0/20
|
Переопределяющие методы и вычисляемые свойства
Это одна из самых крутых вещей в Swift. В Swift подкласс может переопределять как методы, так и вычисляемые свойства. Для этого мы используем ключевое слово override . Давайте переопределим description вычисляемого свойства в классе CustomHotel :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
class CustomHotel: Hotel {
init(size:Int) {
super.init()
rooms = size
}
override var description:String {
return super.description + » Howdy!»
}
}
let c = CustomHotel(size:20)
c.description
// Size of Hotel: 20 rooms capacity:0/20 Howdy!
|
Результатом является то, что description возвращает результат метода description суперкласса со строкой "Howdy!" добавлено к нему.
Что хорошего в переопределении методов и вычисляемых свойств, так это ключевое слово override . Когда компилятор видит ключевое слово override , он проверяет, реализует ли суперкласс класса переопределяемый метод. Компилятор также проверяет, не конфликтуют ли свойства и методы класса со свойствами или методами, расположенными выше в дереве наследования.
Я не знаю, сколько раз опечатка в переопределенном методе в Objective-C заставляла меня ругаться, потому что код не работал. В Swift компилятор скажет вам точно, что не так в этих ситуациях.
сооружения
Структуры, определенные с помощью ключевого слова struct , являются более мощными в Swift, чем в C и Objective-C. В C структуры определяют только значения и указатели. Структуры Swift похожи на структуры C, но они также поддерживают вычисляемые свойства и методы.
Все, что вы можете сделать с классом, вы можете сделать со структурой, с двумя важными отличиями:
- структуры не поддерживают наследование, как классы
- структуры передаются по значению, а классы передаются по ссылке
Вот несколько примеров структур в Swift:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
struct Rect {
var origin: Point
var size: Size
var area: Double {
return size.width * size.height
}
func isBiggerThanRect(r:Rect) -> Bool {
return (self.area > r.area)
}
}
struct Point {
var x = 0
var y = 0
}
struct Size {
var width = 0
var height = 0
}
|
4. Необязательные
Решение проблемы
Опционные опции — это новая концепция, если вы пришли из Objective-C. Они решают проблему, с которой мы все сталкиваемся как программисты. Когда мы обращаемся к переменной, в значении которой мы не уверены, мы обычно возвращаем индикатор, известный как часовой , чтобы указать, что возвращаемое значение является нулевым значением. Позвольте мне проиллюстрировать это на примере из Objective-C:
|
1
2
3
4
|
NSString* someString = @»ABCDEF»;
NSInteger pos = [someString rangeOfString:@»B»].location;
// pos = 1
|
В приведенном выше примере мы пытаемся найти позицию @"B" в someString . Если @"B" найден, его местоположение или положение сохраняется в pos . Но что произойдет, если @"B" не найден в someString ?
В документации говорится, что rangeOfString: возвращает NSRange с location установленным в константу NSNotFound . В случае rangeOfString: NSNotFound является NSNotFound . Часовые используются для указания того, что возвращаемое значение недействительно.
В Какао есть много применений этой концепции, но значение часового отличается от контекста к контексту, 0 , -1 , NULL , NSIntegerMax , INT_MAX , Nil и т. Д. Проблема для программиста состоит в том, что она должна помнить, какой часовой страж используется в каком контексте. Если программист не внимателен, он может ошибочно принять действительное значение для сторожа и наоборот. Swift решает эту проблему с опциями. По словам Брайана Ланье, «Факультет — это тот, кто правит ими всеми».
Необязательные параметры имеют два состояния: nil состояние, что означает, что необязательное значение не содержит значения, и второе состояние, что означает, что оно содержит действительное значение. Думайте об опциях как о пакете с индикатором, который сообщит вам, является ли содержимое пакета действительным или нет.
использование
Все типы в Swift могут стать опциональными. Мы определяем необязательный, добавив ? после объявления типа вот так:
|
1
2
3
|
let someInt: Int?
// someInt == nil
|
Мы присваиваем значение необязательному пакету так же, как мы делаем с константами и переменными.
|
1
2
3
|
someInt = 10
// someInt!
|
Помните, что дополнительные функции похожи на пакеты. Когда мы объявили let someInt: Int? мы определили пустое поле со значением nil . Присваивая значение 10 необязательному, поле содержит целое число, равное 10 и его индикатор или состояние становится не равным нулю .
Чтобы добраться до содержимого необязательно, мы используем ! оператор. Мы должны быть уверены, что опциональное значение имеет допустимое значение перед его развертыванием. Несоблюдение этого требования приведет к ошибке во время выполнения. Вот как мы получаем доступ к значению, хранящемуся в необязательном порядке:
|
1
2
3
4
5
6
7
|
if ( someInt != nil) {
println(«someInt: \(someInt!)»)
} else {
println(«someInt has no value»)
}
// someInt: 10
|
Приведенный выше шаблон настолько распространен в Swift, что мы можем упростить приведенный выше блок кода, используя необязательную привязку с ключевыми словами if let . Посмотрите на обновленный блок кода ниже.
|
1
2
3
4
5
|
if let value = someInt {
println(«someInt: \(value)»)
} else {
println(«someInt has no value»)
}
|
Необязательные являются единственным типом, который может принимать nil значение. Константы и переменные не могут быть инициализированы или установлены в nil . Это часть политики безопасности Swift, все не необязательные переменные и константы должны иметь значение.
5. Управление памятью
Если вы помните, еще во времена ARC мы использовали strong и weak ключевые слова для определения принадлежности объекта. У Swift также есть strong и weak модель собственности, но она также вводит новую, unowned . Давайте посмотрим на каждую модель владения объектами в Swift.
strong
Сильные ссылки по умолчанию в Swift. В большинстве случаев нам принадлежит объект, на который мы ссылаемся, и мы несем ответственность за поддержание этого объекта в живых.
Поскольку по умолчанию используются строгие ссылки, нет необходимости явно сохранять строгую ссылку на объект, любая ссылка является строгой ссылкой.
weak
Слабая ссылка в Swift указывает на то, что ссылка указывает на объект, за сохранение которого мы не несем ответственности. Он в основном используется между двумя объектами, которым не нужен другой, чтобы объект продолжал свой жизненный цикл.
Есть одно но , однако. В Swift слабые ссылки всегда должны быть переменными с необязательным типом, потому что они имеют значение nil когда ссылочный объект освобожден. weak ключевое слово используется для объявления переменной как слабой:
|
1
|
weak var view: UIView?
|
unowned
Неизвестные ссылки являются новыми для программистов Objective-C. Неизвестная ссылка означает, что мы не несем ответственности за поддержание ссылочного объекта живым, как слабые ссылки.
Разница со слабой ссылкой заключается в том, что неиспользуемая ссылка не устанавливается в nil когда объект, на который она ссылается, освобождается. Еще одно важное отличие от слабых ссылок состоит в том, что неподписанные ссылки определяются как необязательный тип.
Неизвестные ссылки могут быть константами. Неизвестный объект не существует без его владельца, и, следовательно, не имеющая ссылки ссылка никогда не равна nil . Неизвестным ссылкам нужно ключевое слово unowned перед определением переменной или константы.
|
1
|
unowned var view: UIView
|
Вывод
Свифт — удивительный язык, обладающий большой глубиной и потенциалом. Писать программы с удовольствием, и он удаляет много стандартного кода, который мы пишем в Objective-C, чтобы убедиться, что наш код безопасен.
Я настоятельно рекомендую язык программирования Swift , который можно бесплатно приобрести в магазине Apple iBooks.