Pull to refresh

Как пользоваться утилитой Instruments в Xcode

Reading time 16 min
Views 62K
Original author: Matt Galloway

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

Помимо улучшения вашего приложения путем добавления в него всяких «завитушек», есть одна вещь, которую все хорошие разработчики должны сделать со своим кодом – обработать его утилитой Instruments!

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

Вы узнаете:
  • Как обнаружить и устранить в вашем коде ошибки управления памятью, используя инструменты Allocations и Leaks, и
  • Как определить проблемное место в коде, используя инструмент для профилирования времени, и
  • Как сделать ваш код более эффективным.

Примечание: Это руководство предполагает, что вы компетентны в Objective-C и iOS программировании. Если вы полный новичок в iOS программировании, вам нужно почитать другие уроки с этого сайта. Приложение, на котором мы тренируемся в этом руководстве, использует storyboard, так что убедитесь, что вы знакомы с этой концепцией; если нет — начните с этого нашего руководства.

В этом руководстве использован Xcode 4.5, убедитесь, что ваш Xcode обновлен до последней версии, которая доступна в Mac App Store.

Всё готово? Приготовьтесь окунуться в увлекательный мир Instruments!

Приступая к работе


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

Скачайте проект, распакуйте его и откройте в Xcode.
Откомпилируйте и запустите приложение, в приложении в строку поиска введите какой-нибудь текст, выполните поиск, затем войдите в результаты поиска, и вы увидите нечто вроде этого:


Попользуйтесь приложением, попробуйте его основные функции. Как вы поняли, смысл приложения — поиск и отображение фотографий с Flickr. В верхней части приложения есть строка поиска, куда вы пишите запрос, выполняете поиск, и строка с результатами появляется в таблице.

Эта строка таблицы содержит поисковый запрос и число найденных результатов в скобках. Если вы нажмете на строку, то увидите другую таблицу, с маленьким изображением и его названием.

Если вы нажмете на одну из строчек с изображением, то сможете посмотреть уже большое изображение, на полный экран. При этом, используя кнопку Rotate, вы можете поворачивать изображение.

Пока все хорошо! Вы видите, что приложение работает, как задумано. И вы готовы выкладывать его в магазин. Однако давайте посмотрим, что скажет про это приложение утилита Instruments.

Время профилировать


Многие разработчики начинают разработку со смутной внутренней потребностью сделать приложение, которое будет быстрым — и это достойная цель. Затем они читают о так называемой «преждевременной оптимизации» и задаются вопросом, как можно избежать это страшное явление, осуждаемое седобородыми программистами. А в худшем случае, начинающие разработчики забывают об оптимизации вообще!

Конечно, Вы можете исключить оптимизацию из вашего процесса разработки приложений. Это десять лет назад мобильные устройства были невероятно ограничены, даже использование чисел с плавающей точкой было запрещено, потому что это делало размер кода огромным, а расчёты шли со скоростью ледника.

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

Что такое «профилирование»? Профилирование — это измерение. Результат сеанса профилирования позволяет определить, какие части кода используются наиболее часто, что в свою очередь, говорит вам, какие части кода вы должны попытаться улучшить.

Вы можете потратить неделю на тонкую настройку интересного алгоритма, но если этот код занимает только 0,5% от общего времени выполнения, никто никогда не заметит разницы, независимо от того, насколько вы улучшили его. Если вместо этого вы провели оптимизацию цикла, на который ваша программа тратит 90% своего времени, и улучшили цикл только на 10%, то скорей всего обновлённая программа получит в отзывах пять звезд, потому что она станет намного быстрее!

Первый урок оптимизации: найти правильные места, чтобы их оптимизировать!
Преждевременная оптимизация, как вы уже догадались, тратит время на оптимизацию кода, который в итоге не имеет значения.

Первый инструмент, который мы рассмотрим, это «Time Profiler». При профилировании вы часто будете сталкиваться с трассировкой стека потока. Вот, например, окно трассировки:


Этот экран отображает стек вызовов каждого потока. Каждая строка, или фрейм, как его ещё называют, представляет собой выполняемые программой команды, а самая верхняя строка (фрейм №0) — строка, в которой в настоящий момент процессор выполняет код.

Время, потраченное каждой строкой, определяется тем, сколько раз профилировщик остановился на этой строке, пока процессор обрабатывал код, указанный в этой строке.
Например, если 100 сэмплов выполнять с интервалом в 1 миллисекунду, и конкретная строка кода оказывается в верхней строке трассировки стека у 10 сэмплов, то вы можете сделать вывод, что примерно 10% от общего времени выполнения, то есть 10 миллисекунд, было потрачена на выполнение этого кода. Это довольно грубое приближение, но оно работает!

Если пока непонятно, разберёмся потом, начнём пользоваться Instruments!
В меню Xcode, выберите пункт ProductProfile или нажмите ⌘ I. Так вы откомпилируете приложение и запустите Instruments. Вас встретят окном выбора, которое выглядит следующим образом:


Тут можно выбрать шаблон профилирования, который вы хотите использовать.
Выберите шаблон Time Profiler и нажмите кнопку Profile. Запустится симулятор iOS и ваше приложение. Система попросит ввести пароль для доступа утилиты Instruments к другим процессам — не бойтесь, это безопасно!
В окне Instruments, вы сможете увидеть отсчет времени, и как маленькая стрелка движется слева направо над графиком в центре экрана. Это означает, что ваше приложение работает.
Теперь, используем наше приложение по назначению. Сделайте пару поисков, понажимайте по результатам поиска. Вы, наверное, заметили, что вход в результаты поиска работает утомительно медленно, а прокрутка списка результатов поиска также невероятно раздражает – у нас ужасно неуклюжее приложение!
Что ж, вам повезло, потому что мы собираемся всё это исправить! Однако для начала быстренько осмотримся в Instruments.
Во-первых, убедитесь, что у селектора View на панели инструментов нажаты все три пункта выбора и выглядит он вот так:


Это значит, что все панели видны. Теперь посмотрите на скриншот ниже и описание каждого раздела под ним:


  1. Это кнопки управления запуском. Средняя красная кнопка останавливает и запускает приложение, которое в настоящее время профилируется. Она на самом деле останавливает и запускает приложение – а не ставит на паузу.
  2. Это таймер работы и навигатор. Таймер отсчитывает, сколько времени работает приложение, которое профилируется. Стрелками можно перемещаться между запусками. Если Вы остановите, а затем запустите приложение с помощью элементов управления, вы начнёте новый запуск. На дисплее будет написано «Run 2 of 2», но вы сможете вернуться к данным первого запуска, остановив текущий запуск, а потом нажав на стрелку влево.
  3. Это называется трек. В шаблоне Time Profiler, который мы выбрали, есть только один инструмент, и значит, только один трек. Вы узнаете больше об особенностях графиков трека позже, в этом учебнике.
  4. Это панель дополнительных подробностей. В шаблоне Time Profiler он используется для отображения трассировки стека.
  5. Это панель подробностей. Тут показана основная информация о конкретном инструменте, который вы используете. В нашем случае она показывает вызовы, которые можно назвать «горячими» — то есть, те, которые использовали больше всего процессорного времени.
    Если вы щелкните на панели чуть выше, на которой написано: «Call Tree» (той «Call Tree», что слева) и выберите пункт «Sample List», то вы увидите другой вид данных. Это будет список сэмплов. Нажмите на пару сэмплов, и вы увидите пойманную ими трассировку стека в панели дополнительных подробностей.
  6. Это панель опций. Вы узнаете о ней в ближайшее время.

А теперь правим неуклюжий интерфейс нашей программы!

Ищем глубоко


Запустите поиск изображений, и войдите в результат. Мне лично нравится искать слово «dog», но вы сами выбираете то, что вы хотите искать — вы можете быть одним из тех самых людей, обожающих котэ!

Покрутим окно результата вверх и вниз несколько раз, так чтобы у вас был хороший объем данных для профилирования. Вы увидите, что цифры в середине экрана меняются и график заполняется; значит, циклы центрального процессора используются.

Вы наверно, не ожидали такого «тормознутого» поведения пользовательского интерфейса; приложение со списком не готово к AppStore, пока список не прокручивается, как по маслу! Чтобы помочь точно определить проблему, необходимо изменить некоторые опции у профилирования по времени.

В панели опции (помните, №6 на рисунке), в разделе Call Tree, поставьте галочку у пунктов Separate by Thread, Invert Call Tree, Hide System Libraries и Show Obj-C Only. Это будет выглядеть так:


Вот для чего нужны эти опции:
  • Separate by Thread: Каждый поток должен рассматриваться отдельно. Это позволяет понять, какие потоки ответственны за наибольшее количество использования CPU.
  • Invert Call Tree: Трассировка стека будет отображаться сверху вниз. Это означает, что вы увидите в отчёте те строки кода, которые были в фрейме №0, когда остановился сэмпл. Ведь, как правило, вы хотите увидеть самые «тяжёлые» строки кода, на которые процессор тратит больше всего времени.
  • Hide Missing Symbols: Если не найден dSYM-файл для вашего приложения или системного фреймворка, то вместо имён процедур, вы увидите только шестнадцатеричные значения. Если выбрана эта опция, то они будут скрыты, и только полностью распознанные вызовы будут показаны. Это поможет упорядочить представленные данные.
  • Hide System Libraries: Если выбрана эта опция, то только вызовы вашего собственного приложения будут отображаться на дисплее. Это очень полезная опция, так как обычно вам интересны только затраты процессора на ваш код — вы ничего не можете сделать с затратами процессора на системные библиотеки!
  • Show Obj-C Only: Если выбрана эта опция, то будут отображаются только методы Objective-C, а не всякие там функции C или C++. Их обычно в вашей программе нет, ну если только вы не используете OpenGL, например.
  • Flatten Recursion: Эта опция позволяет отображать рекурсивную функцию (функцию, которая сама себя вызывает) в качестве одной записи в каждой трассировке стека, а не в виде нескольких записей.
  • Top Functions: Если поставить галочку, то Instrumens будет считать временем, потраченным функцией, как сумма времени непосредственно внутри этой функции и времени, проведенном в под-функции, вызванной этой функцией. То есть, если функция A вызвала функцию B, то время, потраченное A, будет равняться времени, потраченным функцией A ПЛЮС времени потраченным функцией B. Это может быть очень полезным, так как позволяет найти сразу самый трудоемкий вызов.


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


Ну, конечно, мы видим проблему. Подавляющее большинство времени потрачено на выполнение процедуры setPhoto: из класса PhotoCell, то есть на вывод фото в строке таблицы. Это не должно быть для вас новостью, вы помните, что самым «медленным» элементом интерфейса программы как раз и была прокрутка таблицы.
Чтобы узнать о том, что происходит внутри этого метода, дважды щелкните на строке с его названием. Вы увидите такое окно:


Вот это уже интересно, не правда ли! Почти три четверти потраченного времени процедура setPhoto: занята созданием переменной imageData для фото!

Теперь вы можете увидеть, в чём проблема. Метод dataWithContentsOfURL ждёт и не возвращается, пока данные не были загружены. А каждый запрос в интернет на получение данных может занять до нескольких секунд. Этот метод выполняется в главном потоке, и, следовательно, весь пользовательский интерфейс блокирован в то время, когда загружаются изображения.

Чтобы решить эту проблему, надо воспользоваться вызовом ImageCache, который позволяет асинхронно загружать изображения в фоновом потоке. Код уже есть в классе PhotoCell.
Вы можете переключиться в Xcode и найти нужный файл, но Instruments имеет удобную кнопку «Open in Xcode». Найдите её на панели чуть выше кода и нажмите:


Нажимаем! Xcode открывает нужное нам место!
Теперь, закомментируйте две строки, в которых мы заполняем переменную imageData и изображение у строки таблицы, и снимите комментарий в блоке кода ниже. Теперь метод setPhoto будет выглядеть следующим образом:

- (void)setPhoto:(FlickrPhoto *)photo {
    _photo = photo;
 
    self.textLabel.text = photo.title;
 
//    NSData *imageData = [NSData dataWithContentsOfURL:_photo.thumbnailUrl];
//    self.imageView.image = [UIImage imageWithData:imageData];
 
    [[ImageCache sharedInstance] downloadImageAtURL:_photo.thumbnailUrl
                                  completionHandler:^(UIImage *image) {
                                      self.imageView.image = image;
                                      [self setNeedsLayout];
                                  }];
}


Вновь запустите профилирование приложения с помощью Instruments, нажав ProductProfile (или ⌘ I — помните, что эти «горячие клавиши» действительно экономят ваше время).
Обратите внимание, что в этот раз у вас не спросили, какой шаблон Instruments использовать. Это потому, что у вас ещё открыто окно нашего приложения, и Instruments предполагает, что вы хотите запустить его снова с тем же шаблоном.

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

Выглядит здорово! Не пора ли отправить программу в AppStore? Ещё нет!

Что-то с памятью моей стало


Следующий инструмент, который мы рассмотрим, это Allocations. Он даст вам подробную информацию обо всех объектах, которые были созданы и о памяти, которую они заняли; он также показывает количество ссылок на каждый объект.

Начнём профилирование нашего приложения с новым профилем. Закройте Instruments, вернитесь в Xcode и выберите пункт меню ProductProfile снова. Затем выберите профиль Allocations и нажмите кнопку Profile.


Утилита Instruments откроется опять, и вы увидите такое окно:


В этот раз вы увидите два трека. Один из них называется Allocations, другой — VM Tracker. Профиль Allocations мы подробно рассмотрим в этом уроке; профиль VM Tracker тоже очень полезен, но он сложнее, про него в другой раз.
Так что за ошибку мы собираемся искать?

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

Второй вид утечки немного сложнее. Он называется «неограниченный рост памяти». Он происходит, когда память продолжает выделяться и ей не даётся шанс быть освобождённой.
Если так будет продолжаться бесконечно, то в какой-то момент память будет заполнена, а вы столкнётесь с большими проблемами с памятью. В iOS это означает, что приложение будет убито системной контрольного таймера. Так ваше приложение не получит пять звезд!

Разработаем план, как нам обнаружить неограниченный рост памяти. Во-первых, выполните в приложении штук 10 разных поисков (но не заходите пока в результаты). Убедитесь в том, что поиск дал какие-то результаты! Теперь подождём несколько секунд, не трогая приложение.
Вы заметите, что график в треке Allocations растёт. Это говорит о том, что память продолжает выделяться. Вот вы и получили неограниченный рост памяти.

Сделаем, так называемый «анализ снимка кучи». Чтобы его сделать, нажмите кнопку под названием «Mark Heap». Вы её найдете на панели с левой стороны:


Нажмите её и увидите как в треке появился красный флаг, вот так:


Цель анализа снимка кучи — выполнить какое-то действие несколько раз, и посмотреть, где память растет неограниченно. Войдите в один из результатов поиска и подождите несколько секунд, пока изображения не загрузятся, а затем вернитесь на главную страницу. Поставьте ещё один флажок на куче. Сделайте это несколько раз с разными результатами поисков.

После 10 входов во все 10 результатов, окно Instruments будет выглядеть так:


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

Вполне возможно, что ваше приложение ни в чём не виновато, и это что-то в глубине UIKit расходует память. Дадим системным фреймворкам и вашему приложению шанс очистить свою память, прежде чем показывать пальцем на виноватого.

Симулируем сообщение о нехватке памяти, выбрав в меню симулятора iOS пункт Hardware\Simulate Memory Warning (в русской версии – Аппаратура\Симулировать предупреждение о нехватке памяти). Вы заметите, что памяти стало использоваться поменьше, но, конечно, не так мало как раньше. То есть, где-то происходит неограниченный рост памяти.

Давайте рассмотрим сделанные снимки кучи.

Порази меня своим лучшим снимком


Первый снимок — базовый. Откройте его, вы увидите все объекты, для которых была выделена память и которые не были освобождены на момент, когда был сделан снимок. Последующие снимки будут содержать только изменения в объектах между текущим и предыдущим снимками.

Посмотрите на колонку «Heap Growth», и вы увидите, что, безусловно, рост где-то происходит. Откройте один из снимков кучи, и вы увидите такое окно:


Ого, сколько объектов! С чего начать?

Лучше всего посмотреть на классы, которые вы используете в вашем приложении напрямую. В нашем случае, классы HTTPHeaderDict, CGRegion, CGPath, CFNumber, и т.д. могут быть проигнорированы.

А вот класс UIImage, который мы используем в приложении, нам интересен. Нажмите на стрелку слева от UIImage, чтобы раскрыть список созданных объектов UIImage. Выберите в списке один из конкретных объектов UIImage и посмотрите на дополнительную панель информации (справа):


Тут показана трассировка стека в момент, когда этот конкретный объект UIImage был создан. Строки трассировки, показанные серым шрифтом, находятся в системных библиотеках; строки чёрным шрифтом — в коде приложения. Если дважды щелкнуть на такую, выделенную чёрным шрифтом, строку – попадём в код нашего приложения:

- (void)downloadImageAtURL:(NSURL*)url completionHandler:(ImageCacheDownloadCompletionHandler)completion {
    UIImage *cachedImage = [self imageForKey:[url absoluteString]];
    if (cachedImage) {
        completion(cachedImage);
    } else {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            [self setImage:image forKey:[url absoluteString]];
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(image);
            });
        });
    }
}


Утилита Instruments очень полезна, но она не может сделать за вас всё! Вам сейчас нужно поработать самостоятельно, чтобы понять, где проблема с памятью.

Посмотрите на метод выше, и вы увидите, что он вызывает метод с названием SetImage:forKey. Этот метод кэширует изображение, чтобы позже использовать его снова в приложении. Ага! Может быть, мы нашли проблему!

Взгляните на реализацию этого метода:

- (void)setImage:(UIImage*)image forKey:(NSString*)key {
    [_cache setObject:image forKey:key];
}


Тут в словарь добавляется изображение, в качестве объекта, соответствующего ключу – URL-адресу изображения. Но если вы посмотрите на код, вы заметите, что изображение никогда не удаляется из этого словаря!
Вот где происходит наш неограниченный рост памяти! Все работает как надо, но приложение не удаляет ничего из кэша — оно только добавляет!

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

Чтобы ImageCache получал предупреждения, измените метод инициализации, чтобы он стал таким:

- (id)init {
    if ((self = [super init])) {
        _cache = [NSMutableDictionary new];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(memoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    }
    return self;
}


Так вы зарегистрируете получение сообщений UIApplicationDidReceiveMemoryWarningNotification в метод memoryWarning:. Теперь реализуйте этот метод, как показано ниже:

- (void)memoryWarning:(NSNotification*)note {
    [_cache removeAllObjects];
}


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

Чтобы проверить это, запустите снова утилиту Instruments (в Xcode нажмите ⌘ I) и повторите шаги, которое вы делали раньше. Не забывайте симулировать нехватку памяти в конце!
Примечание: Убедитесь, что вы запускаете исправленное приложение из Xcode, а не просто нажимаете красную кнопку в Instruments. Только так вы запустите профилирование изменённого кода.

В этот раз анализ снимков кучи будет выглядеть так:


В этот раз, использование памяти резко упало после того, как было получено сообщение о нехватке памяти. Конечно, всё равно используется некий объём памяти, но не такой большой, как раньше.

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

Молодцы! Еще одна проблема устранена! Ну, а теперь-то, настало время загрузить приложение в AppStore! Ой, подождите – ведь есть ещё проблема первого типа утечки памяти, и мы её до сих пор не решили.

Зови сантехника — у тебя утечка!


Следующий профиль, который мы рассмотрим это Leaks. Он используется, чтобы найти утечки памяти первого рода, как мы говорили ранее – это когда на объект больше нет ссылок, а он занимает память.

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

Закройте Instruments, вернитесь в Xcode и выберите пункт меню ProductProfile. Выберите профиль Leaks и нажмите кнопку Profile:


В окне Instruments 2 трека — Allocations и Leaks. Трек Allocations тот же самый, что вы использовали в предыдущем разделе.

Мы будем использовать только трек Leaks в этом разделе, поэтому щелкните по нему, чтобы его выделить. Обратите внимание, что содержимое других окон Instruments после этого изменилось и теперь показывают информацию о треке Leaks.

В информационной панели, внизу слева, уже включён флаг Automatic Snapshotting. Это значит, что снимки памяти для обнаружения утечек памяти будут делаться автоматически.
Интервал между снимками может быть изменён, но 10 секунд, поставленных по умолчанию, вполне достаточно для наших целей. Также можно сделать снимок в любое время, нажав на кнопку «Snapshot Now».

Поработаем с приложением! Запустите поиск и войдите в результаты поиска. Затем нажмите на одну из строк с результатом, чтобы увидеть изображение на полный экран. Понажимайте несколько раз кнопку Rotate в левом верхнем углу.

Вернитесь в Instruments. Если вы сделали все эти шаги правильно, вы заметите появление утечки! Окно Instruments будет выглядеть следующим образом:


Вернитесь в симулятор iOS и понажимайте кнопку Rotate ещё пару раз. Вернитесь в Instruments и подождите. Появятся ещё утечки, окно теперь выглядит примерно так:


Откуда эти утечка? Если дополнительной панели информации у вас не открыто, откройте её, нажав крайнюю справа кнопку в селекторе View:


Выберите в списке объектов с обнаруженной утечкой памяти объект CGContext. Посмотрите на дополнительную панель информации, там показана трассировка стека в момент создания объекта. Трассировка выглядит подобно этой:


И снова в списке фреймов среди «серых» фреймов, вызовов системных фреймворков, есть «черный» фрейм – вызов кода нашего приложения. Дважды щелкните по нему, чтобы увидеть код для этого метода.
Это метод rotateTapped:, который обрабатывает нажатие кнопки Rotate. Этот метод вращает исходное изображение и создает новое изображение, код показан ниже:

- (void)rotateTapped:(id)sender {
    UIImage *currentImage = _imageView.image;
    CGImageRef currentCGImage = currentImage.CGImage;
 
    CGSize originalSize = currentImage.size;
    CGSize rotatedSize = CGSizeMake(originalSize.height, originalSize.width);
 
    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 rotatedSize.width,
                                                 rotatedSize.height,
                                                 CGImageGetBitsPerComponent(currentCGImage),
                                                 CGImageGetBitsPerPixel(currentCGImage) * rotatedSize.width,
                                                 CGImageGetColorSpace(currentCGImage),
                                                 CGImageGetBitmapInfo(currentCGImage));
 
    CGContextTranslateCTM(context, rotatedSize.width, 0.0f);
    CGContextRotateCTM(context, M_PI_2);
    CGContextDrawImage(context, (CGRect){.origin=CGPointZero, .size=originalSize}, currentCGImage);
 
    CGImageRef newCGImage = CGBitmapContextCreateImage(context);
    UIImage *newImage = [UIImage imageWithCGImage:newCGImage];
 
    self.imageView.image = newImage;
}


И опять утилита Instruments даёт вам подсказку, где проблема, но в чём проблема она не может сказать вам точно. Instruments может вам лишь показать, где был создан объект, виновный в утечке памяти. А ваша работа решить проблему!

Тот, кто писал код, наверное, думал, что ARC должен был взять на себя всё управление памятью… правильно?
Напомним, что ARC работает только с объектами Objective-C. Он не занимается подсчётом ссылок и освобождением объектов CoreFoundation, которые не являются объектами Objective-C.

И теперь становится очевидным, что проблема в том, что объекты CGContextRef и CGImageRef не были освобождены! Чтобы это исправить, добавьте следующие две строки кода в конце метода rotateTapped:

CGImageRelease(newCGImage);
CGContextRelease(context);


Эти два вызова необходимы, чтобы сбалансировать количество ссылок на эти два объекта. Мораль этой истории в том, что вы все равно должны помнить о подсчёте ссылок на объекты, даже если вы используете ARC в вашем проекте!
Войдите в Xcode, нажмите ⌘I, чтобы профилировать приложение в Instruments.

Поработайте в приложении снова, используя профиль Leaks и убедитесь, что утечка памяти была устранена. Если вы выполнили описанные шаги правильно, утечка должна исчезнуть!
Молодцы! Так держать!

Что дальше?


Теперь воспользуйтесь полученными знаниями, чтобы профилировать с помощью Instruments свои собственные приложения и посмотреть, что интересного в них творится! Кроме того, постарайтесь сделать использование Instruments частью вашей работы по разработке ПО.

Пропускайте через Instruments свой код как можно чаще. Выкладывайте своё приложение в AppStore, только убедившись, что вы проделали всё возможное по поиску проблем с производительностью и управлением памятью.
Теперь идите и делайте эффективные приложения!
Tags:
Hubs:
+35
Comments 15
Comments Comments 15

Articles