Лекция 4 CS193P Winter 2015 — Больше Swift и Foundation Frameworks. (часть 1)

Title iOS 8

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

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

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

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

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

Вы можете читать PDF-файл конспекта и использовать его offline

Лекция 4 CS193P Winter 2015 iOS 8.pdf

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

Добро пожаловать на лекцию 4 CS193p, Stanford Winter of 2015.

До сих пор я показывал демонстрационные примеры, но сегодня будут только слайды. Я должен многое рассказать, если не все, что вам предлагается в Заданиях для чтения ( Reading Assignments), но я должен высветить очень важные темы, а кроме того, показать некоторый материал, который не представлен в Заданиях для чтения ( Reading Assignments) и который вы должны знать. Это вовсе не замещает чтения, но позволит сфокусироваться на ключевых темах.

Screen Shot 2015-02-10 at 6.28.04 PM

Я хочу начать с Optionals  и enums, тем более теперь вы уже знаете, что такое перечисления  enum. Что собой представляют Optional ?

Screen Shot 2015-02-10 at 6.33.15 PM

Optional — это очень простое перечисление enumgeneric типа как Array <T>. У вас есть массив, и мы определяем тип его элементов — <T>. Тоже самое и Optional —  он generic. Вы определяете тип  <T>, который является типом ассоциированного значения, ну, вы знаете, что у enum  есть ассоциированные значения. Это просто тип значения, которое принимает Optional , если оно не nil. На слайде я привел, как будут выглядеть известные Optional типы, если использовать enum нотацию для получения Optional.

На самом деле Optional очень прост. Знак ? вопроса и знак ! восклицательный делают его реально сложным, но в действительности это просто «синтаксический сахар» для Optional.

Развертывание Optional реально выглядит просто как оператор switch, который в случае Some возвращает саму величину, а в случае None — возникает исключительная ситуация. Вот почему происходит аварийное завершение программы (crash), когда мы разворачиваем что-то, что равно nil.

Теперь очень быстро рассмотрим Array. Синтаксис с треугольными  скобками означает абсолютно тоже самое, что и с квадратными скобками.

Screen Shot 2015-02-10 at 9.03.25 PM

[js]
let animal = ["Giraffe", "Cow", "Doggie", "Bird"]
[/js]

Вы не можете модифицировать или что-то добавить в Array animal, так как он определен как let, следовательно он — постоянный массив. Если вы пишите  let … = [ …] , а не var … = […], то это означает, что массив неизменяемый (immutable), и вы не можете ни добавлять, ни вставлять, ни еще что-то делать, что вы можете делать с изменяемым ( mutable) массивом.

И, конечно, если вы пытаетесь вынуть из массива, указывая в квадратных скобках индекс, который находится за пределами массива — у этого массива только четыре животных, вы пытаетесь получить пятое, — то произойдет аварийное завершение программы. Это присутствует во всех языках.

Как перечислить элементы массива?

Если вы хотите пройти через каждый элемент и взглянуть на него, то нужно использовать

[js]
for animal in animals {
. . . . . .
}
[/js]

Внутри вы можете использовать текущее значение animal. Я поместил сюда это в основном для сравнения со следующим слайдом, на котором представлены словари Dictionary.

Screen Shot 2015-02-12 at 8.07.06 PM

И опять, также как и с массивами Arrayесть два альтернативных способа:

  • c квадратными скобками и
  • с ключевым словом Dictionary и треугольными скобками.

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

[js]
let ranking = pac10teamRankings ["Ohio Atate"]
[/js]

ranking будет равен nil, так как в словаре pac10teamRanking  нет команды «Ohio State». Поэтому если вы что-то ищите в Dictionary, всегда возвращается Optional.
Для перечисления элементов Dictionary используется кортеж  (tuple), состоящий из ключа и значения

[js]
for (key, value) in pac10teamRankings {
println ("\(key) = \(value)")
}
[/js]

Это очень легкий путь для перечисления элементов Dictionary.

Я собираюсь учить вас новым структурам, которых вы еще не видели. Это Range.

Screen Shot 2015-02-12 at 9.34.58 PM


Range

Range в Swift — это просто две конечные точки «разумного» типа (сейчас не собираюсь объяснять)
Range — generic (обобщенный), например Range <T>
Это своего рода псевдо-представление Range :

[js]
struct Range <T> {
var startIndex: T
var endIndex:T
}
[/js]

Диапазон массива Array должен быть Range <Int> (так как  массив индексируется по Int)
Предупреждение. Поддиапазон String не является Range <Int> (это  Range <String.Index> но … поговорим позже)
Есть специальный синтаксис для определения Range:
либо (включают оба значения), либо ..< (c открытой границей — включается только левая граница, правая не включается )

[js]
let array = ["a", "b","c","d"]
let subArray1 = array [2…3] // subArray1 будет ["c","d"]
let subArray2 = array [2..<3] // subArray2 будет ["c"]
for i in 27…104 {} // Range — перечисляемый, как и Array, String,
Dictionary
[/js]


Я привел псевдокод, чтобы показать, что собой представляет Range, это просто описание того, что есть начальная и конечная точка диапазона Range. Вы можете получить startIndex  и endIndex и установить эти значения.

Также, как и с  Array и Dictionary мы в 99 % случаев не будем использовать структуру  Range<Int>, а будем использовать специальный синтаксис, и для Range это . . . или . . <. Но вы можете использовать и синтаксис Range<T>. Это абсолютно эквивалентные вещи. Вы можете передавать их как аргументы в любом виде. 

Но другая вещь состоит том, что вы должны знать диапазон чего вы хотите создать. Например, создать диапазон целых чисел Range<Int>.  Но вы не можете делать тоже самое со строками : вместо Int вам нужно иметь String.Index. И такой диапазон немного сложнее создать, так как String.Index не может быть создана из числовых литералов. Вы должны создавать его программным образом, и мы поговорим об этом где-то через 10 слайдов. 

Вы можете перечислять Range, используя конструкцию  for in как мы делали это в Array и Dictionary.

Есть другие классы помимо Array, Dictionary и Range. Например, NSObject.
Screen Shot 2015-02-13 at 4.04.48 PM


Другие классы

  • NSObject
    Основной класс для всех Objective-C классов.
    Некоторые продвинутые концепции потребуются вам для создания subclass от NSObject
  • NSNumber
    Generic класс для чисел

    [js]
    let n = NSNumber (35.5)
    let intversion = n.IntValue // также doubleValue, boolValue и т.д.
    [/js]

  • NSDate
    Используется для нахождения даты и времени для текущего момента времени или момента будущего или прошлого
    Следует также посмотреть классы NSCalendar, NSDateFormatter, NSDateComponents
    Если вы показываете дату на вашем UI, то нужно учитывать последствия локализации, нужно их проверить !
  •   NSData
    «Сумка» с битами. Используется для хранения / восстановления / передачи «сырых» данных в iOS SDK

Нет ничего вредного в том, чтобы сделать ваш Swift класс подклассом NSObject, так что мы могли бы сделать СalculatorBrain подклассом (subclass) NSObject. И в будущем, как только мы изучим некоторые более сложные концепции iOS, было бы неплохо это сделать в нашим CalculatorBrain. Раньше  в Objective-C все классы наследовали от  NSObject, такие как UIViewController и все другие наследовали в конце концов от NSObject. Но теперь, в Swift NSObject не имеет больше такого значения.
Давайте кратко рассмотрим NSNumber, которое вы уже видели «в деле». Это
generic класс, поддерживающий числа, и есть тонны методов работы с этим классом, которые вы должны смотреть в документации. Например, такие методы как  doubleValue, intValue, которые дают мне значения чего-то как Int, как Double  и т.д. В  Swift в действительности мы не будем использовать их очень часто, из-за того, что в Swift все очень строго типизировано, и мы уже используем типы Int и Double.  Но в Objective-C APIs нет NSNumbers  и вы должны знать об этом классе. Немного позже я собираюсь рассказать немного о том факте, что все типы Swift «магически» связаны мостиком (bridged) с  NSNumber, все эти Ints и Doubles. Вообще вы не должны беспокоиться о  NSNumber до тех пор, пока не используете NSNumberFormatter, которую мы применили в нашей домашней работе для возвращения чего-то как NSNumber.

ВОПРОС:  Могу ли я создать в  Swift массив со случайными объектами?
ОТВЕТ: Мы об этом поговорим позже.

Итак, другой класс NSDate, делает ровно то, как звучит его имя: сохраняет даты и время тоже, получает текущие дату и время. У него очень много методов. Но есть еще больше методов в других имеющих отношение к этому классу классах: NSCalendarNSDateFormatterNSDateComponents. Если вы размещаете дату на экране, как на части вашего приложения, вам просто необходимо изучить все эти классы  и прочитать всю документацию о датах. Потому что даты представляются на экране очень по-разному в различных частях мира. Различные календари, различные языки, различный порядок «месяц» «день» «год» . Вывод даты на экран очень хитроумный, если вы хотите построить приложение, которое будете распространять по миру. Поэтому вы должны хорошо понимать NSDate и ее друзей.

Супер простой класс —  NSData, который представляет собой гигантскую «сумку» битов или маленькую «сумку» битов. Это — не типизированные данные, и вы можете думать об этом как об указателе на «память», в которой вы не знаете что находится.
Именно так iOS передает повсюду не типизированные «сырые» данные, и когда вы начнете использовать этот класс, вы поймете что к чему. В этом курсе мы не собираемся использовать NSData слишком много, потому что мы не будем иметь дело с  «сырыми» данными.

Далее я немного поговорю о структурах данных в Swift в целом. И тут у нас три фундаментальных строительных блока, из которых будут строиться ваши структуры данных. Это  class, struct и enum.

Screen Shot 2015-02-13 at 6.44.08 PM


Структуры данных в Swift

  • Классы, структуры и перечисления
    Существуют 3 фундаментальных строительных блока для структур данных в Swift
  •  Похожесть
    Синтасис декларирования …

[js]
class CalculatorBrain {

}
struct Vertex {

}
enum Op{

}
[/js]


Единственная разница при декларировании заключается в ключевом слове classstruct или  enum, а в остальном абсолютно одинаково.

Screen Shot 2015-02-13 at 6.54.41 PM

Они также очень похожи в том, что могут иметь свойства и функции. Все три, но enum не могут запоминать какие-то значения, вы можете запоминать значения в enum только ввиде ассоциированной информации. Но enum может иметь вычисляемые свойства (computed properties).
Также все три могут иметь функции. Это очень важно понимать, что все они очень очень похожи.

Они также похожи, по крайней мере strucs и  classes похожи в том, что они могут иметь инициализаторы.

Screen Shot 2015-02-13 at 8.03.04 PM

Мы будем говорить об инициализаторах детально через несколько слайдов.

Но есть пара вещей, в которых они очень различаются.

Screen Shot 2015-02-13 at 8.13.38 PMТолько классы имеют наследование ( inheritance). И это основное отличие того, чем класс собственно является — наследование. Затем интроспекция и кастинг, о которых я поговорю немного позже, которыми обладают только классы. Ничего этого нельзя делать со структурами.

Но наиболее важное отличие это то, что struct и enum  передаются «по значению» (value type), а class «по ссылке» (reference type). Мы это видели в демонстрационном примере.
Structs и enums передаются в Swift и запоминаются путем копирования. А classes являются типами данных, передаваемыми «по ссылке», мы передаем указатель на них, указатель туда, где они «живут» в «куче».  Помните, что «куча» управляется системой, и «память» для классов выделяется и освобождается ей автоматически.
Мы создаем классы когда хотим и, когда никто их не использует, память «вычищается» системой и никакого «сбора мусора» (garbage collection). Идет автоматический подсчет ссылок (automatic reference counting).

У меня есть слайд по сопоставлению  передачи «по значению» (value type) и передачи «по ссылке» (reference type). Иногда люди путаются при написании кода, и они не понимают, что происходит.

Screen Shot 2015-02-13 at 8.50.06 PM


«По значению» (value type) и  «По ссылке» (reference type).

  • Value (struct и  enum)

Копируются, когда передаются как аргументы в функцию
Копируются, когда присваиваются другим переменным
Неизменяемые (Immutable), если присваиваются переменной с let
Помните, что параметры функции являются по умолчанию константами
Вы можете поместить ключевое слово var впереди параметра, и он станет изменяемым (mutable), но все равно останется копией
Вы должны пометить любую функцию func, которая изменяет struct / enum, ключевым словом mutating

  • Reference (class)

Запоминаются в «куче» и ведется автоматический подсчет ссылок
Если у вас неизменяемый (Immutable) экземпляр класса, когда используется ключевое слово let, то вы все равно можете менять его свойства и вызывать меняющие его методы
При передаче аргументов копия не делается (передается только указатель на тот же самый экземпляр)

  • Что выбрать для использования?

Обычно вы будете выбирать class, а не struct. struct стараются больше использовать для фундаментальных типов.
Использование enum —  по ситуации (в любом случае, когда у вас есть тип данных с дискретными значениями).


В случае с Value Type (enum и struct)  если вы пишите

[js]
let y =  х
[/js]

делается копия, сейчас это может быть сделано lazy «за сценой», но в любом случае вы имеете дело с копией. И если вы хотите, чтобы она была изменяемой (mutable), вы должны копировать ее в var вместо let, вот тогда вы можете ее менять. Но меняете вы свою собственную копию, а не оригинал, который не изменяется.
Если вы работаете с Value Type (enum и struct)  и добавили функцию, которая модифицирует enum или struct , вам нужно пометить ее специальным ключевым словом mutating. Это для того, чтобы  Swift знал, что кто-поможет менять  enum или struct. Но это можно делать только, если ваша переменная var, а не  let.

В случае с Reference Type (class)  ссылка на экземпляр класса запоминается в «куче». Если у вас есть константный указатель на экземпляр класса

[js]
let y =  х
[/js]

вы все равно можете посылать y сообщения , меняющие его, потому что он в «куче». Итак, у вас есть  указатель на y, и вы передаете его в функцию, вы передаете указатель на y,  и если функция модифицирует этот указатель, то модифицируется сам экземпляр класса, который был передан при вызове.

Какую же структуру выбрать? В 90% случаев вы будете выбирать class. Swift — язык объектно-ориентированного программирования. Мы хотим иметь возможность наследовать ( inheritance), переопределять (overriding) методы и свойства и делать подобные вещи. Struct в большей степени применяется для основных фундаментальных типов, таких как  Double, Int, Array, Dictionaries, String. Это действительно фундаментальные типы, и когда мы перейдем к графикам, вы увидите точки (points), размеры (sizes), прямоугольники (rectangles) — все они
structs. Большая часть того, что мы будем создавать, — это classes. И так как система вместо нас управляет автоматически распределением памяти под classes, то не так-то сложно использовать classes.

Поговорим о методах.

Screen Shot 2015-02-14 at 10.21.27 AM


 Методы

  • Вы можете переопределять (override) методы / свойства в вашем superclass 

Поставьте перед func или var ключевое слово override
Метод можно пометить как final, что не позволит subclasses переопределять (override) его
Классы также можно пометить как final, это означает, что вы не можете создавать subclasses

  • Как типы (types), так и экземпляры типа (instances)  могут иметь методы / свойства

Для примера рассмотрим struct Double ( да, Double — это структура)

[js]
var d: Double  = …
if d.isSignMinus {
d = Double.abs (d)
}
[/js]

isSignMinus — это свойство экземпляра класса  Double, instance property . Вы посылаете его вполне определенной Double.
Double.abs — метод типа (type method) Double.Вы посылаете его самому типу Double, а не вполне определенной Double.
Вы декларируете метод или свойство типа (type ) c помощью префикса static для структур и class для классов.

[js]
static func abs(d: Double) -> Double
[/js]


Некоторые методы посылаются экземплярам (instances), вполне определенным экземплярам каких-то вещей, а некоторые методы — типам (types). Две различных категории методов, но правила для них одинаковые.
В классах вместо слова static мы употребляем слово class,  потому что они называются методами класса.
Я не хочу вас запутывать, но когда вы начнете смотреть методы подобные abs, которые посылаются типу Double,  то это абсолютно нормально.

Когда вам нужно использовать методы типа а когда методы экземпляра этого типа? Если вы делаете какую-то работу типа утилиты, которая не относится к какому-то конкретному Double, то вы используете методы типа. Иногда вам захочется создавать объекты определенного свойства и управлять ими. Возможно, разделяемый объект (shared object), который существует в целом приложении в единственном экземпляре, и вы запрашиваете его разделяемый экземпляр (shared instance). Методы типа — это, обычно, либо утилиты, либо создание объектов. Вы увидите их в  iOS и будете использовать их при случае. В действительности вы могли бы использовать их вместо глобальных функций. В Swift  не может быть глобальных функций. Возможно, вы предпочтете создавать методы, то есть  ассоциированные с типом (type) функции вместо глобальных функций. Это  type функции.
Теперь давайте поговорим об именах параметров методов. Я дам вам краткий курс по именованию параметров методов. Но я надеюсь, что вы прочтете документацию.

Screen Shot 2015-02-14 at 5.38.49 PM


 Методы

  • Имена параметров

Все параметры ко всем функциям имеют внутренние internal имена и внешние external  имена 
Все internal имена являются именами локальных переменных, которые используются внутри метода
external имя — это то, что используется при  вызове метода

[js]
func foo( external internal: Int) {
let local = internal
}
func bar () {
let result = foo ( external: 123)
}
[/js]

Вы можете написать _, если хотите, чтобы при вызове никогда не использовалось external имя данного параметра

[js]
func foo(_ internal: Int) {
let local = internal
}
func bar () {
let result = foo ( 123)
}
[/js]

Подчеркивание _ ставится по умолчанию для внешнего имени первого параметра (только) в методе ( но не для init методов)

[js]
func foo(internal: Int) {
let local = internal
}
func bar () {
let result = foo ( 123)
}
[/js]

Вы можете заставить внешнее имя первого параметра быть одновременно и внутренним именем с помощью знака #

[js]
func foo(#internal: Int) {
let local = internal
}
func bar () {
let result = foo (internal: 123)
}
[/js]

Для других (не первого) параметров внутреннее имя является по умолчанию и внешним

[js]
func foo(first: Int, second: Double) {
let local = internal
}
func bar () {
let result = foo (123, second: 5.5)
}
[/js]

Внешние имена других параметров могут быть изменены

[js]
func foo(first: Int, externalSecond second: Double) {
let local = internal
}
func bar () {
let result = foo (123, externalSecond: 5.5)
}
[/js]

Или даже пропущены (хотя это было бы «анти-Swift»)

[js]
func foo(first: Int, _ second: Double) {
let local = internal
}
func bar () {
let result = foo (123,5.5)
}
[/js]


Я не рекомендую писать подчеркивание _  в параметрах. Бывают некоторые исключения, но я не рекомендую.

Давайте поговорим о свойствах (properties). Наблюдатели свойств ( property observers) очень важны, и мы их будем использовать в больших количествах.

Screen Shot 2015-02-14 at 6.34.42 PM


Свойства ( Properties)

  • Наблюдатели свойств (Property Observers)

Вы можете отслеживать изменения в любых свойствах с помощью willSet и didSet 

[js]
var someStoredProperty: Int = 42{
willSet {newValue — это новое значение}
didSet {oldValue — это старое значение}
}
override var inheritedProperty {
willSet {newValue — это новое значение}
didSet {oldValue — это старое значение}
}
[/js]


Вы можете думать о желтом на слайдах как то, на что следует обратить внимание.
Внутри фигурных скобок вместо get {} и set {} как в вычисляемых свойствах (computed property) мы пишем willSet {} и didSet {}. Внутри фигурных скобок willSet {} и didSet {} находятся куски кода, в которых мы можем использовать newValue и oldValue. Наблюдатели свойств willSet  и didSet вызываются тогда, когда кто-то устанавливает это свойство. Наблюдатель willSet вызывается непосредственно перед тем, как свойство будет установлено, и внутри наблюдателя можно использовать специальную переменную с именем newValue. Наблюдатель didSet вызывается сразу после получения свойством значения, и внутри можно использовать специальную переменную с именем oldValue.
На слайде приведены два случая использования наблюдателей.

Один для stored property в вашем классе. И всякий раз, когда кто-то устанавливает это свойство, вы хотите что-то сделать.

Другой для наследуемых (inherited) свойств. Вам не нужно делать с этими свойствами что-то напрямую, но вы захотите знать, когда их кто-то меняет. Поэтому вы можете использовать наблюдатели  willSet  и didSet для свойств, наследуемых от superclass.
Что мы можем делать в этих наблюдателях? Очень общий случай в iOS — это адаптировать пользовательский интерфейс. Допустим, у вас есть  ViewController или View, и вы изменяете какое-то свойство в них, тогда в  didSet вы будете адаптировать пользовательский интерфейс.

Screen Shot 2015-02-14 at 7.32.13 PM


Свойства ( Properties)

  • Наблюдатели свойств (Property Observers)

Вы можете отслеживать изменения в любых свойствах с помощью willSet и didSet 
Очень общий случай того, что можно делать в наблюдателях в Controller — это адаптировать пользовательский интерфейс.

  • Отложенная инициализация

Lazy свойства (свойства с отложенной инициализацией) не инициализируются до тех пор, пока кто-то их не запросит.
Вы можете разместить (allocate) объект, выполнить замыкание (closure) или вызвать метод

[js]
// если калькулятор потребляем много ресурсов
lazy var brain = CalculatorBrain ()
lazy var someProperty:Type = {
// конструируем здесь значение someProperty
return <сконструированное значение>
}()

lazy var myProperty = self.initializeMyProperty ()
[/js]

Это удовлетворяет правилу: «Все свойства должны быть инициализированы»
К сожалению, инициализированные таким образом свойства не могут быть константами (var —  можно, let — нельзя)
Это можно использовать для решения некоторых головоломок зависимой инициализации


Если вы разместите слово lazy перед декларирование свойства, то это значит, что инициализация состоится только в случае доступа непосредственно к этому свойству. Одним из способов  lazy инициализации является написание  знака «=» при декларировании, инициализация не происходит до тех пор, пока кто-то не осуществил доступ к этому свойству. Вы можете инициализировать с помощью замыкания, поставив фигурные скобки сразу после знака  «=» при декларировании. Это замыкание также не будет выполнено до тех пор, пока кто-то не запросит это свойство. И вы даже можете выполнить метод при инициализации, но только в случае, если стоит ключевое слово  lazy. Потому что вы не можете вызвать некоторые методы в вашем классе до тех пор, пока ваш класс не будет полностью инициализирован. Но после инициализации вы сможете вызвать эти методы. Понятны преимущества отложенной инициализации?
Lazy свойства удовлетворяют правилу, согласно которому все свойства должны быть инициализированы, даже если их истинная инициализация откладывается до момента доступа к ним. Вы можете  lazy инициализировать только vars, не lets.

Иногда вам приходится сражаться с вашим superclass при инициализации, и здесь помогает lazy инициализация. Замечательная вещь — отложенная инициализация свойств.
Теперь поговорим об инициализации.

Screen Shot 2015-02-14 at 8.35.23 PM


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

  • Когда нужен init метод?

init  не так уж часто встречается, потому что у свойств есть установки по умолчанию с использованием «=»
Или свойства могут быть Optional, в этом случае они стартуют по умолчанию с nil
Вы также можете инициализировать свойство путем выполнения замыкания
Или использовать lazy инициализацию
Поэтому init вам нужен только тогда, когда вы не можете установить свойство одним из этих способов

  •  Вы также получаете некоторые init методы «бесплатно»

Если все свойства в базовом классе (нет superclass) установлены по умолчанию, вы получаете init () «бесплатно»
Если у struct нет инициализаторов, она по умолчанию получает инициализатор со всеми свойствами в качестве аргументов.

[js]
struct myStruct {
var x: Int = 42
var y: String = "moltuae"

init (x:Int, y:String) // получает бесплатно
}
[/js]


В CalculatorBrain показано применение init(). Там он нам действительно был нужен.

Существует много способов избежать применения  init(). Но если он вам действительно нужен, как мы должны реализовать его?  init() — очень интересная взаимозависимая вещь и непростая. Но иногда вы получаете  init() «даром».

Если у вас базовый класс, без superclass, как CalculatorBrain, и все свойства установлены по умолчанию, то вы получаете  init() без аргументов «даром».
Если вы не реализовали в классе CalculatorBrain  init(), то  получаете  init() без аргументов «даром» и можете написать brain = CalculatorBrain ().

Есть еще один способ получения  init, если у вас struct, а не class: если у вас нет инициализаторов, вы по умолчанию получаете  инициализатор со всеми свойствами в качестве аргументов.

Что мы можем делать внутри инициализаторов?
В СalculatorBrain мы инициализировали «известные» операции.

Screen Shot 2015-02-14 at 9.33.29 PM


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

  • Что мы можем делать внутри init ?

 Мы можем установить значения любым свойствам, даже тем, у которых есть значения по умолчанию
Могут быть установлены свойства- константы ( то есть те, которые декларируется как let )
Вы можете вызвать другие init методы в вашем собственном классе, используя  self.init (<args>)
В классе вы, конечно, можете вызвать super.init (<args>)
Но существуют некоторые правила для вызова inits  из других inits в class


Мы можем устанавливать некоторым свойствам значения, мы даже можем устанавливать значения тем свойствам, которые уже установлены по умолчанию.
Если у вас есть свойство, установленное в 12, то в вашем init вы можете его переустановить во что-то другое, если передумаете.
Вы также можете установить свойства-константы. Люди не всегда понимают как это можно установить константы (let в декларировании), но их можно устанавливать внутри вашего init.

Существует множество правил для вызова других inits, особенно в классах. В struct это не так сложно, но в классах огромное множество правил и нам понадобятся два слайда, чтобы кратко «пробежаться» по ним.

Часть этих правил связана с тем, что требуется делать внутри  init? Что является обязательным?

Screen Shot 2015-02-14 at 10.42.54 PM


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

  • Что обязательно нужно делать внутри init ?

К концу init, все свойства должны иметь значения (Optional могут иметь значения nil)
У классов (class) есть два типа inits : convenience и назначенные (то есть не  convenience)
Назначенный init должен  вызывать назначенный  init своего  непосредственного superclass; и  он может вызывать только этот инициализатор
Вы должны инициализировать все свои собственные свойства перед тем, как вызвать init своего  непосредственного superclass
Вы должны вызывать init своего  superclass перед тем, как присваивать значения наследуемым (inherited) свойствам
convenience init  должен  вызывать назначенный  init своего собственного класса; и  он может вызывать только этот инициализатор
convenience init  может вызывать назначенный  init своего собственного класса не на прямую, а через другой convenience init
convenience init  должен  вызывать назначенный  init своего собственного класса перед тем, как он сможет устанавливать значения любым свойствам
Вызов других inits должен быть закончен перед тем, как вы сможете использовать свойства и исполнять методы


Вы знаете, что должны инициализировать все свойства в вашем классе. Вы не можете начать работать с классом без значений свойств. Помните, что для Optionals, nil — это значение и оно устанавливается по умолчанию.

Существует два типа инициализаторов для класса (class).  Не для структур struct, а только для classes. Один называется convenience init ( удобный инициализатор) и другие ( не convenience init), которые называются назначенными (designated) инициализаторами. По умолчанию инициализатор является  назначенным (designated) инициализатором, но есть еще и convenience инициализаторы. Мы должны сделать различие между ними, потому что у них немного различаются правила использования. Давайте поговорим об этом.

Это ПРАВИЛО НОМЕР 1 для назначенных инициализаторов. Назначенный инициализатор, тот у которого нет впереди ключевого слова convenience, должен  вызывать назначенный  init своего  непосредственного superclass. И он может вызывать только этот инициализатор. Это очень важное правило. Если у вас есть назначенный инициализатор, то есть без ключевого слова convenience, он не может вызывать другой инициализатор в вашем классе. Он должен вызвать  init своего  непосредственного superclass, который в свою очередь должен быть также назначенный инициализатором superclass, а не  convenience инициализатор в superclass. Это все есть в документации.

Это ПРАВИЛО НОМЕР 2 для назначенных инициализаторов. Вы должны инициализировать свои собственные свойства перед тем, вы вызовите инициализатор вашего  superclass. Это правило только для назначенных инициализаторов, потому что они — единственные, кто вызывает инициализатор в superclass. Вам нужно закончить со своими собственными свойствами, прежде чем вызовите  init своего superclass.

Это ПРАВИЛО НОМЕР 3 для назначенных инициализаторов. Но вы должны вызывать init своего  superclass перед тем, как присваивать значения наследуемым (inherited) свойствам из вашего superclass. Другими словами, вам нужно дать сработать инициализатору вашего  superclass, чтобы он установил все свои свойства, и только после этого вы можете устанавливать значения наследуемым свойствам.

Эти 3 правила действуют для для назначенных инициализаторов и вызова инициализаторов superclass.

Теперь рассмотрим convenience инициализаторы и для них действуют другие правила.
Это ПРАВИЛО НОМЕР 1 для  convenience инициализаторов. convenience инициализатор должен  вызывать назначенный  init своего собственного класса. И  он может вызывать только этот инициализатор, он не может вызвать  init своего  superclass. Но convenience init  может вызывать назначенный  init своего собственного класса не на прямую, а через другой convenience init, который в свою очередь вызывает назначенный инициализатор своего класса. В любом случае назначенный инициализатор своего класса должен быть вызван.

Это ПРАВИЛО НОМЕР 2 для  convenience инициализаторов. convenience инициализатор должен  вызвать назначенный  init своего собственного класса прямым или косвенным способом перед тем, как вы начнете работать с любыми значениями свойств или с методами. Другими словами вы должны сделать первоначальную инициализацию вашего объекта прежде, чем начнете «удобным» способом «поправлять» ваши свойства.

Это ПРАВИЛО  для всех инициализаторов. Вызовы всех инициализаторов должны быть завершены прежде чем вы начнете вызывать методы и использовать свойства с ключевым словом self. Вот почему мы используем lazy, если мы хотим, чтобы свойство устанавливало свои значения путем вызова метода. Потому что мы не можем сделать это не lazy способом, так как мы не можем инициализировать в любой момент времени, мы можем находиться все еще в процессе инициализации. Есть две фазы инициализации и если мы прошли первую, то можем продолжить инициализацию с использованием  self свойств и методов.

ВОПРОС: Можем ли мы установить константы путем вызова метода или что-то типа этого?

ОТВЕТ:  Нет. Вы можете установить константы с помощью «=», либо в вашем инициализаторе.

Давайте поговорим о наследовании (inheriting) инициализаторов. Это также сложная тема.

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

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

Лекция 4 CS193P Winter 2015 — Больше Swift и Foundation Frameworks. (часть 1): 2 комментария

  1. Спасибо вам огромное. Вследствие недостатка опыта не всё понятно, но зато не нужно мучаться переводить на русский самому 🙂

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