Лекция 2 CS193P Spring 2016 — Применяем MVC.

Screen Shot 2016-05-05 at 8.38.58 PM

На Лекции 2 профессор подробно объясняет использование паттерна MVC для разработки iOS приложений.

Затем следует продолжение демонстрационного приложения Калькулятор, начатого на прошлой лекции. Происходит разделение классов Модели и классов, обслуживающих View Controllers. Появляется новый класс CalculatorBrain, который является классом Модели и имеет свои особенности. На примере этого класса профессор показывает, как проектировать класс, отталкиваясь от его public API. Используются все возможные в  Swift структуры данных : класс class, перечисление enum и структура struct. Подчеркиваются основные их отличия. Очень подробно рассказывается об ассоциированных значениях enum и Optionals, которые также являются enum.

Показаны возможности замыканий (closures), использование которых вместе с именами параметров по умолчанию в виде $0, $1, $2 … сильно упрощает и делает более понятным код.

Показано взаимодействие класса ViewController, обслуживающего Controller, с классом Модели CalculatorBrain (зеленая стрелка на схемах MVC).

В конце лекции демонстрируется применение StackViews для конструирования «растягивающего» пользовательского интерфейса (UI), который будет прекрасно работать в портретном и ландшафтном режимах на любых приборах.

Код демонстрационного примера для Лекции 2 находится на Github для Xcode 7 и Swift 2.2. Для Swift 3.0  и Xcode 8 код находится на Github, а для Swift 3 .2  и Xcode 9 — также на Github.Лекция 2 и слайды на английском языке находятся на  iTunes название “2. Applying MVC.”

Русскоязычный неавторизованный конспект лекции, хронометрированный через каждые 5 минут, представлен в виде PDF-файла, который можно скачать и использовать offline

Лекция 2 CS193P Spring 2016 iOS 9 .pdf

Лекция 2 CS193P Spring 2016 — Применяем MVC.: 43 комментария

  1. Огромное СПАСИБО за ваши старания! Вы вносите не ценимый вклад в развитие ИТ.

    • Дмитрий, плохо не знать Русский язык. Не «не ценимый» (который получается, что никто не ценит), а неоценимый (который нельзя оценить)! Это очень большая разница…
      А Татьяне реально очень большое спасибо за перевод!

      • Не придирайтесь, все поняли правильно, что хотел сказать Дмитрий.

  2. Хорошо, что вы занимаетесь этим делом. Материалов по Свифту на русском очень мало, к сожалению. Но или перевод корявый, или в оригинале так объясняют, что непонятны как отдельные фразы, так и целые части.

    • Объясняет профессор великолепно, но, конечно, нужно иметь соответствующий уровень программирования — это курс не для начинающих. Так что остается два варианта: либо у вас недостаточно знаний для этого курса и нужно подучиться где-то еще, либо действительно недостаточно хорош перевод. Я могу управлять только второй частью. Поэтому, если вы согласны, я могу вам предоставить право комментировать текст перевода, и вы укажите, что вам конкретно непонятно, а я попробую по возможности улучшить перевод. Заранее вам благодарна, так как в наших общих интересах улучшить русский текст.

  3. Интересно, у меня одного дисплей калькулятора при повороте в landscape становится таким узким, что символов не разобрать? Все делал как Профессор учил, даже Код демонстрационного примера для Лекции 2 с Github скачал — всё равно дисплей сужается — как на симуляторе, так и на реальном устройстве. Обидно даже

    • Увеличил у дисплея Compression Priority — всё заработало…

      • Точно. Молодец. Именно это надо было сделать. Механизму Autolayout профессор посвятит целую лекцию № 12.

  4. Добрый день. А верно ли я понимаю:

    enum Operation {
    case Constant(Double)
    case UnaryOperation((Double) -> Double)
    case BinaryOperation
    case Equals
    }

    var operations: Dictionary = [
    «P» : Operation.Constant(M_PI),
    «E» : Operation.Constant(M_E),
    «V» : Operation.UnaryOperation(sqrt),
    «COS» : Operation.UnaryOperation(cos)
    ]

    enum Operation — это как тип у которого может быть любой generic type. Внутри enum values c пример томы (case Constant(Double))

    И когда мы уже в Dicionary присваиваем значение «P» : Operation.Constant(M_PI) происходите примерно следующее:

    case Constant(Double) равносильно объявлению var yes: Bool!

    а уже в Dictionary «P» : Operation.Constant(M_PI) грубо говоря проишодит yes = false

    верно ли моё понимание?

    • Нет, структуру данных enum вы понимаете неправильно, это никакой не generic, у которого тип «» заранее не определен.
      В Swift, как и в других языках, enum — это прежде всего перечисление, то есть дискретный набор значений: Constant, UnaryOperation, BinaryOperation и т.д.
      И уже внутри каждого конкретного значения в Swift можно определить одно или несколько ассоциированных значений, которые имеют вполне определенный тип — Double или функцию (Double) -> Double. Где здесь generic? Все типы определены.
      А вот Dictionary — действительно generic, К и V — это произвольные типы, за исключением K, у которого, как ключа, есть ограничения на тип. И в качестве типа V мы взяли наш enum Operation.

      • generic в том плане, что может быть любого типа. Странно сам Paul так говорит

        • Назовите номер Лекции и приблизительное время.

          • На 48 минуте профессор рассказывает о представлении Optional как enum в виде
            enum Optional <T> {
            case None
            case Some(T)
            }

            Это частный случай enum с именем Optional и в данном конкретном случае он действительно является generic, так как может применяться к любому типу T .
            Если мы взглянем на перечисление enum Optional, то у него всего два варианта (или два cases). У одного из этих case, а именно Some есть ассоциированное значение, которое является generic, а второй — вообще не generic, всегда одинаковый для любых типов данных T.
            Таким образом нам удается добиться «унифицированного» обозначения отсутствия установки любого типа одним и тем же значением.
            Обратное несправедливо, то есть не всякий enum обязательно generic, и вовсе необязательно, что его ассоциированное значение является generic.
            Это зависит от определения enum.
            В нашем случае

            private enum Operation{
            case Variable
            case NullaryOperation(() -> Double,String)
            case Constant(Double)
            case UnaryOperation((Double) -> Double,(String) -> String)
            case BinaryOperation((Double, Double) -> Double, (String, String) -> String)
            case Equals
            }

            Мы не определяем enum Operation как generic, у нас нет <T>.
            Следовательно и ассоциированные значения не могут быть generic, они все конкретные.
            Так что из того, что Optional<T> может быть представлена как generic enum, не следует что все enum должны быть generic.

  5. То есть в enum’e мы грубо говоря обозначаем, какого типа будет value
    В Dictionary уже присваиваем value данного типа
    А в методе perfromOperation в конструкции switch вытягиваем значение установленное в Dicitonaty сохраняем её в константу и уже присваиваем её нашему accumulator

    такой алгоритм?

  6. Спасибо за предидущие пояснения.
    А в конструкции switch в методе performOperation, когда мы объявляем константу value case .Constant(let value): accumulator = value — самое значение к примеру M_PI берётся из константы operation( if let operation = operations[symbol]) или же константа value каким от образом ссылается на Dictionary operations?

    • Отвечаю поздно, возможно, вы уже справились со своей проблемой, но вопрос слишком хорош, чтобы оставить его без внимания.
      Действительно, когда видишь код типа

      switch operation {
      case .NullaryOperation(let function, let descriptionValue):
      accumulator = function()
      descriptionAccumulator = descriptionValue
      case .Constant(let value):
      accumulator = value
      . . . . . . .

      кажется, что извлечение value из operation похоже на «магию».
      Это связано с тем, что декларирование enum немного сложнее, чем, скажем Int, из-за наличия ассоциированных значений, которые могут быть любого типа.
      То есть получается, что в нашем типе enum Operation еще присутствует куча других типов:

      private enum Operation{
      case NullaryOperation(() -> Double)
      case Constant(Double)
      case UnaryOperation((Double) -> Double)
      case BinaryOperation((Double, Double) -> Double)
      case Equals
      }

      Будем проводить сопоставление с Int.
      Для Int:

      1. декларирование — Int
      2. задание переменной определенного типа — а: Int
      3. присвоение конкретного значения — a = 43
      4. извлечение конкретного значения — x = a

      Для Operation:

      1. декларирование — определение Operation
      2. задание переменной определенного типа — operation:Operation
      3. присвоение конкретного значения — operation = Operation.Constant(M_E)
      4. извлечение конкретного значения —

        switch operation {
        case .NullaryOperation(let function, let descriptionValue):
        x = function()
        case .Constant(let value):
        x = value
        . . . . . .
        Всегда перебираем все варианты

      Так что ничего таинственного нет — что заложили, то и получили.
      В нашем случае чуть сложнее — у нас много operation в зависимости от символа операции, который является КЛЮЧОМ, а сама operation — это ЗНАЧЕНИЕ для словаря operations. Но для каждого символа, например, «x⁻¹» мы задаем операцию — константу (как мы делаем это с x=42)

      Operation.UnaryOperation({1.0/$0})

      Поэтому, отвечая на ваш вопрос, понятно, что value берется и из словаря и из определения операции-константы. То есть мы сначала извлекаем ЗНАЧЕНИЕ из словаря с помощью квадратных скобок и проверяем есть ли такое ЗНАЧЕНИЕ-операция:

      if let operation = operations[symbol]{

      а потом изучаем полученное ЗНАЧЕНИЕ-операцию, так как это перечисление, то нет другого способа извлечь ассоциированное значение, которое мы поместили при определении, как использовать оператор switch (это вам не Int, когда можно просто написать x=a):

      switch operation {
      case .NullaryOperation(let function, let descriptionValue):
      accumulator = function()
      descriptionAccumulator = descriptionValue
      case .Constant(let value):
      accumulator = value
      . . . . . . .

  7. Спасибо за ответ. Правда прочёл его уже практически разобравшись со всем, но он навеял новые вопросы ))) А именно.

    enum ListOfValues {
    case Value1(Double)
    case Value2(String)
    }

    Здесь ListOfValues это тип, а каждый case — это value с его associated value , так?
    То есть аналог:
    var first: Int?
    Где очень грубо можно произвести параллели между var и value
    Я верно всё понимаю?

    • Вопрос куда банальнее: Элемент энума — это value ? или как болле правильнее его обозначить словесно? или же просто элемент энума не имеющий аналогов?

      • enum — это перечисление, я интерпретирую это как конечное число вариантов. Варианты могут представляться как перечень целых чисел или набор строк и т.д. Когда вы присваиваете значение перечислению enum, вы прежде всего выбираете нужный вам вариант. А затем, если у этого варианта есть ассоциированное значение, то и ассоциированному значению даете нужную величину, и все.

        • То есть нет другого варианта кроме switch, чтобы извлечь associated value из элементов энума?Получается в swift enum and switch тесно связаны?

          • Необязательно.
            enum Trade {
            case Buy(stock: String, amount: Int)
            case Sell(stock: String, amount: Int)
            }
            let trade = Trade.Buy(stock: "APPL", amount: 500)
            if case let Trade.Buy(stock, amount) = trade {
            print("buy \(amount) of \(stock)")
            }

            В Swift внутренняя ассоциированная информация — это просто кортеж, так что вы можете написать так:

            let tp = (stock: "TSLA", amount: 100)
            let trade = Trade.Sell(tp)

            if case let Trade.Sell(stock, amount) = trade {
            print("buy \(amount) of \(stock)")
            }
            // Prints: "buy 100 of TSLA"

  8. Татьяна, подскажите пож., в 3 лекции часто упоминаются задания на чтение, что это за задания?

  9. Здравствуйте, подскажите пожалуйста, я делаю все по гайду, но в середине второй лекции (после создания баттона с кнопкой) столкнулся с проблемой. При нажатии на клавишу пишет:
    2016-09-16 20:24:21.203619 Calculator[3806:822679] -[Calculator.ViewController touchDigit:]: unrecognized selector sent to instance 0x100a06860
    2016-09-16 20:24:21.204110 Calculator[3806:822679] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[Calculator.ViewController touchDigit:]: unrecognized selector sent to instance 0x100a06860’
    *** First throw call stack:
    (0x1816741c0 0x1800ac55c 0x18167b278 0x181678278 0x18157259c 0x1874f19a0 0x1874f1920 0x1874dbdd0 0x1874f120c 0x1874f0d34 0x1874ebf7c 0x1874bca44 0x187ca9ea8 0x187ca3910 0x181622278 0x181621bc0 0x18161f7c0 0x18154e048 0x182fd1198 0x187527818 0x187522550 0x100041a6c 0x1805305b8)
    libc++abi.dylib: terminating with uncaught exception of type NSException
    (lldb)

    Сверял с Вашим гитом и все так же
    ЗЫ связи проверил

    • Без проекта трудно понять, но попробую угадать. Вам пишут, что компилятор не находит метода touchDigit: (с одним аргументом sender) в вашем ViewController при нажатии на эту кнопку. Вполне возможно, что вы привязывали кнопки несколько раз и, например, без аргумента (sender). Ваш метод тогда будет touchDigit (без двоеточия), а часть кнопок оставили со старым методом. Это нужно смотреть проект. Нельзя например, просто так переименовывать Action. Можете разместить ваш проект на Github?

      • Вы правы, я проверил привязку только по кнопке рядом с методом touchDigit, а по факту оказалось что по 2 привязки было у кнопки. Спасибо Вам огромное за молниеносный ответ и за гигантскую проделанную работу по переводу курса!

  10. Будьте добры, проверьте доступность пдф файла? У меня не загружает ни с каких браузеров.

    • Все загружается, но, возможно, долго, поскольку картинки на слайдах очень насыщенные. Я могу вам выслать приглашение на просмотр напрямую файла Google Doc, у вас в комментариях есть почта yandex.ru. По этой почте можно выслать приглашение?

  11. Татьяна, добрый день.
    Спасибо большое за Ваш труд.
    Все работает, но что-то не так с моими constraints, если у Вас или у других читающих будет время — взгляните, пожалуйста, на лог ошибки, которая появляется каждый раз при запуске приложения и повороте экрана — как над таким работать?

    http://pastebin.com/raw/9kebYwVJ

    • Нашел один неправильно выставленный Stack View Distribution, после того как изменил Fill Proportionally на просто Fill, проблема исчезла, но теперь при горизонтальном расположении экрана практически не виден Display, несмотря на
      Stack View with Display.top = Top Layout Guide.bottom

      (где Stack View with Display — самый большой Stack)

    • Ошибка сообщает вам, что вы установили противоречивые ограничения, которые все одновременно не могут быть удовлетворены. Вам предлагается рассмотреть их внимательно и от чего-то отказаться. Для того, чтобы сделать какое-то точное заключение о том, что неправильно, нужно ваше приложение. Сможете разместить его в Dropbox или на Github? Там можно зарегистрироваться и выложить код бесплатно.

  12. Добрый день!
    Правильно ли я понял, что в Git выложен только код демо-примера, без реализации заданий?
    Интересно было бы посмотреть реализации сделаную профессиональным программистом с точки зрения стиля и конструкций.

    И второй вопрос.
    В дополнительных заданиях к лекции есть задание — сделать кнопку «С» по которой будет осуществляться полный сброс калькулятора.
    С точки зрения best practice можно ли, что бы обнулять вручную свойства brain сделать при нажатии кнопки просто:
    brain = CalculatorBrain()

  13. Добрый день!
    У меня такая ошибка: при нажатии «+» или любой другой бинарной операции, если потом не нажать следующее число, а снова нажимать на «+», то в результат складывается результат сам с собой, и так бесконечно.
    Я запустила проект из вашего решения, там происходит так же.
    Не могу придумать, как это поправить, чтобы при повторном нажатии на знак операции, ничего в строке результата не менялось.
    Подскажите пожалуйста.

    • Это не ошибка — так работает очень упрощенная версия Калькулятора, представленная профессором в качестве демонстрационного примера. Если вы внимательно послушаете или прочитаете Лекцию 2, то поймете, что в этом упрощенном калькуляторе изначально результат бинарной операции можно получить, только нажав клавишу «=» и код выглядел следующим образом:

      case .binaryOperation(let function):
      pending = PendingBinaryOperationInfo(binaryOperation: function, firstOperand: accumulator)
      case .equals:
      executeBinaryOperation()

      Затем профессор решает, что такой набор “7” “x” “8” “x” “2” “x” “3 не работает, потому что требуется нажать знак “=” для оценки промежуточных операций. То есть он должен нажимать следующее: “4” “x” “7” “=” “x” “3” “=” “x” “8” “=”. В действительности же нужно, чтобы автоматически появлялся знак “=” при нажатии бинарной операции.
      Я могу нажать “4” “x” “5” и когда я нажму “x”, то мне не нужно было бы нажимать “=”.
      Для этого появилась функция executeBinaryOperation() еще и перед обработкой бинарной операции:
      case .binaryOperation(let function):
      executeBinaryOperation()
      pending = PendingBinaryOperationInfo(binaryOperation: function, firstOperand: accumulator)
      case .equals:
      executeBinaryOperation()

      Если вам это не нужно, и вы хотите строго соблюдать получение результата бинарной операции по знаку равенства «=», то используйте первый вариант.
      Не стоит относится к этому демонстрационному примеру как к законченному приложению «Калькулятор». Полноценное приложение «Калькулятор» проектировалось в предыдущем курсе по iOS 8 https://bestkora.com/IosDeveloper/lektsiya-3-cs193p-winter-2015-primenyaem-mvc/. Этот калькулятор использовал стэк для запоминания операций и операндов и рекурсивные функции для его оценки. В том курсе на мой взгляд демонстрационный пример сильно переусложнен и алгоритмические проблемы перевешивают синтаксические конструкции Swift, которые мы должны изучать.
      В курсе iOS 9 профессор выбрал более легкий и сильно упрощенный вариант калькулятора, за что ему отдельное спасибо. Теперь мы больше смотрим на синтаксис Swift, чем на алгоритмы функционирования калькулятора и это правильно.
      Если вас интересует готовое приложение Калькулятор, то можно посмотреть Лекцию 3 курса «iOS 8 + Swift». Там все более фундаментально и много интересного.

  14. Спасибо большое Вам за качественный перевод!
    Лектор упоминает о домашнем задании. Подскажите, пожалуйста, где его взять?

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