Изменения в новой версий, можно разделить две основные категории:
- Удаленные функции, которые уже устарели с Swift 2.2
- Улучшения языка
Давайте начнем с категории удаленных функций, поскольку ее легче понять и вы, наверняка, встречались с этими функциями, когда получали предупреждения в Xcode версий 7.3.
Операторы ++ и --
Операторы инкремента и декремента наследия языка C и их функциональность проста — добавить один или вычесть один к определенной переменной:
var i = 0
i++
++i
i--
--i
Тем не менее, все становится сложнее, когда дело доходит до принятия решения, какой из них выбрать. Каждый из них может быть представлен в двух возможных вариантах: префикс и постфикс — они все функционируют благодаря движку и возвращают значения, которые вы можете использовать или отклонить благодаря перезаггрузке операторов.
Это больше чем достаточно для начинающих, так как они были удалены — используйте операторы присваивания и сложение += и вычитание -= вместо:
var i = 0
i += 1
i -= 1
Конечно, вы можете использовать операторы сложения (+) и вычитания (-), а также — совместно использовать операторы присваивания, благодаря чему вы сможете экономить свое время при написании кода, хотя:
i = i + 1
i = i - 1
Для ознакомления: Если вы хотите больше узнать о мотивах этого изменения, посмотрите предложение Криса Латнера по этому поводу.
Стиль языка С для написания циклов уже история
Наиболее распространенным примером использования операторов инкрементов и декрементов является стиль языка С для написания цикла. Удаление операторов означает удаление всего что с ними связано, поскольку с этим всем вы уже ничего не сможете сделать, в отличии от того, что вы делали ранее с выражениями и оператором для указания диапазона.
Например, если у вас есть несколько знании, вы вероятно будете использовать цикл for для отображения числа от 1 до 10:
for (i = 1; i <= 10; i++) {
print(i)
}
В Swift 3 это невозможно. Это его прототип в языке Swift 3 — давайте рассмотрим оператор закрытого диапазона (...) в действии:
for i in 1...10 {
print(i)
}
В качестве альтернативы, вы можете также использовать для-каждого цикла с замкнутыми выражениями и сокращёнными аргументами — боле подробную информацию вы можете найти здесь.
(1...10).forEach {
print($0)
}
Для ознакомления: Если вы хотите узнать больше о мотивации этого изменения, посмотрите предложение Эрики Садун.
Удалили переменную из параметров функции
Параметры функции обычно определяются как константы, поскольку их не нужно изменять внутри функций. Тем не менее, существуют определенные случаи, когда объявление их как переменные может пригодиться. В Swift 2, вы можете отметить параметр функции как переменную с ключевым словом var. После того, как параметр будет указан как var, он создаст локальную копию значения, поэтому вы сможете изменять ее значение в теле функции.
В качестве примера, следующая функция определяет наибольший общий делитель двух заданных чисел — если вы упустили математический курс в средней школе, почитайте больше об этом здесь:
func gcd(var a: Int, var b: Int) -> Int {
if (a == b) {
return a
}
repeat {
if (a > b) {
a = a - b
} else {
b = b - a
}
} while (a != b)
return a
}
Алгоритм прост: если оба числа уже равны, выполняется возврат одного из них. В противном случае, сравните их, выполните вычитание меньшего из большего и присваивайте результат к большему, пока они не станут равными и выполните возвращение любого из них. Как вы видите, пометив а и Ь в качестве переменных, мы можем изменить их значения в функции.
Swift 3 больше не позволяет разработчикам устанавливать параметры функции, как переменные, поскольку разработчики Swift могут запутаться между var и inout. Таким образом, последняя версия Swift просто удаляет var из параметров функции.
Поэтому, чтобы написать ту же самую функцию gcd в Swift 3, необходим иной подход. Вам нужно сохранить значения параметров функции для локальных переменных:
func gcd(a: Int, b: Int) -> Int {
if (a == b) {
return a
}
var c = a
var d = b
repeat {
if (c > d) {
c = c - d
} else {
d = d - c
}
} while (c != d)
return c
}
Если вы хотите узнать больше о мотивах этого решения, вы можете прочитать оригинальное предложение.
Последовательное поведение лейблы для параметров функции
Списки параметров функции являются кортежи, так что вы можете использовать их для вызова функций, до тех пор, пока структура кортежа совпадает с прототипом функции. Возьмем функцию gcd() в качестве примера. Вы можете выполнить вызов следующим образом:
gcd(8, b: 12)
Или вы можете даже вызвать функцию, как показано ниже:
let number = (8, b: 12)
gcd(number)
Как вы видите в Swift 2, вам не нужно указать метку первого параметра. Тем не менее, вы должны указать метку для второго (и остальных параметров) при вызове функции.
Этот синтаксис является запутанным для начинающих разработчиков, поэтому он предназначен для стандартизации поведения меток. В новой версий языка Swift, вы можете вызвать функцию следующим образом:
gcd(a: 8, b: 12)
Вы должны явно указать метку для первого параметра. Если вы не сделаете этого, Xcode 8 покажет вам сообщение об ошибке.
Ваша первая реакция на это изменение может выглядеть подобно «OMG! Мне придется сделать много изменений в существующем коде ». Вы правы. Это тонны изменений. Таким образом, компания Apple предлагает способ подавить первую метку параметра вызова функции. Вы можете добавить подчеркивание к первому параметру, как показано ниже:
func gcd(_ a: Int, b: Int) -> Int {
...
}
Делая это, вы можете вызвать функцию, используя старый способ — без указания первой метки. Это поможет сделать миграцию кода с Swift 2 на Swift 3 намного проще.
О мотивации и намерении этого изменения, вы можете прочесть это предложение.
Селекторы как строки больше не используются
Давайте создадим кнопку и выполним какое-то действие, при нажатии на нее — используйте только playground, а не storyboard:
// 1
import UIKit
import XCPlayground
// 2
class Responder: NSObject {
func tap() {
print("Button pressed")
}
}
let responder = Responder()
// 3
let button = UIButton(type: .System)
button.setTitle("Button", forState: .Normal)
button.addTarget(responder, action: "tap", forControlEvents: .TouchUpInside)
button.sizeToFit()
button.center = CGPoint(x: 50, y: 25)
// 4
let frame = CGRect(x: 0, y: 0, width: 100, height: 50)
let view = UIView(frame: frame)
view.addSubview(button)
XCPlaygroundPage.currentPage.liveView = view
Там происходит довольно много событий, так что давайте разобьем их на этапы:
- Импорт фреймворков UIKit и XCPlayground — вам необходимы чтобы создать кнопку и показать ее в редакторе playground.
- Определите метода нажатия, который будет выполнятся, когда пользователь нажимает на кнопку и создайте целевой? логический объект кнопки ее базовый класс NSObject, поскольку селекторы работают только с методами Objective-C.
- Объявление кнопки и установка свойств.
- Создание view определенного размера, добавление кнопки на view и отображение его в ассистенте редактора Playground.
Посмотрите на выделенный код. Селектор кнопки является строкой. Если вы введете его неправильно, то код будет скомпилирован, но произойдет сбой во время его выполнения, так как соответствующий метод может быть не найден.
Чтобы устранить потенциальную проблему во время компиляции, в Swift 3 заменили строку селекторов ключевым словом #selector(). Это позволяет компилятору обнаружить проблему раньше, если вы не правильно укажите имя метода.
button.addTarget(responder, action: #selector(Responder.tap), for: .touchUpInside)
Для понимания причин этого изменения, вы можете прочесть предложение Доуга Грегора.
Это что касается удаленных функций. Теперь давайте перейдем к основным моментам модернизации языка.
Key-paths в виде строки
Эта функция аналогична предыдущей, но она относится key value coding (KVC) и ключевому-значению наблюдения (KVO):
class Person: NSObject {
var name: String = ""
init(name: String) {
self.name = name
}
}
let me = Person(name: "Cosmin")
me.valueForKeyPath("name")
Вы создаете класс Person, который соответствует ключевому-значению кодирования, создаете мою идентичность с классом назначенным инициализатором и используете KVC, чтобы установить имя.
Опять же, если вы сделаете это неправильно, все взорвется!
К счастью, это больше не произойдет в Swift 3. Key-path, были заменены выражением #keyPath():
class Person: NSObject {
var name: String = ""
init(name: String) {
self.name = name
}
}
let me = Person(name: "Cosmin")
me.value(forKeyPath: #keyPath(Person.name))
Для понимания причин данного изменения, вы можете прочесть предложение Девида Хартс.
Удален префикс базовых типов NS
Убрали префикс NS для базовых типов. Посмотрим как это работает. Типичным примером является работа с JSON:
let file = NSBundle.mainBundle().pathForResource("tutorials", ofType: "json")
let url = NSURL(fileURLWithPath: file!)
let data = NSData(contentsOfURL: url)
let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)
Вы можете использовать базовые классы для подключения к файлу и извлечения данных в формате JSON соответствующим образом: NSBundle -> NSURL -> NSData -> NSJSONSerialization.
Префикс NS в Swift 3 не используется, так что все это сводится к Bundle -> URL -> Data -> JSONSerialization():
let file = Bundle.main().pathForResource("tutorials", ofType: "json")
let url = URL(fileURLWithPath: file!)
let data = try! Data(contentsOf: url)
let json = try! JSONSerialization.jsonObject(with: data)
print(json)
Для этой процедуры изменения присвоения имен, вы можете проверить это предложение, написанное Тони Паркером и Филиппом Хауслером.
M_PI vs .pi
Давайте вычислим окружности и площадь круга с заданным радиусом:
let r = 3.0
let circumference = 2 * M_PI * r
let area = M_PI * r * r
Для старых версий Swift, вы используете M_PI для обозначения константы pi. Swift 3 интегрирует константу pi в тип Float, Double и CGFloat:
Float.pi
Double.pi
CGFloat.pi
Выше представленный фрагмент кода будет написан, как это в Swift 3:
let r = 3.0
let circumference = 2 * Double.pi * r
let area = Double.pi * r * r
С помощью логического вывода типа, вы можете даже опустить тип. Вот короткая версия:
let r = 3.0
let circumference = 2 * .pi * r
let area = .pi * r * r
Grand Central Dispatch
Grand Central Dispatch используется для сетевых операций, которые не блокируют пользовательский интерфейс в основном потоке. Он написано на языке C и его API является большой сложностью для начинающих разработчиков, даже для выполнения простых задач, таких как создание асинхронной очереди и выполнения каких-либо операций:
let queue = dispatch_queue_create("Swift 2.2", nil)
dispatch_async(queue) {
print("Swift 2.2 queue")
}
Swift 3 устраняет весь шаблонный код и избыточный материал, принимая объектно-ориентированный подход:
let queue = DispatchQueue(label: "Swift 3")
queue.async {
print("Swift 3 queue")
}
Для получения дополнительной информации об этом изменении, вы можете прочитать написанное Мэттом Райт.
Core Graphics теперь больше Swifty
Core Graphics представляет собой мощный графический фреймворк, но он использует интерфейс в стиле С, похожий на GCD:
let frame = CGRect(x: 0, y: 0, width: 100, height: 50)
class View: UIView {
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let blue = UIColor.blueColor().CGColor
CGContextSetFillColorWithColor(context, blue)
let red = UIColor.redColor().CGColor
CGContextSetStrokeColorWithColor(context, red)
CGContextSetLineWidth(context, 10)
CGContextAddRect(context, frame)
CGContextDrawPath(context, .FillStroke)
}
}
let aView = View(frame: frame)
Вы создаете view frame, расширяя класс UIView, переопределив метод DrawRect ().
Swift 3 принимает совершенно иной подход — сначала разворачивает текущий графический контекст и выполняет все операции рисования, впоследствии связанные с ним:
let frame = CGRect(x: 0, y: 0, width: 100, height: 50)
class View: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
let blue = UIColor.blue().cgColor
context.setFillColor(blue)
let red = UIColor.red().cgColor
context.setStrokeColor(red)
context.setLineWidth(10)
context.addRect(frame)
context.drawPath(using: .fillStroke)
}
}
let aView = View(frame: frame)
Примечание: Контекст будет нулевым перед вызовом метода DrawRect(), поэтому вам необходимо его развернуть при помощи выражения guard — подробнее об этом здесь.
Соглашение о присвоении имен глаголов и существительных
Время для грамматики! Группы методов в Swift 3 делятся на две категории: методы, которые возвращают некоторое значение — подразумеваются как существительные — и методы, которые выполняют определенный вид действий — подразумеваются как глаголы.
Здесь выполняется вывод от 10 до 1:
for i in (1...10).reverse() {
print(i)
}
Вы используете метод reverse(), для изменения диапазона. Swift 3 обрабатывает эту операцию как существительное, так как он возвращает исходный диапазон в обратном порядке. Он добавляет суффикс “ed” к методу:
for i in (1...10).reversed() {
print(i)
}
Наиболее распространенное использование кортежей/tuples, чтобы вывести содержимое массива:
var array = [1, 5, 3, 2, 4]
for (index, value) in array.enumerate() {
print("\(index + 1) \(value)")
}
Swift 3 обрабатывает этот метод как существительное, так как он возвращает кортеж, содержащий текущий индекс и значение этого массива, и добавляет суффикс “ed” к нему:
var array = [1, 5, 3, 2, 4]
for (index, value) in array.enumerated() {
print("\(index + 1) \(value)")
}
Другим примером может служить сортировка массива. Вот пример того, как вы можете отсортировать массив в порядке возрастания:
var array = [1, 5, 3, 2, 4]
let sortedArray = array.sort()
print(sortedArray)
Swift 3 обрабатывает эту операцию как существительное, так как он возвращает отсортированный массив. Метод sort теперь называется sorted:
var array = [1, 5, 3, 2, 4]
let sortedArray = array.sorted()
print(sortedArray)
Давайте отсортируем массив, без использования промежуточной константы. В Swift 2, вы можете вызвать функцию следующим образом:
var array = [1, 5, 3, 2, 4]
array.sortInPlace()
print(array)
Вы используете sortInPlace() для сортировки изменяемого массива. Swift 3 обрабатывает этот метод как глагол, так как он выполняет фактическую сортировку без возвращения значений. Он использует только базовое слово, которое описывает действие. Так sortInPlace() теперь называется sort():
var array = [1, 5, 3, 2, 4]
array.sort()
print(array)
Более подробную информацию о соглашении об именах, вы можете проверить в API Design Guidelines.
API в языке Swift
Swift 3 принимает простую философию для своих API, — опустить ненужные слова, так что если что-то является излишним или может быть выведено из контекста, удалите его:
- XCPlaygroundPage.currentPage становится PlaygroundPage.current
- button.setTitle(forState) становится button.setTitle(for)
- button.addTarget(action, forControlEvents) становится button.addTarget(action, for)
- NSBundle.mainBundle () становится Bundle.main ()
- NSData (contentsOfURL) становится URL(contentsOf)
- NSJSONSerialization.JSONObjectWithData() становится JSONSerialization.jsonObject(with)
- UIColor.blueColor () становится UIColor.blue ()
- UIColor.redColor () становится UIColor.red ()
Enum
Swift 3 обрабатывает перечисление как свойства, так что используйте lowerCamelCase вместо upperCamelCase для них:
- .System становится .system
- .TouchUpInside становится .touchUpInside
- .FillStroke становится .fillStroke
- .CGColor становится .cgColor
@discardableResult
В Swift 3, Xcode покажет вам предупреждение, если вы не используете возвращаемое значение функции или метода. Вот пример:
В приведенном выше коде, метод printMessage возвращает полученное сообщение для вызывающего объекта. Тем не менее, возвращаемое значение не используется. Это может быть потенциальной проблемой, так что компилятор в Swift 3 даст вам предупреждение.
В данном случае, если это не является обрабатываемым возвращаемым значением. Вы можете подавить предупреждение путем добавления @discardableResult в объявлении метода:
override func viewDidLoad() {
super.viewDidLoad()
printMessage(message: "Hello Swift 3!")
}
@discardableResult
func printMessage(message: String) -> String {
let outputMessage = "Output : \(message)"
print(outputMessage)
return outputMessage
}
Заключение
Это все о Swift 3. Новая версия Swift является одним из основных релизов, что делает язык еще лучше. Он содержит много фундаментальных изменений, которые, безусловно могут повлиять на ваш существующий код. Я надеюсь, что эта статья поможет вам лучше понять изменения, и, надеюсь, поможет вам сэкономить некоторое время, чтобы перенести свой проект на Swift.
Комментарии (20)
IgorFedorchuk
20.10.2016 10:09UIColor.redColor () становится UIColor.red ()
UIColor.redColor () становится UIColor.red
Конструкции типа
надо переделывать вif array?.count > 0
или использовать добавленную xcode перегрузку оператора сравненияif array != nil && array!.count > 0
func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l > r default: return rhs < lhs } }
IgorFedorchuk
20.10.2016 10:21И если есть категория на Swift для типа, у которого убрали префикс NS, то ее уже нельзя использовать в ObjC классе.
zaitsevyan
20.10.2016 12:33if (array?.count ?? 0) > 0
или
if let array = array, array.count > 0 { .... }
или
guard let array = array, array.count > 0 else { return }
iFamily
20.10.2016 11:39Еще бы SourceKitService работал нормально было бы вообще шикарно. А так постоянное пропадание подсветки синтаксиса от любого чиха, периодическое разрастание процесса SourceKitService до 20Гб и т.д. и т.п. — очень раздражает.
DrBAXA
20.10.2016 11:39+3Только мне кажется что полностью ломать обратную совместимость чтоб сменить имена методов это как-то странно?
grossws
20.10.2016 11:55+1Можно считать, что в терминах semver это версия 0.3.
farcaller
20.10.2016 12:53+1В терминах semver это как раз 3.0, так как сломана обратная совместимость, нет?
grossws
20.10.2016 13:16С моей колокольни swift пока выглядит как under heavy development, ни разу ещё нормально не стабилизировавшись. Возможно, на iOS и Mac OS X это не так.
Semver допускает несовместимость между минорными версиями в рамках 0.x
ozkriff
21.10.2016 09:05+1До версии 1.0 semver позволяет вносить ломающие изменения в 0.* выпуски
4) Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.
menzoda
20.10.2016 12:01+2Слава богу что не боятся ломать, главное чтобы в лучшую сторону. С помощью современных инструментов отрефакторить несколько имен в проекте вполне посильная задача. Если бы они предложили какую-нибудь специальную утилитку для конвертации исходников было бы вообще шикарно. При этом язык не будет превращаться в кучу костылей и анахронизмов из-за фанатичного следования принципу «обратной совместимости».
zaitsevyan
20.10.2016 12:29+3А такая утилита и есть, при запуске старого проекта в Xcode, он сразу показывает окно «Convert to Swift 3». И делает это достаточно неплохо.
andrew8712
20.10.2016 17:50+2Она делает это ужасно на сколь-либо большом проекте. Например, у меня она поменяла это:
array.filter({ $0.property == entity })
на это:
array.filter({ $0.property! == entity })
В результате получаем крэш на этапе выполнения.
PapaBubaDiop
20.10.2016 14:08С селекторами, видимо, работа не закончена.
Например, имеем такой вызов в классе DemoViewController
// // DemoViewController.swift // Election Balls func animateScore() { if tempScore<maxScore { tempScore += 1 scoreLabel.text = "\(tempScore)" Timer.scheduledTimer(timeInterval: 0.07, target: self, selector: #selector(PlayViewController.animateScore), userInfo: nil, repeats: false) } else { } }
работает правильно, хотя в вызове ошибка.
DarkEld3r
20.10.2016 15:24Я, конечно, не настоящий
сварщиксвифтер, но@discardableResult
какой-то странной штукой кажется. В смысле, сознательно делаем метод с "не особо полезным" возвращаемым значением. Не лучше тогда сделать две разные функции/перегрузки и/или дать возможность подавлять предупреждение в вызывающем коде?Johan
20.10.2016 15:56Простой пример не особо полезного возвращаемого значения (один из методов массива):
public mutating func removeAtIndex(index: Int) -> Element
@discardableResult mutating func remove(at index: Int) -> Element
Иногда нужен только побочный эффект, а иногда и результат.
дать возможность подавлять предупреждение в вызывающем коде
А вот этого, видимо, хотели избежать. Тот кто проектирует API знает, есть ли побочный эффект у функции и может правильно аннотировать. Если побочный эффект у функции имеется, то её вызов имеет смысл даже без использования результата. Если побочного эффекта нет, то без результата вызов смысла не имеет.
DarkEld3r
20.10.2016 17:11Да, согласен. Хотя, справедливости ради, важность возвращаемого значения может зависеть и от предметной области.
OgreSwamp
25.10.2016 13:29+2Это своя статья или перевод? Похоже на Google Translate, очень сложно читать.
OgreSwamp
25.10.2016 13:33+3Беглое гугление показало, что это просто перевод статьи с AppCoda.
1. При переводах принято указывать, что это перевод, а не оригинал
2. Переведено явно программно и потом, возможно, чуточку подправлено.
Yak52
Если отредактировать русский язык, то получится отличная статья.
s_suhanov
Там еще и с логической целостностью проблемы. Одно только "если вы упустили математический курс в средней школе, почитайте больше об этом здесь:" и следующий за ним кусок кода (вместо ожидаемого пояснения про наибольший общий делитель) чего стоят. :)