Статьи

Введение в Swift: часть 2

В первой статье этой вводной серии о Swift мы поговорили о философии Swift, сначала взглянули на его синтаксис и выделили несколько ключевых отличий с Objective-C. В этой статье мы продолжим наше исследование синтаксиса Swift. Вы также узнаете об опциях и увидите, как работает управление памятью в Swift.

Если операторы идентичны в 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

Определение функций и замыканий в 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 является замыканием, которое мы передаем функции.

Пришло время поговорить о краеугольном камне объектно-ориентированного программирования, о классах. Классы, как упоминалось ранее, определены в одном файле реализации с расширением .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
}

Опционные опции — это новая концепция, если вы пришли из 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, все не необязательные переменные и константы должны иметь значение.

Если вы помните, еще во времена ARC мы использовали strong и weak ключевые слова для определения принадлежности объекта. У Swift также есть strong и weak модель собственности, но она также вводит новую, unowned . Давайте посмотрим на каждую модель владения объектами в Swift.

Сильные ссылки по умолчанию в Swift. В большинстве случаев нам принадлежит объект, на который мы ссылаемся, и мы несем ответственность за поддержание этого объекта в живых.

Поскольку по умолчанию используются строгие ссылки, нет необходимости явно сохранять строгую ссылку на объект, любая ссылка является строгой ссылкой.

Слабая ссылка в Swift указывает на то, что ссылка указывает на объект, за сохранение которого мы не несем ответственности. Он в основном используется между двумя объектами, которым не нужен другой, чтобы объект продолжал свой жизненный цикл.

Есть одно но , однако. В Swift слабые ссылки всегда должны быть переменными с необязательным типом, потому что они имеют значение nil когда ссылочный объект освобожден. weak ключевое слово используется для объявления переменной как слабой:

1
weak var view: UIView?

Неизвестные ссылки являются новыми для программистов Objective-C. Неизвестная ссылка означает, что мы не несем ответственности за поддержание ссылочного объекта живым, как слабые ссылки.

Разница со слабой ссылкой заключается в том, что неиспользуемая ссылка не устанавливается в nil когда объект, на который она ссылается, освобождается. Еще одно важное отличие от слабых ссылок состоит в том, что неподписанные ссылки определяются как необязательный тип.

Неизвестные ссылки могут быть константами. Неизвестный объект не существует без его владельца, и, следовательно, не имеющая ссылки ссылка никогда не равна nil . Неизвестным ссылкам нужно ключевое слово unowned перед определением переменной или константы.

1
unowned var view: UIView

Свифт — удивительный язык, обладающий большой глубиной и потенциалом. Писать программы с удовольствием, и он удаляет много стандартного кода, который мы пишем в Objective-C, чтобы убедиться, что наш код безопасен.

Я настоятельно рекомендую язык программирования Swift , который можно бесплатно приобрести в магазине Apple iBooks.