Это решение Задания 3 — дополнительные пункты 1, 2 и 3. Обязательные пункты Задания, а также ссылки на текст самого Задания 3 можно посмотреть:
Задание 3. Решение — обязательные пункты.
Для данного поста код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.
Пункт 1 дополнительный
Поймите, как работают Instruments, анализируя производительность жестов panning и pinching в вашем графическом View. Что делает перемещение графика по экрану таким “вялым”? Объясните в комментариях к вашему коду, что вы обнаружили и что с этим можно сделать.
Вместо запуска приложения запускаем Instruments для приложения с помощью ⌘I и выбираем Time Profiler.
Нажимаем кнопку записи,
возвращаемся к нашему приложению (оно уже запущено), создаем график sin (1/M) и начинаем водить по экрану (жест pan)
и, наконец, останавливаем запись Time Profiler. В полученных данных устанавливаем участок, где пользователь применял жест pan, и устанавливаем его для анализа длительностью 2с.
Обратите внимание, что все опции Call Tree должны быть включены вами, так как при старте Time Profiler они почти все отключены по умолчанию. Большая часть времени (90,8%) расходуется на метод drawRect в GraphView.
Дважды кликаем на строке с drawRect и получаем распределение времени в модуле drawRect: 41,8% приходится на рисование осей в функции axesDrawer.drawAxesInRect, 58,1% — на рисование самого графика в функции drawCurveInRect.
Мы можем пойти еще дальше и посмотреть, чем занята функция построения осей графика? Дважды кликаем на функции axesDrawer.drawAxesInRect и видим, что 91,3% времени занимает построение и оцифровка «засечек» на осях.
Теперь посмотрим, чем занята функция drawCurveInRect построения графика, дважды кликнув на соответствующей строке в Time Profiler.
Мы видим, что 63,7% времени занимает вычисление значения y (x).
Итак, мы исследовали жест panning, и приходим к выводу, что для ускорения этого процесса нужно не строить на осях «засечки» — это сократит время на 40% — и не рассчитывать значения функций y (x) или рассчитывать их пореже или кэшировать данные.
Теперь займемся исследованиями жеста pinchining для изменения масштаба с помощью Time Profiler.
Большая часть времени (90,9%) как и прежде расходуется на метод drawRect в GraphView.
Дважды кликаем на выделенной строке и получаем распределение времени в модуле drawRect: 28,5% приходится на рисование осей в функции axesDrawer.drawAxesInRect, 71,4% — на рисование самого графика в функции drawCurveInRect.
Теперь посмотрим, чем занята функция drawCurveInRect построения графика, дважды кликнув на соответствующей строке в Time Profiler.
Мы видим, что 55,1% времени занимает вычисление значения y (x).
Мы можем пойти еще дальше и посмотреть, чем занята функция построения осей графика? Дважды кликаем на функции axesDrawer.drawAxesInRect и видим, что 85,8% времени занимает построение и оцифровка «засечек» на осях.
Итак, мы исследовали жест 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 и не нуждаются в новом параметре.
В классе GraphView добавим private свойство
private var lightAxes:Bool = false, которое по умолчанию выставлено в false и меняется на true и обратно только в обработке жеста panning.
Второе — создадим в GraphViewController кэш для накопления данных:
Этот словарь будем опустошать при каждом задании новой программы program вычисления для нашего калькулятора.
Кэш будет создаваться и использоваться в методе делегата GraphViewDataSource
Проведем оценку быстродействия полученного результата на Time Profiler.
Мы видим, что Большая часть времени (73,7 %, а не 90,8% как до этого варианта выполнения жеста pan) расходуется на метод drawRect в GraphView. Дважды кликаем на выделенной строке и получаем распределение времени в модуле drawRect: 15,4% приходится на рисование осей в функции axesDrawer.drawAxesInRect, 84,3% — на рисование самого графика в функции drawCurveInRect.
Результат понятен, поскольку мы вместо осей рисуем просто две взаимно перпендикулярные линии.
Теперь посмотрим, чем занята функция drawCurveInRect построения графика, дважды кликнув на соответствующей строке в Time Profiler.
Мы видим, что существенно упало время, затрачиваемое на работу калькулятора (за счет кэширования) —11,4%, а было 63,7%, и теперь основное время уходит на собственно рисование — функцию stroke() — 73,3%. Следовательно, наше кэширование работает очень хорошо.
Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.
Вариант 2-ый: отсутствие засечек на осях и использование «замороженного» изображения.
Как только жест начался, мы делаем мгновенный снимок нашего графика и помещаем его в
private var snapshot:UIView?
Мы добавляем его к нашему view – график застывает, а позволяем скользить вслед за жестом pan только осям без цифровых засечек. Как только мы останавливаем жестом pan, мгновенный снимок убирается с нашего view, и оси восстанавливаются:
Вы видите процесс выполнения жеста pan.
Давайте исследуем его на Time Profiler.
Мы видим прекрасный результат: меньшая часть времени (всего 30,5 %, а не 73,7 % и не 90,8% как до специальных вариантов выполнения жеста pan) расходуется на метод drawRect в GraphView. Дважды кликаем на выделенной строке и получаем распределение времени в модуле drawRect: 92,1% приходится на рисование осей в функции axesDrawer.drawAxesInRect, (хотя они очень легкие, без засечек) 5,9% — на рисование самого графика в функции drawCurveInRect.
Таким образом нам удалось добиться существенного (почти на 60%) повышения быстродействия жеста pan.
Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.
Пункт 3 дополнительный
Сохраните origin и scale между рапусками вашего приложения. Где это следует сделать, оказывая наибольшее уважение MVC, как вы думаете?
используем механизм NSUserDefault и весь код размещаем в GrapViewController:
Setters private переменных scale и origin в GrapViewController получают данные из NSUserDefault, а getters — записывают данные в NSUserDefault. Осталось лишь указать в коде, где NSUserDefault данные будут получены, а где сохранены.
Местом получения NSUserDefault данных является Наблюдатель Свойства (Property Observer) GraphView
Местом сохранения NSUserDefault данных выберем метод «жизненного цикла» View Controller viewWillDisappear
Для правильного функционирования механизма NSUserDefaults необходимо наличие экземпляра класса и «ключи» для сохранения информации.
Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.
Продолжение следует…