Pull to refresh

Что нового в Swift 3?

Reading time10 min
Views24K
Как мы все давно знаем, Apple интегрировала Swift 3 в Xcode 8. Это первая версия языка с открытым исходным кодом, который работает как на macOS, так и на Linux. Если вы следили за процессом развития языка на Swift Evolution с декабря прошлого года и успели с ним поэкспериментировать в IBM sandbox, вы уже наверное поняли, что в нем появилось множество изменений. Совершенно уверен, что при компилировании существующего проекта в Xcode 8, ваш код удивит вас наличием ошибок. Но это поправимо. Давайте познакомимся с некоторыми изменениями в новой версий языка.
image

Изменения в новой версий, можно разделить две основные категории:

  • Удаленные функции, которые уже устарели с 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

Там происходит довольно много событий, так что давайте разобьем их на этапы:

  1. Импорт фреймворков UIKit и XCPlayground — вам необходимы чтобы создать кнопку и показать ее в редакторе playground.
  2. Определите метода нажатия, который будет выполнятся, когда пользователь нажимает на кнопку и создайте целевой? логический объект кнопки ее базовый класс NSObject, поскольку селекторы работают только с методами Objective-C.
  3. Объявление кнопки и установка свойств.
  4. Создание 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 покажет вам предупреждение, если вы не используете возвращаемое значение функции или метода. Вот пример:

image

В приведенном выше коде, метод 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.
Tags:
Hubs:
+19
Comments20

Articles

Change theme settings