Цитаты #9

Иногда так сложно сказать слова, но, не сказав их, будет намного сложнее жить.

Комментариев нет

Memory management

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

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

Так давайте же определимся что такое утечки. Утечки - выделенные но не освобождённые области памяти, на которые нет указателя. Всё это выглядит достаточно не серьёзно, на iPhone куча оперативной памяти, а приложение занимает дай бог 2 мб. Но всё хуже, приложение может использовать до 3мб, а дальше начинают возникать memory warning, и соответственно os пытается освободить те области, которые считает “ненужными”. Попросту говоря удаляет невидимые в данный момент элементы интерфейса, а это как минимум некрасиво, а может привести и к крашу. Зомби - уже освобождённые(убитые) облости памяти, к которым пытается обратиться программа. Соответственно это так же приводит к падению приложения.

В IPhone есть сборщик мусора!

Хотя часто и говорят, что его нет, да я и сам этим грешу. Но всё же сборщик мусора есть, правда весьма примитивный. Принцип управления памяти основан на подсчёте кол-ва ссылок на объект. При выделении памяти под объект их кол-во ставится в 1, а при достижении 0 память освобождается(выбрасывается мусор). Каждый раз когда вы хотите использовать объект вы должны увеличить кол-во ссылок, а каждый раз когда он вам становится ненужным уменьшить.

Есть 4 волшебных слова.

Чтобы управлять памятью нужно запомнить всего 4 слова: alloc, copy, retain, release. Давайте поговрим о каждом из них в отдельности:

alloc

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

copy

Копирует объект(не указатель). Оставляет неизменным кол-во ссылок на объект который мы копируем и увеличивает их кол-во на новый объект.

retain

Просто увеличивает кол-во ссылок на объект.

release

Обратная операция к трём выше перечисленным, уменьшает кол-во ссылок на 1.

Autorelease

Это немного другая история. Если отправить объекту сообщение autorelease то произойдёт “отложенный” release. Это нужно для тех случаев, когда вы не сможете по каким либо причинам отправить сообщение release объекту после действий с ним. Его применение в основном - возвращение объектов методами. Любой метод в названии которого нет alloc, copy или retain возвращает и должен возвращать ауторелизный объект.

А почему бы тогда не использовать его везде? Это уже удобно! Удобно, но накладно. Из-за того, что кол-во ссылок на объект уменьшается не сразу и соответственно память тоже освобождается не сразу. Тут нас и ждёт подводный камень, у нас всего 3мб, а если мы за раз обрабатываем большой объём данных появляются те самые memory warning.

Dealloc

Этот метод вызывается при удалении объекта, в нём вы должны зарелизить все переменные, которые использовал объект и в конце вызвать dealloc у родителя

1
2
3
4
5
- (void)dealloc {
[model release];
[view release];
[super dealloc];
}

Property

Свойства бывают трёх видов: assign, retain, copy. Последнии два должны быть уже понятны. А первый просто присваивает значение, не как не влияя на кол-во ссылок на объект. Приведу правильную реализацию сеттеров для каждого типа:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//assign
- (void)setList:(List *)list{
_list = list;
}

//retain
- (void)setList:(List *)list{
[_list autorelease];
_list = [list retain];
}

//copy
- (void)setList:(List *)list{
[_list autorelease];
_list = [list copy];
}

Помните, что вам не нужно следить что там делает property, вам достаточно утановить его а в случае необходимости сбросить значение, обнулите его и всё.

Delegate, кируца и яйцо

В процессе работы мы наткнулись на такую проблему, что у нас утекало большое кол-во объектов, выяснилось, что у нас два объекта ретейнили друг-друга и соответственно никогда не освобождали память. Это происходило при использовании паттерна делегирования. Был объект, который создавал другой объект и ставил себя его делегатом. А propery delegate было прописанно как retain. Ситуацию спасает замена retain на assign.

Советы

  • Слово release должно приходить сразу после alloc, retain или copy
  • А слово nil сразу после release
  • Всегда обnilяйте делегатов
  • По возможности воздержитесь от autorelease
  • Создовайте объекты именно тогда, когда они вам нужны
  • Не злоупотребляйте полями класса
  • Static Analysys Tool всё красиво и понятно рисует
  • Изредка проверяйте на утечки

Комментариев нет

Логирование

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

Можно для продажен версии заменять логи на простой комментарий типа /**/

1
2
3
4
5
6
7
#define DEBUG

#ifdef DEBUG
   #define MYLog(…) NSLog(__VA_ARGS__)
#else
   #define MYLog(…) /**/
#endif

Теперь просто заменяем везде NSLog на MYLog. А когда нужно выключить логи комментируем строку:

1
//#define DEBUG

Можно пойти дальше и автоматизировать это дело. Для этого идём в настройки проекта, build settings. Там ищем пункт Preprocessor Makros и в дебаговой сборке добавляем:

1
DEBUG=1

Это всё хорошо, но нам показалось мало и мы сделали ещё лучше. Добавив сборку для тестеров, в которой логи собираются в файл на девайсе, откуда его можно достать по средствам органайзера в Xcode. Хотите так же? Идём дальше.

Добавляем новую сборку и по такому же методу добавляем ему макросы:

1
VERSION_FOR_TESTERS=1 DEBUG=1

Теперь переправляем логи в файл, для этого в appdelegate добавляем такие строки:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void) redirectNSLogToDocuments {
   NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   NSString *documentsDIR = [allPaths objectAtIndex:0];
   NSString *pathForLog = [documentsDIR stringByAppendingPathComponent:@"logs.txt"];
   freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   #ifdef VERSION_FOR_TESTERS
      [self redirectNSLogToDocuments];
   #endif
   /*Здесь ваш остальной код*/
}

Вот и всё, приятного дебага:)

Комментариев нет

I am not normal, i have idea

I am not normal, i have idea

Комментариев нет

Ignoring user interaction

Во время разработки часто приходится сталкиваться с необходимостью отключить события взаимодействия с тач скрином. Можно это сделать для каждой вьюшки отдельно, а можно для всех сразу:

1
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];

И соответственно включаем обратно:

1
[[UIApplication sharedApplication] endIgnoringInteractionEvents];

Комментариев нет

Status bar аля reader.

Это первый мой пост про программирование, наконец-то свершилось:)

Как я уже говорил, я работаю в компании, которая в том числе занимается разработкой приложений под iPhone. И при работе над новым проектом перед нами встала задача засунуть в статус бар свою иконку – кнопку, аля reader.

Изначально мы думали, что придётся отрисовывать его самим, как-то определяя оператора, уровень сигнала и т.п. Но оказалось всё гораздо проще. В результате гугления был найден проект на github MTStatusBarOverlay, но к сожалению он нам не подошёл, у него немного другие цели. Так же был найден вопрос на stackoverflow.com. Два этих линка дали достаточно знаний, что бы написать своё решение данной задачи.

Суть метода в том, что мы накладываем новое окно(UIWindow) над статус баром и добавляем туда любые сабвьюшки (sub view).

1
2
3
4
5
6
overlayWindow = [[UIWindow alloc] initWithFrame: [[UIApplication sharedApplication] statusBarFrame]];
overlayWindow.windowLevel = UIWindowLevelStatusBar+1.0f;
overlayWindow.hidden = NO;
UIView *view =[[UIView alloc] initWithFrame:overlayWindow.frame];
[overlayWindow addSubview:view];
[view release];

Можно сделать лучше, создать наследника класса UIWindow и сделать всё необходимое там.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface statusBarOverView : UIWindow {
}
@end


@implementation statusBarOverView
- (id)init {
    if ((self = [super initWithFrame:[[UIApplication sharedApplication] statusBarFrame]])) {      
        self.windowLevel = UIWindowLevelStatusBar+1.0f;
        self.hidden = NO;
        self.layer.masksToBounds = YES;
        UIView *view =[[UIView alloc] initWithFrame:overlayWindow.frame];
        [overlayWindow addSubview:view];
        [view release];
    }
   
    return self;
}
@end;

У вас может возникнуть желание наоборот “убрать” иконку из статус бара. Это можно сделать, наложив на него соответствующую картинку или вьюшку(UIView) с чёрным фоном, если стиль вашего статус бара стоит как opaque black.

Надеюсь, вам поможет этот пост, и у меня хватит запала продолжить писать.

Комментариев нет

Цитаты #8

Друг – это тот, кто не даст зарасти твоей могиле травой. Я думаю, я нашёл таких людей.

Иногда один шаг вперёд сложнее десяти в обход.

Комментариев нет