Лекция 4 CS193P Winter 2015 — Больше Swift и Foundation Frameworks. (часть 2. AnyObject, as?, is, String.Index)

Title iOS 8

STANFORD UNIVERSITY: Разработка iOS 8 приложений с Swift CS193P

 Лекция 4: Больше Swift и Foundation Frameworks

Профессор Пол Хэгарти (Paul Hegarty)

Лекцию на английском языке и слайды можно найти на  iTunes название  “4. More Swift and Foundation Frameworks”.

Русскоязычный неавторизованный конспект лекций принадлежит сайту bestkora.com/IosDeveloper

Начало: 1 — ая часть лекции (0 — 37 минут) находится здесь.
Этот документ:  продолжение: 2 — ая часть лекции (37 минута — конец ) 

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

————————————- 37 -ая минута лекции ————————-

Давайте поговорим о наследовании init. Потому что это также сложно. Ниже представлены правила при наследовании  init.

Screen Shot 2015-02-15 at 6.16.20 PM


Инициализация

  • Наследование init.

Если  вы не реализуете никакие назначенные inits, то вы унаследуете все назначенные inits вашего superclass
Если  вы переопределите (override)  все назначенные inits вашего superclass, то вы унаследуете все convenience inits вашего superclass
Если  вы не реализуете ни одного  inits в, то вы унаследуете все  inits вашего superclass
Любой  inits, унаследованный по этим правилам, должен удовлетворять всем правилам на предыдущем слайде

  • Required init.

Класс может пометить один или более  init методов как required
Любой subclass должен реализовать эти упомянутые методы ( даже если они были унаследованы по приведенным выше правилам)


Например, ваш superclass имеет три назначенных (designated) инициализатора с различными аргументами. Если вы переопределите (override) все, то тогда вы наследуете все convenience inits вашего superclass.
Также если вы не реализуете ничего, вы наследуете все назначенные (designated) инициализаторы и все convenience inits вашего superclass в этом случае.
Это правила наследования. Людей это ставит в тупик : » Я реализовал один инициализатор, а почему другие не работают?»
Как только вы реализовали один назначенный (designated) инициализатор, вы не наследуете остальные. Либо все, либо ничего, если дело касается назначенных инициализаторов (designateds).
В отношении convenience inits тоже либо все, либо ничего.
Потому что вы должны либо реализовать все, либо не реализовать ничего, чтобы получить в наследство convenience inits.
Любой init, унаследованный по этим правилам должен принимать в внимание правила на предыдущем слайде.
Возможно иметь то, что называется required init. Если вы поставили ключевое слово required перед  init, то это означает, что subclasses должны реализовать этот  init. 
Вы можете наследовать required init по правилам, указанным выше.
Если вы не реализовали ни одного назначенного инициализатор, то вы наследуете все, в том числе и  required init. Это нормально. Но если вы реализовали какой-то из назначенных инициализаторов, то вы должны выполнить и required inits.

Наконец «ошибающиеся» инициализаторы (failable initializers).
Screen Shot 2015-02-15 at 8.24.03 PM


Инициализация

  • «Ошибающиеся» init.

Если  init декларируется с вопросительным ? знаком (или с восклицательным ! знаком) после слова init , то инициализатор возвращает Optional

[js]
init? (arg :Type1, …) {
// может вернуть nil
}
[/js]

Они очень редко используются.
Замечание. В документации они не достаточно хорошо представлены.
Но вам необходимо сказать об этом, потому что компилятор будет предупреждать вас о типе возвращаемого значения.

[js]
let image = UIImage(named: "foo") //image — это Optional UIImage (UIImage?)
[/js]

Обычно мы будем использовать if — let для этих случаев

[js]
if let image = UIImage(named: "foo"){
//image создано успешно
} else {
// не смогли создать image
[/js]


Некоторым инициализаторам позволено «ошибаться» и возвращать nil.
Вы помечаете такие инициализаторы знаком ? вопроса.
Они очень редко встречаются и вы не часто их увидите, да и документация недостаточно их представляет.
Нужно знать, что он возвращает Optional значение и обращаться с ним соответствующим образом.
Например, на слайде метод UIImage( named:) возвращает изображение (image) по имени. Конечно, может не оказаться изображения с таким именем, и тогда возвращается nil. Следовательно, image имеет Optional тип UIImage? Таким образом инициализатор UIImage( name:) является «ошибающимся» инициализатором (failable initializer). В таких случаях мы используем  if let конструкцию.

ВОПРОС: Является ли это заменой try — catch конструкции?
ОТВЕТ: Мы не собираемся говорить о try — catch на этом курсе. В действительности в Swift мы даже не используем  try — catch конструкцию. В этом нет необходимости. Конструкция try — catch, если кто не знает, «ловит» исключительные ситуации и может управлять ими. Это программная методология не используется в   iOS. В Objective C, может быть, используется немного, но не уверен. Но в Swift мы определенно не собираемся ее использовать.

Поговорим о создании объектов.

Screen Shot 2015-02-15 at 9.53.59 PM


Инициализация

  • Создание объектов

Обычно вы создаете объект путем вызова его инициализатора через имя типа…

[js]
let x = CalculatorBrain()
let y = ComplicatedObject (arg1: 42, arg2:"hello", …)
let z = [String] ()
[/js]

Но иногда вы создаете объекты путем вызова методов типа в классах…

[js]
let button = UIButton.buttonWithType(UIButtonType.System)
[/js]

Иногда другие объекты создают объекты вместо нас

[js]
let commaSeparatedArrayElements:String = ",".join(myArray)
[/js]


Вы знаете как создавать объекты с помощью типа: пишите тип, затем круглые скобки и в них параметры для инициализатора. Но вы не всегда можете создать объект таким образом.  Иногда вы просите метод класса или метод типа сделать это. На слайдах вы видите один из примеров UIButton.buttonWithType (…). Мы посылаем сообщение типу UIButton с просьбой создать кнопку. Точно также мы посылали сообщение типу  UIImage с просьбой создать изображение по имени на предыдущем слайде.  Иногда вы будете создавать объекты таким образом, но очень редко. Реально Apple двигается в сторону ухода от этого привычного ранее способа создания объектов в пользу инициализаторов при создании объектов.

Иногда другие объекты будут создавать объекты для вас. Как, например, этот замечательный метод  join для String. Он берет массив строк и разделяет их какой-то строкой, которой посылаете сообщение: объединить элементы массива. В нашем случае мы получим элементы массива, разделенные запятой.

Теперь поговорим об AnyObject, который нам встречался раньше.

Screen Shot 2015-02-15 at 10.34.39 PM


AnyObject

  • Специальный «Type» (в действительности это Protocol)

Используется в первую очередь для совместимости с существующими и основанными на  Objective-C APIs

  • Где вы его увидите?

В качестве свойств ( как одиночных, так и массивов чего-то) , например…

[js]
var distinationViewController: AnyObject
var toolBarItems: [AnyObject]
[/js]

… как аргументы функций…

[js]
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject)
func addConstraints (constraints:[AnyObject])
func appendDigit (sender: AnyObject)
[/js]

… или даже как возвращаемый тип в функциях…

[js]
class func buttonWithType (buttonType: UIButtonType) -> AnyObject
[/js]


Вы можете думать об AnyObject как о типе, таком же, как и Double или Array или что-то еще. AnyObject используется в первую очередь для совместимости с существующими и основанными на  Objective-C APIs. Вы не будете часто использовать AnyObject для построения собственной структуры данных, хотя можете это делать, но я думаю это будет «анти-Swift». Swift — строго типизированный язык с «выводом типов» и все такое. Поэтому вы можете использовать  AnyObject, но я буду говорить о нем в контексте совместимости.

Что такое AnyObject? AnyObject означает указатель на объект, то есть на экземпляр класса, но вы не знаете какого класса. Это указатель на объект неизвестного класса.

Это звучит странно, но мы  рассмотрим как его использовать. Где его можно видеть в iOS? Иногда вы увидите свойства (properties), которые являются AnyObject, например свойство storyboardSegues, о котром мы будем говорить на следующей неделе, называемое destinationViewController, которое вместо того, чтобы иметь свойство UIViewController , имеет тип AnyObject. В действительности,  по историческим причинам. Тоже самое с UIViewController, свойством которого является  toolbBarItems и оно возвращает массив  [AnyObject].
В основном, эти свойства возвращают вещи, которые вы не знаете, что они собой представляют.
Вы также увидите  AnyObject как аргументы функций, как например, в методе  класса  UIViewController prepareForSegue. Этот метод берет нормальный аргумент, segue, но также и sender, который может быть любым аргументом.
Но в этом случае это действительно имеет значение, потому что этот метод вызывает segue, находящийся в пользовательском интерфейсе, и sender действительно может быть  AnyObject.
Другой пример: метод  addConstraints класса UIViewController. Этот метод добавляет правила (ограничения) для Autolayout ( авторазметки) к вашему Controller. И это будем массив ограничений.  Я не буду учить вас этой технике создания ограничений   Autolayout  программным путем. Но, опять, по историческим причинам это массив [AnyObject].
И даже знакомый нам метод appendDigit (sender: AnyObject) . Помните? Мы с вами делали CTRL-перетягивание от кнопки в код? Я попросил вас сменить AnyObject на UIButton? Если бы вы не сменили, то получили бы  sender: AnyObject.
Также AnyObject может быть возвращаемым значением в функциях. Например, функция buttonWithType, о которой я говорил пару слайдов назад, действительно возвращает AnyObject.
Вы можете подумать :»Что за ерунда? Что это вы раздаете указатели на то, что и сами не знаете?»
Мы поговорим немного почему мы это делаем, но прежде давайте поговорим о том, как можно использовать  AnyObject. Потому что  AnyObject — указатель на неизвестный объект и я реально не могу послать ему никакого сообщения, потому что я не знаю, кто он такой. Для того, чтобы мы могли его использовать, мы должны преобразовать его к типу, который мы действительно знаем. 

Screen Shot 2015-02-16 at 8.23.00 AM


AnyObject

  • Как мы используем AnyObject?

Обычно мы не можем использовать его напрямую
Вместо этого мы преобразуем его в другой, известный тип

  • Как мы его преобразуем?

Нам нужно создать новую переменную известного типа ( не AnyObject)
Затем мы присваиваем этой новой переменной то, что имеет тип AnyObject
Конечно, если новая переменная будет совместимого типа
Если мы попытаемся заставить  AnyObject быть чем-то несовместимым — аварийное завершение приложения (crash)!
Но есть несколько способов проверить совместимость (либо перед «принуждением», либо во время «принуждения» быть чем-то)


Единственный способ использования — это инвертировать его в класс, который мы знаем. Допустим, мы получили некоторый AnyObject, который был аргументом функции или локальной переменной или еще чем-то и хотим его преобразовать. Создаем новую переменную, имеющую тип, в который мы хотим преобразовать AnyObject, и присваиваем ей тот AnyObject, который мы хотим преобразовать. Конечно, мы должны преобразовывать его в совместимый тип. В реальности AnyObject является одним из этих совместимых типов. Если мы получаем UIButton из метода buttonsWithType,  то метод возвращает нам AnyObject, и мы сможем преобразовать AnyObject в  UIBuutton, потому что она и является UIButton.

Но мы не можем преобразовать кнопку в  UILabel, потому что это не UILabel, и вы действительно получите аварийное завершение приложения. Как нам сделать это преобразование безопасным образом, чтобы не рушить наше приложение каждый раз?
Есть два способа и мы называем это «кастингом» (casting).

Screen Shot 2015-02-16 at 9.00.02 AM


AnyObject

  • «Кастинг» ( casting)  AnyObject

Мы «принуждаем» AnyObject  быть чем-то еще с помощью его «кастинга» (casting), используя ключевое слово as
Используем в качестве примера переменную  var destinationViewController :AnyObject

[js]
let calcVC = destinationViewController as CalculatorViewController
[/js]

… это закончится аварийно, если calcVC не является CalculatorViewController или его subclass

Для защиты от аварийного завершения используем if let  c as?

[js]
if let calcVC = destinationViewController as? CalculatorViewController {…}
[/js]

as? возвращает Optional (calcVC = nil если  calcVC не является CalculatorViewController)
Или будем проверять с помощью ключевого слова is перед тем, как применить as?

[js]
if destinationViewController is CalculatorViewController {…}
[/js]


Вы знаете, что означает «кастинг» типа, мы по существу делаем «кастинг» одного объекта во что-то другое. И мы можем сделать «принудительный» кастинг с помощью ключевого слова as. Swift, как обычно, очень краток.
Дальше я буду использовать destinationViewController как пример. Я получаю destinationViewController как  AnyObject. Я знаю, что это  ViewController, но я думаю, что destinationViewController должен быть калькулятором  CalculatorViewController.
Я хочу разговаривать с destinationViewController, который AnyObject, но я знаю, что в моем случае это калькулятор CalculatorViewController. Но если это не так, то моя программа закончиться аварийно. Но из контекста моей задачи я знаю, что это CalculatorViewController. Поэтому я могу написать 

[js]
let calcVC = destinationViewController as CalculatorViewController
[/js]

Теперь моя новая константа, calcVC, имеет тип не AnyObject, ее тип CalculatorViewController. Теперь я «схватил» AnyObject таким образом, что могу посылать моему новому calcVC сообщения CalculatorViewController .

Вы можете защититься от аварийного завершения, если вы не совсем уверены, является ли destinationViewController нашим калькулятором  CalculatorViewController, но вы надеетесь, что все-таки это так. Вы можете использовать вопросительный ? знак после asas?.  В этом случае вернется Optional версия того, во что вы хотите превратить свой AnyObject. То есть as? действует точно также как as, но это Optional  версия, так что мы можем получить nil, если этот  AnyObjectdestinationViewController, не может быть преобразован в калькулятор CalculatorViewController, потому что он им не является, это ViewController другого типа или что-то еще. Я могу написать код и уверенно получить внутри переменную calcVC как калькулятор CalculatorViewController.

[js]
if let calcVC = destinationViewController as? CalculatorViewController {…}
[/js]

Итак, к нас есть as и  as?. Но мы можем заранее проверить, является ли наш  AnyObjectdestinationViewController, нашим калькулятором  CalculatorViewController.  Для этого мы должны использовать ключевое слово is.

[js]
if destinationViewController is CalculatorViewController {…}
[/js]

Обычно мы используем as? и для проверки, и для преобразования за один раз.
Итак, это о том, как мы используем AnyObject — мы выполняем «кастинг» его во что-то еще с помощью  as?.
ВОПРОС: Есть ли способ как-то спросить объект, какого он типа и вернуть этот тип?
ОТВЕТ: Для целей, о которых вы говорите — нет. Вы не должны это делать. Вы, определенно, можете спросить :» А являешься ли ты какой-то определенной вещью?», и вы не должны спрашивать :»Кто ты такой?» Мы не используем эту парадигму.

Давайте поговорим о массиве [AnyObject], потому что иногда нам возвращают массив  [AnyObject]. Как например, constraints: [AnyObject], ограничения ( правила) для Autolayout, о которых я говорил. Вы можете здесь делать некоторые интересные вещи.

Screen Shot 2015-02-16 at 10.54.11 AM


AnyObject

  • «Кастинг» ( casting) Arrays  [AnyObject]

Если вы имеете дело с [AnyObject], вы можете делать «кастинга» (casting) элементов или всего массива целиком…
Используем в качестве примера var toolbвaritems : [AnyObject] …

[js]
for item in toolbarItems { // тип item — AnyObject
if let toolbarItem = item as? UIBarButtonItem {
// делайте что-то с toolbarItem,тип которой UIBarButtonItem
}
}
[/js]

…   или …

[js]
for toolbarItem in toolbarItems as [UIBarButtonItem]{ //иначе crash!
// делайте что-то с toolbarItem,тип которой UIBarButtonItem
}
}
// не можете использовать as?, так как это может быть
"for toolbarItem in nil", что лишено смысла
[/js]


Используем в качестве примера var toolbвaritems : [AnyObject] … Это свойство UIViewController и оно возвращает массив, содержащий UIBarButtonitems. Но по историческим причинам он возвращает [AnyObject]. Я начинаю перечислять элементы этого массива и item будет иметь тип AnyObject, но внутри я могу использовать «кастинг» для элемента массива и уже потом с этим работать.

[js]
for item in toolbarItems { // тип item — AnyObject
if let toolbarItem = item as? UIBarButtonItem {
// делайте что-то с toolbarItem,тип которой UIBarButtonItem
}
}
[/js]

Вначале я перечисляю этот массив [AnyObject], а затем внутри использую as?.

Но я могу это сделать другим способом и использовать «кастинг» всего массива целиком

[js]
for toolbarItem in toolbarItems as [UIBarButtonItem]{ //иначе crash!
// делайте что-то с toolbarItem,тип которой UIBarButtonItem
}
}
// не можете использовать as?, так как это может быть
"for toolbarItem in nil", что лишено смысла
[/js]

Если я знаю, что массив объектов — это реально массив [UIBarButton], то я могу применить «кастинг». У нас тот самый случай. Вы не можете здесь использовать as?  так как это может быть лишено смысла. Но вы должны быть уверены, что это массив того, что вам нужно. Теперь  toolBarItem уже имеет тип UIBarButtonItem, потому что я провел «кастинг» всего массива как [UIBarButtonItem].

Вы должны понимать разницу между этими двумя способами.
Другой пример, где появляется AnyObject.

Screen Shot 2015-02-16 at 12.12.30 PM


AnyObject

  • Другой пример…

Помните, когда мы привязывали наши Actions на нашей  storyboard ?
По умолчанию в диалоговом всплывающем окне указан AnyObject.
Мы изменили его на UIButton.
А что будет, если мы не изменим его на  UIButton?
Как нам выполнить appendDigit?

[js]
@IBAction func appendDigit (sender: AnyObject){
if let sendingButton = sender as? UIButton {
let digit = sendingButton.currentTitle!

}
}
[/js]


Вы можете спросит:»Почему вообще по умолчанию стоит AnyObject? Почему они предлагают мне AnyObject?» Потому что вы можете создавать Action от разных  UI элементов вашего пользовательского интерфейса. Этот Action может устанавливать, например, и кнопка, и слайдер. Я не могу представить, как это будет, но это возможно. Теоретически возможно. В этом случае sender действительно будет AnyObject. И в этом случае я должен писать if sender as thing1 {…} else if   sender as thing2 {…}. То есть мы должны использовать as. Это очень редкий случай, когда два различных UI элемента имеют общий Action, поэтому непонятно, почему по умолчанию Apple использует именно этот случай.

Другой пример AnyObject. Давайте вернемся к кнопке.

Screen Shot 2015-02-16 at 12.44.10 PM


AnyObject

  • Еще один пример…

Возможно создание кнопки в коде, используя  UIButton метод типа

[js]
let button:AnyObject = UIButton.buttonWithType (UIButtonType.System)
[/js]

Типом этой кнопки только по историческим причинам является AnyObject.
Для того, чтобы ее использовать, мы должны сделать «кастинг» button до UIButton.

Мы можем сделать это «на лету»

[js]
let title = (button as UIButton).currentTitle
[/js]

И опять, это завершится аварийно, если кнопка фактически не является  UIButton.


Давайте рассмотрим «кастинг» вообще, не только применительно к AnyObject.

Screen Shot 2015-02-16 at 1.05.11 PM


«Кастинг»

  • «Кастинг» не только для AnyObject

Вы можете делать «кастинг» с as ( или проверку с is)  любого указателя объекта, если это имеет смысл

Например …

[js]
let vc: UIViewController = CalculatorViewController ()
}
[/js]

Тип vc —  UIViewController ( потому что мы явно указали его)
Присваивание законно, так как  CalculatorViewController является UIViewController 
Но мы не можем написать, например, vc.enter()

[js]
if let calcVC = vc as? CalculatorViewController ()
// здесь можно писать calcVC.enter() если хотите
}
[/js]


Вы можете использовать «кастинг» двух объектов. Не для всех объектов, очевидно, что эти два объекта должны находится  в той же самой иерархии наследования. Вы можете делать «кастинг»  по нисходящей (downcast) в вашем собственном наследовании этой иерархии.

Например, у нас есть локальная переменная vc:  UIViewController, ее тип UIViewController, но я установил этот тип явно тому, кто на самом деле является калькулятором CalculatorViewController. И тем не менее, я не могу написать  vc.enter (), Метод  enter() —  это метод в нашем  CalculatorViewController. А с точки зрения  Swift vc —  UIViewController, поэтому Swift проигнорирует enter(). Но мы можем выполнить «кастинг» по нисходящей (downcast) и написать

if let calcVC = vc as a CalculatorViewController.  Вот тогда можно использовать calcVC.enter (). Вы можете выполнять «кастинг» по нисходящей в иерархии наследования.

Давайте поговорим о некоторых интересных функциях во всех классах. И начнем с массивов.

Screen Shot 2015-02-16 at 1.52.36 PMВы знаете, что массивы Array<T> выполняют операцию += , но аргументом += является другой массив. Если вы хотите добавить отдельный элемент, то вам нужно   поместить этот элемент в квадратные [] скобки. Некоторые люди спотыкаются на этом. Они добавляют просто Int , если у них массив [Int]. Это не работает. Потому что += добавляет массив целиком к другому массиву, это своего рода «конкатенация массивов».
Пара интересных свойств в Array<T> : first и last, которые возвращают соответственно первый и последний элемент массива. Интересно то, что они возвращают Optional.  Они не делают проверку индексов на выход за границы, поэтому если массив — пустой, то возвращается nil. Но вы должны знать, что  first и last возвращают Optional <T>.  Это может иногда улучшить читаемость кода.
Для последующих функций у меня будет маленький пример ввиде массива из трех элементов любого типа, потому что массив — это generic тип: Array<T>.  Мы предполагаем любой тип T.

Далее вы можете предполагать, что это маленький массив элементов типа T.
Давайте поговорим об append, который добавляет что-то в массив. Мы делали это все время в нашем  Сalculator. Есть функция insert, так что вы можете вставить элемент в середину массива. Но вам нужно указать с какого индекса.

Есть также функция splice, которая берет массив и «вплетает» его в середину нашего массива. Это похоже на  insert, но вставляется массив в массив.

Есть также функция removeAtIndex, которая, конечно, работает если массив var. Если ваш массив var, то вы можете выполнять removeAtIndex, а также removeRange. Вы можете определить диапазон (range) как 5…7 или 5.. <7 и убрать элементы массива с индексами в соответствующем диапазоне. Вы также можете использовать функцию replaceRange и заменить элементы в указанном диапазоне на  другой массив, при этом длина заменяемого диапазона и длина заменяющего диапазона могут различаться.
Есть также сортировка массива. Методы сортировки массива берут один аргумент и это функция, которая определяет, являются ли два элемента упорядоченными, то есть один впереди другого или нет. Этот метод пробегает по всему массиву и располагает элементы в определенном порядке. Этот метод должен знать, как сортировать. Если это Quick sort или еще какой-то другой метод сортировка, но так или иначе, он должен уметь сравнивать два элемента, и это обеспечивается функцией сравнения, которую мы должны задать обычно внутри замыкания. На слайде вы видите сортировку по умолчанию, которая предполагает сравнение на «меньше». Если вам нужно отсортировать массив a,  вы пишите a.sort {$0 <$1}. Функция в замыкании вернет true, если  $0 <$1 а именно это и нужно знать функции sort. Она вызывает это замыкание многократно и пробегает по всему массиву. Помимо функции sort, есть функция sorted. Функция sort работает по месту, а функция sorted создает и возвращает новый отсортированный массив, копию вашего массива, но отсортированную.

Есть еще другие методы для массивов.

Screen Shot 2015-02-16 at 3.50.30 PM


Функции

  • Больше Array <T> методов

Метод filter создает новый массив с отфильтрованными элементами, то есть из массива удалены так называемые «нежелательные» элементы
Функция, передаваемая как аргумент, возвращает false, если элемент «нежелательный»

[js]
filter (includeElement:(T) -> Bool) -> [T]
[/js]

Метод map cоздает новый массив путем трансформации каждого элемента массива во что-то другое
Элемент, в который идет трансформация, может иметь другой тип, чем элемент исходного массива

[js]
map (transform:(T) -> U) -> [U]
let stringified: [String] = [1,2,3].map {"\($0)"}
[/js]

Метод reduce  cворачивает целый массив до единственного значения

[js]
reduce (initial:U, combine: (U,T) -> U) -> U
let sum: Int = [1,2,3].reduce(0){$0 +$1}//добавляет числа в массиве
[/js]


Перед нами 3 совершенно замечательных метода.

Один — это filter. Он дает вам новый массив без «нежелательных» элементов.
«Нежелательность» задается аргументом — функцией, которая возвращает Bool, показывающий является элемент «нежелательным» или нет.
Есть другая функция — map. Она возвращает новый массив, в котором каждый элемент в вашем массиве преобразован во что-то другое. То, во что вы преобразуете элементе не обязательно должно иметь тот же тип. У меня может быть массив [Int] и я могу с помощью map перевести его в [String].
Вы видите, что Swift замечательно краткий язык: никаких for, никаких циклов.
Вы, конечно, захотите извлечь выгоду из всех этих вещей с замыканиями, синтаксисом и т.д.
Есть функция reduce,  которая превращает полный массив в единственное значение. Эта функция берет в качестве аргумента начальное значение, с которого вы хотите стартовать. Ее вторым аргументом является функцию, которая в свою очередь берет сформированной к настоящему времени значение и следующий элемент массива и возвращает их комбинацию. Вы просто комбинируете, комбинируете, комбинируете. На слайде приведен пример сложения элементов массива. Комбинация задана в виде замыкания, в котором  $1 — следующий элемент массива, а $0 — это скомбинированное к настоящему времени значение.

Теперь строки. Со строками немного сложнее.

Screen Shot 2015-02-16 at 4.42.37 PM


  String

  • String.Index

В Unicode определенный глиф может быть представлен множеством Unicode символов (например, символ с диакритическим знаком (надстрочным), который дальше для краткости, я буду называть «знак ударения»).
Примечание переводчика. Например, Ë — это символ E  + надстрочный «знак ударения». То есть ваш один глиф Ë состоит из двух Unicode символов: E и двух точек сверху.
Как результат этого, вы не можете индексировать String по Int (потому что это коллекция символов, а не глифов)
Множество «родных» Swift String функций использует String.Index для определения какой глиф вам нужен
Вы можете получить String.Indexзапрашивая у строки ее  startIndex, а затем продвигаться вперед
Вы продвигаетесь вперед с помощью функции (а не метода) advance (String.Index, Int)

[js]
var s = "hello"
let index = advance(s.startIndex,2)//index-к 3-му глифу "l"
s.splice("abc",index) // s = "heabcllo"
let startIndex = advance (s.startIndex,1)
let endIndex = advance (s.startIndex,6)
let substring = s[index..<endIndex] // substring будет "eabcl"
[/js]


String —  сложные, если приходится их индексировать. То есть, например, вычисление подстроки. Множество людей говорит:»Я не могу получить substring строки в Swift. Это так сложно.»

Но если вы один раз поймете, что строки состоят из Unicode символов (characters) и что, когда вы получаете substring (подстроку), вам нужна  в действительности не подстрока Unicode символов (characters). Вам нужна подстрока Unicode символов (characters), которая соответствует границам глифов. Глиф — это что-то, что воспринимается вами как символ. Но в Unicode один глиф может быть сделан из множества Unicode символов. Например символ «ударение» (условно это символы над гласными в Европейских языках). Например некоторые французские буквы имеют некоторые символы, расположенные сверху других букв). Они могут представляться двумя  Unicode символами. То есть два Unicode символа: сам символ и символ «ударение», которые вы воспринимаете как один глиф.

Поэтому вы не можете индексировать строку, которая представляет собой массив Unicode символов. Строка String внутренне  является коллекцией (collection)  Unicode символов. Если вы будете индексировать по Int, то вы можете попасть на середину символа с ударением. Вместо этого строки String индексируется другим типом, называемым  String.Index.

Как нам получить String.Index? Вначале мы должны получить  startIndex вашей строки. Это даст вам String.Index,  который указывает на первый символ строки. И затем вы можете продвигаться вперед на столько глифов, на сколько вам нужно с помощью advance (String.Index, Int). Это не метод String, это типа глобальной функции с именем advance.
Вы даете ей String.Index и говорите сколько раз ей нужно продвинуться вперед. И она возвращает вам новый индекс.
Изучаем пример на слайде.
String.Index — это все о функции advance. Функция advance — это ключ для понимания как работает substring с типом String.
Если вы это поняли, то есть много другого относительно String. Например, метод rangeOfString.

Screen Shot 2015-02-16 at 6.47.18 PM


String

  • String.Index

Метод  rangeOfString возвращает Optional Range <String.Index>
В качестве примера получим целую часть числа строки, представляющей Double

[js]
let num = "56.25"
if let decimalRange = num.rangeOfString("."){//тип Rang<String.Index>
let wholeNumberPart = num[num.startIndex..<decimalRange.startIndex]
}
[/js]

Мы могли бы убрать целую часть, используя метод

[js]
s.removeRange([s.startIndex..<decimalRange.startIndex])
[/js]

Существуют другие параметры у removeRange  (заданные по умолчанию), но я о них здесь рассказывать не буду.

Существует  raplaceRange (Range, String)


Помните? Мы использовали rangeOfString  в Сalculator, и я говорил вам, что он возвращает nil, если ему не удается ничего найти? Что же он возвращает, если он нашел то, что искал? Он возвращает Range <String.Index>. Но в действительности это Optional Range <String.Index>. Вот как вы должны использовать его в своем домашнем задании. Вы должны использовать его Optional природу, а пока вы используете только его  Range природу.
Например, если вы хотите получить только целую часть числа в строке, которая представляет Double. Я могу запросить найти для меня десятичную точку. И мне вернется decimalRange, если «точка» нашлась. И теперь мне надо вычленить часть строки с начала и до десятичной точки, не включая ее, то есть получить строку

[js]
let wholeNumberPart = num[num.startIndex..<decimalRange.startIndex]
[/js]

Вы можете убрать целую часть из строчного представления числа (смотри на слайде).

Вообще есть очень много методов, основанных на String.Index.

Screen Shot 2015-02-16 at 7.37.35 PMВы можете посмотреть все методы в документации. Некоторые из них взяты у
NSString, о которой я расскажу через секунду. Вы можете найти endIndex, здесь метод  join, о котором мы говорили до этого. Заметьте, что есть метод  toInt, но нет   toDouble. Почему? Потому что когда мы преобразуем в Double, мы должны определить, сколько значащих цифр мы должны оставить, сколько после десятичной точки и подобные вещи. Слишком много аргументов нужно, а когда вы преобразуете в Int, ничего не нужно. Просто toInt(). Но заметьте, что  toInt() возвращает Optional Int?, потому что вы можете послать этому методу «hello».
Есть ряд скрытых методов, о которых вы даже не знаете, и которые являются преобразованиями, использующими init.

Screen Shot 2015-02-16 at 7.56.06 PMНапример, если у меня есть Double и Float (на слайде). Я могу написать let x = Int(d) и тут же происходит усечение дробной части числа и превращение его в Int.
Я пребразовал Double. Это имеет смысл для чисел типа Int или Float. Вы увидите
CGFloat на следующей неделе. Это другой тип числа с плавающей точкой. Вы создаете новый объект с помощью инициализатора. Итак, это числа.

Вы будете удивлены. Если вы внимательно читали ваше домашнее задание или
reading assignment, то вы видели это. Дело в том, что вы можете делать такие преобразования с массивами и строками, используя их инициализаторы. Вы можете преобразовывать строки в массив символов

[js]
let a = Array("abc") // получим ["a","b","c"]
let s = String(["a","b","c"]) // получим "abc"
[/js]

В случае массива вернется массив символов в строке, Unicode символов. И наоборот, если у вас есть массив Unicode символов, вы посылаете его String  инициализатору, и он вернет вам  скомпонованную строку.
Это своего рода «скрытые» возможности.
Вы не можете преобразовать таким образом 52.5 в строку String (52.5), но для этого существует механизм  «\(52.5)». Это для Floats в  Strings.

Но мы можем это проделать с Int, например, String (52), потому что String имеет  метод toInt, и знает как это делать.

Теперь assertions (утверждения).

Screen Shot 2015-02-16 at 8.47.38 PM


Assertions (утверждения)

  • Помощь в отладке

Намеренно аварийно завершает ваше приложение, если условия не выполняются ( и дает сообщение)

[js]
assert(() -> Bool, "message")
[/js]

Первый аргумент — функция в виде «autoclosure«, так что фигурные скобки {} не нужны.
Например,

[js]
assert(validation() != nil, "функция validation возвращает nil")
[/js]

Программа закончится аварийно,если validation вернет nil (потому что мы утверждаем, что она не должна возвращать nil)
Часть validation() != nil может быть любым кодом
Когда создается release (для AppStore или еще куда-нибудь) assertions полностью игнорируются.


Для отладки это прекрасная вещь. Многие языки имеют assert и сейчас Swift тоже имеет этот инструмент.

Есть функции, которые пока не задокументированы, поэтому этот слайд будет вашим единственным документом.
Это глобальные функции, а не методы. Они берут аргументы, которые мне тяжело вам сейчас объяснить. Но в основном они берут массивы и строки, словари, но и другие вещи тоже.

Screen Shot 2015-02-16 at 9.17.16 PMВы можете посмотреть этот список интересных функций и комбинировать их. В конце приведены две строчки, где показана комбинация. В последней строке я передаю массив символов, применяю функцию reverse и возвращаю строку с помощью инициализатора, как мы недавно говорили.

На следующей лекции совместимость с Objective-C. Очень важная тема.

Лекция 4 CS193P Winter 2015 — Больше Swift и Foundation Frameworks. (часть 2. AnyObject, as?, is, String.Index): 4 комментария

  1. 1
    2
    3
    if let calcVC = vc as? CalculatorViewController ()
    // здесь можно писать vc.enter() если хотите
    }

    -нужно исправить на «здесь можно писать calcVC.enter() если хотите»

Обсуждение закрыто.