В первой статье этой вводной серии о 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.