Задание 3. Решение — дополнительные пункты 1, 2 и 3

Screen Shot 2015-05-10 at 9.35.51 PM

Это решение Задания 3 — дополнительные пункты 1, 2 и 3. Обязательные пункты Задания, а также ссылки на текст самого Задания 3 можно посмотреть:

Задание 3. Решение — обязательные пункты.

Для данного поста код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.

Пункт 1 дополнительный

Поймите, как работают Instruments,  анализируя производительность жестов  panning и pinching в вашем графическом View. Что делает перемещение графика по экрану таким “вялым”? Объясните в комментариях к вашему коду, что вы обнаружили и что с этим можно сделать.

Вместо запуска приложения запускаем Instruments для приложения с помощью  ⌘I и выбираем Time Profiler.
Screen Shot 2015-05-08 at 8.28.15 PM
Нажимаем кнопку записи,
Screen Shot 2015-05-08 at 8.02.37 PM
возвращаемся к нашему приложению (оно уже запущено), создаем график sin (1/M) и начинаем водить по экрану (жест pan)
Screen Shot 2015-05-08 at 8.03.28 PM
и, наконец, останавливаем запись Time Profiler. В полученных данных устанавливаем участок, где пользователь применял жест pan, и устанавливаем его для анализа длительностью 2с. 
Screen Shot 2015-05-09 at 8.43.57 PM

Обратите внимание, что все опции Call Tree должны быть включены вами, так как при старте Time Profiler они почти все отключены по умолчанию. Большая часть времени (90,8%) расходуется на метод drawRect в GraphView.
Screen Shot 2015-05-09 at 8.30.32 PM
Дважды кликаем на строке с drawRect и получаем распределение времени в модуле drawRect: 41,8% приходится на рисование осей в функции axesDrawer.drawAxesInRect, 58,1% —  на рисование самого графика в функции drawCurveInRect.
Screen Shot 2015-05-09 at 8.34.39 PM
Мы можем пойти еще дальше и посмотреть, чем занята функция построения осей графика? Дважды кликаем на функции axesDrawer.drawAxesInRect  и видим, что  91,3% времени занимает построение и оцифровка «засечек» на осях.
Screen Shot 2015-05-09 at 8.38.08 PM
Теперь посмотрим, чем занята функция drawCurveInRect построения графика, дважды кликнув на соответствующей строке в Time Profiler.
Screen Shot 2015-05-09 at 8.41.13 PM
Мы видим, что 63,7% времени занимает вычисление значения y (x).
Итак, мы исследовали жест panning, и приходим к выводу, что для ускорения этого процесса нужно не строить на осях «засечки» — это сократит время на 40% — и не рассчитывать значения функций y (x) или рассчитывать их пореже или кэшировать данные.
Теперь займемся исследованиями жеста pinchining для изменения масштаба с помощью Time Profiler.
Screen Shot 2015-05-09 at 8.51.37 PM
Большая часть времени (90,9%) как и прежде расходуется на метод drawRect в GraphView.
Дважды кликаем на выделенной строке и получаем распределение времени в модуле drawRect: 28,5% приходится на рисование осей в функции axesDrawer.drawAxesInRect, 71,4% —  на рисование самого графика в функции drawCurveInRect.
Screen Shot 2015-05-09 at 8.57.45 PM
Теперь посмотрим, чем занята функция drawCurveInRect построения графика, дважды кликнув на соответствующей строке в Time Profiler.
Screen Shot 2015-05-09 at 9.29.52 PM
Мы видим, что 55,1% времени занимает вычисление значения y (x).
Мы можем пойти еще дальше и посмотреть, чем занята функция построения осей графика? Дважды кликаем на функции axesDrawer.drawAxesInRect и видим, что 85,8% времени занимает построение и оцифровка «засечек» на осях.
Screen Shot 2015-05-09 at 9.32.45 PM
Итак, мы исследовали жест pinching, и приходим к выводу, что для ускорения этого процесса нужно не строить на осях «засечки» — это сократит время на 28,5%(меньше, чем для жеста panning) — и не рассчитывать значения функций y (x) или рассчитывать их пореже или кэшировать данные.

Вот несколько предложений как можно уменьшить время во для жестов panning и pinching:

  • Вы можете не рисовать засечки на осях во время выполнения жестов …
  • Вы можете уменьшить «шаг»,с которым вычисляются графики, например , вычислять каждую 4-ую или каждую 10-ую точку …
  • Можно кэшировать результаты вычислений и использовать их во время выполнения жестов…
  • Вы можете поступить, как поступает iOS при вращении прибора, — сохранять изображение view перед жестом и манипулировать его размерами и позицией вместо перерасчета графика…

Пункт 2 дополнительный

Используйте информацию, которую вы обнаружили в 1-ом дополнительном пункте, для улучшения производительности жеста panning. НЕ превращайте ваш код в “месиво”, выполняя это. Ваше решение должно быть простым и элегантным. Есть сильное искушение при оптимизации принести в жертву читабельность кода или преступить границы MVC, но вам НЕ разрешено это делать для этого дополнительного пункта!

Вариант 1-ый: отсутствие засечек на осях и кэширование данных.

Первое, что сделаем — позволим осям не рисовать засечки и не оцифровывать их. Они мало помогают нам при жесте pan. Для этого добавим дополнительный параметр в функцию drawAxesInRect класса AxesDrawer, это будет параметр light: Bool = false. Задание параметра по умолчанию равного false поможет избежать изменений обращений к функции drawAxesInRect, которые были сделаны до добавления параметра  light и не нуждаются в новом параметре.
Screen Shot 2015-05-10 at 7.30.17 PM
В классе GraphView добавим private свойство
private var lightAxes:Bool = false, которое по умолчанию выставлено в false и меняется на true и обратно только в обработке жеста panning.
Screen Shot 2015-05-10 at 7.58.02 PM
Второе — создадим в GraphViewController  кэш для накопления данных:

Screen Shot 2015-05-16 at 9.16.31 AM

Этот словарь будем опустошать при каждом задании новой программы program вычисления для нашего калькулятора.
Screen Shot 2015-05-10 at 8.16.32 PM
Кэш будет создаваться и использоваться в методе делегата GraphViewDataSource
Screen Shot 2015-05-10 at 8.24.23 PM
Проведем оценку быстродействия полученного результата на Time Profiler.
Screen Shot 2015-05-10 at 8.32.10 PM
Мы видим, что Большая часть времени (73,7 %, а не 90,8% как до этого варианта выполнения жеста pan) расходуется на метод drawRect в GraphView. Дважды кликаем на выделенной строке и получаем распределение времени в модуле drawRect15,4% приходится на рисование осей в функции axesDrawer.drawAxesInRect84,3% —  на рисование самого графика в функции drawCurveInRect.
Screen Shot 2015-05-10 at 8.38.56 PM
Результат понятен, поскольку мы вместо осей рисуем просто две взаимно перпендикулярные линии.
Теперь посмотрим, чем занята функция drawCurveInRect построения графика, дважды кликнув на соответствующей строке в Time Profiler.
Screen Shot 2015-05-10 at 8.43.47 PM
Мы видим, что существенно упало время, затрачиваемое на работу калькулятора (за счет кэширования) —11,4%, а было 63,7%, и теперь основное время уходит на собственно рисование — функцию stroke()73,3%. Следовательно, наше кэширование работает очень хорошо.

Код  для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.

Вариант 2-ый: отсутствие засечек на осях и использование «замороженного» изображения.

Как только жест начался, мы делаем мгновенный снимок нашего графика и помещаем его в

 private var snapshot:UIView?

Мы добавляем его к нашему view – график застывает, а позволяем скользить вслед за жестом pan только осям без цифровых засечек.  Как только мы останавливаем  жестом pan,  мгновенный снимок убирается с нашего view, и оси восстанавливаются:

Screen Shot 2015-05-10 at 9.31.04 PM
Вы видите процесс выполнения жеста pan.

Screen Shot 2015-05-10 at 9.35.51 PM

Давайте исследуем его на Time Profiler.

Screen Shot 2015-05-10 at 9.41.12 PM
Мы видим прекрасный результат: меньшая часть времени (всего 30,5 %, а не  73,7 % и не 90,8% как до специальных вариантов выполнения жеста pan) расходуется на метод drawRect в GraphView. Дважды кликаем на выделенной строке и получаем распределение времени в модуле drawRect: 92,1% приходится на рисование осей в функции axesDrawer.drawAxesInRect, (хотя они очень легкие, без засечек) 5,9% — на рисование самого графика в функции drawCurveInRect.

Screen Shot 2015-05-10 at 9.45.53 PM
Таким образом нам удалось добиться существенного (почти на 60%) повышения быстродействия жеста pan.
Код  для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.

Пункт 3 дополнительный

Сохраните  origin и scale между рапусками вашего приложения. Где это следует сделать, оказывая наибольшее уважение MVC, как вы думаете?

используем механизм NSUserDefault и весь код размещаем в GrapViewController:
Screen Shot 2015-05-11 at 9.01.11 AM
Setters private переменных scale и origin в GrapViewController получают данные из NSUserDefault, а getters — записывают данные в NSUserDefault. Осталось лишь указать в коде, где NSUserDefault данные будут получены, а где сохранены.
Местом получения NSUserDefault данных является Наблюдатель Свойства (Property Observer) GraphView
Screen Shot 2015-05-11 at 9.12.59 AM
Местом сохранения NSUserDefault данных выберем метод «жизненного цикла» View Controller viewWillDisappear
Screen Shot 2015-05-11 at 9.16.58 AM
Для правильного функционирования механизма NSUserDefaults необходимо наличие экземпляра класса и «ключи» для сохранения информации.
Screen Shot 2015-05-11 at 9.20.58 AM
Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.
Продолжение следует…