Pull to refresh

Comments 43

честно говоря сколько программирую на iPhone/iPad а пару ошибок из списка у себя в проекте нашел
(особенно 6 и 10) так что спасибо
можно преспокойно программировать под iOS полгода, и не знать полностью о жизненном цикле UIViewcontroller'ов

Интервьюировал около 30-40 mac/ios разработчиков. Опыт разный, от полугода до двух лет, все с готовыми проектами.

Первый же вопрос рассказать про run loop вводил людей в ступор, лишь несколько человек рассказывали что-то правдоподобное. Полностью никто так ничего и не рассказал.
Второй вопрос — когда удаляются autorelease объекты — тут хотя бы фантазируют, а не просто молчат, попадают в точку опять же единицы.
Дальше уже обычные вопросы про категории, ассоциативные массивы к ним, прокси, распределенные обьекты для коммуникации между процессами, posix потоки, зачем нужна и как использовать авторизацию, основы gdb, библиотеки, использование intruments хотя бы в gui режиме… В общем, так и не нашел никого у нас :(
А с опытными ифонерами/маководами везде напряг. Обычно наблюдаю две ситуации:
а. Ничего не знают.
б. Ничего не знают и много хотят.
З.Ы. А Вы откуда?
Да, вы абсолютно правы :) Буквально месяц назад приходил к нам 18-летний юнец на должность джуниора. Раньше нигде не работал, подготовки ноль — показал примеры своих работ — о боже, лучше бы не показывал… Когда речь зашла о зарплате и ему был задан вопрос о желаемом окладе он, хитро прищурившись, сказал что знает, сколько должен получать джуниор. 2000$! Я чуть не поперхнулся :) И таких не мало… Куда катиться мир. :)
это еще хорошо что с готовыми проектами, у нас вообще большинство резюме
1) либо имеют только опыт Windows программирования (.NET/Java) приписывая «быстро учусь»
2) либо выложили пару аппстор приложений уровня сэмпл из сдк
вакансия на 5 члена команды висит уже 7 месяцев
А вы не думали опустить планку, взять человека с более низкими требованиями и обучить всему чему нужно?

Работодатели тоже странные бывают, все им гуру программистов подавай! Вот поэтому подобные вакансии и висят по году, ни кому не нужные.
да спокойно, только все хотят полную зарплату за пол-года обучения =)
Надеюсь, у Вас не на Juniora такие требования ;)
Ушел читать про ассоциативные массивы к категориям.
Имхо лучше на собеседовании про жизненный цикл UIViewController-а спрашивать чем то что вы. Еще бы спросили как работает objc_msgSend.
У любого интервьювера со временем набегает ряд «любимых» вопросов, ответы на которые он знает на зубок. Обычно, это что-то достаточно специфическое и редко используемое в повседневной практике. Уж не знаю почему так происходит, но замечал подобное и за собой в том числе. Даже пытался понять — зачем я спрашиваю то, что для работы особо не пригодится или то, что при необходимости можно узнать за 10 минут чтения документации? Может, немного самоутверждаюсь, когда на сложный вопрос никто не может ответить а я могу? Не знаю. Но бороться с собой надо.
Я ниже раскрыл каким образом эти вопросы сформировались, для каждого человека они почти индивидуальны, кроме базовых. У меня не было цели кого-то завалить, это действительно на самом деле рядовые вещи которые как оказалось практически никто не знает и которые на самом деле не узнать за 10 минут в документации. Если брать большинство тех примеров которые я привел (их конечно больше, просто не стал все расписывать), то они как минимум требуют недель изучения, для хорошего понимания тонкостей которые я кстати не спрашиваю — это уже месяцы.

А те несколько простых вопросы просто показывают читал ли вообще человек документацию по языку на котором пишет или нет. Это действительно вещи которые узнаются за 10 минут, но если человек не читал этих вещей, он просто о них не знает и даже не станет искать. Я тоже про них не смогу рассказать все в подробностях, хотя и использовал в проектах. Но я про них как минимум читал, знаю для чего используются и всегда найду за 5 минут. Другое дело когда люди садятся писать на obj-c через неделю листания доков — они не знаю про язык в действительности ничего, такие не нужны. Изучить хорошо obj-c и паттерны cocoa, это минимум полгода, в общих представлениях — пара месяцев. Люди же приходят с недельными знаниями.
Ну тут следует уточнить, что во-первых я искал мак разработчиков (просто их так мало, что откликаются в большинстве случаев iOS), во-вторых на самом деле я вообще бы ничего не спрашивал. Мне всегда было сложно сдавать устные экзамены, болталка не подвешена, поэтому я предпочитаю просто спросить код, чтобы не мучать напрасно людей. Если всё в коде хорошо, то вопросов и не будет. Здесь же так получается, что ни проект, так разные велосипеды или костыли, ну или просто ошибки. Поэтому я начинаю задавать вопросы исключительно по этим местам, это как студента пытаться вытащить задавая ему дополнительный вопрос. Ведь вдруг человек на самом деле знает как нужно сделать правильно, но по разным причинам не сделал этого.

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

Ну и последний шанс — базовые вещи, к которым UIViewController и Ко. на мой взгляд не относятся, они частности, а вот умение найти утечку памяти или понимание MVC — уже относится. Можно выучить первое, но в чуть другом уже начать плавать. А можно просто понимать второе и всегда писать правильно. Поэтому вопросы вполне обычные и нормальные. Вы не поверите, но половина в этом плавает. И ведь я спрашивал самые поверхностные и простые вещи, я никого не заставляю писать probes для dtrace, но когда даже gui не владеют… :/

p.s.
Кстати и objc_msgSend тоже спрашивал, впрочем интереса ради уже. У меня ПО часто выходит за пределы прикладного уровня. Вот сейчас например нужно писать инжект для finder'а чтобы в Lion можно было свою иконку в sidebar повесить. Мои ребята к сожалению в этом не очень, приходится мне заниматься.
Есть один интересный момент. Я часто наблюдал, что метод viewDidUnload далеко не всегда вызывается, когда обнуляется view контроллера.
Поэтому я всегда перегружаю метод:
[code]
— (void)setView:(UIView) value {
super.view = value;
if (![self isViewLoaded]) {
[self viewDidUnload];
}
}
[/code]
В данном случае важно понимать, что метод viewDidUnload может быть вызван несколько раз и он должен быть написан так, чтобы не возникало багов с этим связанных.
А с чего это он должен вызываться, когда обнуляется view?
1. Автор об этом пишет.
2. По чистой логике viewDidUnload является парным методом viewDidLoad. А значит и должен вызываться как минимум по одному разу на каждый вызов viewDidLoad в тречении жизни VC. Это так же подтверждается докой: «This method is called as a counterpart to the viewDidLoad method» VC Reference.
Хотя, нигде не говорится явно, что он должен вызываться когда обнуляется view (но сказано, что если он вызывается то view == nil). Это главная лажа :). Если честно, то Apple тут слажало хорошо. Например, во 2 версии сдк его вообще не было, как и isViewLoaded (вернее они был приватным).
Автор не пишет, что он должен вызываться, когда пользователь обнуляет view. Этот метод вызывается системой, когда она сама выгружает view, для оповещения. Если он вызван, то view действительно == nil. Не вижу никакой лажи.
А вот еще нашел:
«If the view controller releases its view, it calls its viewDidUnload method. You can override this method to perform any additional cleanup required for your views and view hierarchy» — VC Guide.
Лажа в том, что метод хоть и заявлен парным для viewDidLoad, таковым не является. И когда программист расчитывает на то что его вьюхи созданные в loadView/viewDidLoad будут зарелизены во viewDidLoad, а на практике этого не происходит легко получаем как минимум memory leak после повторного вызова loadView/viewDidLoad.
Спасибо за замечание. Подправил часть с viewDidLoad в dealloc. Действительно можно было подумать, что self.view вызовет viewDidUnload.
self.view = nil НЕ вызовет viewDidUnload
self.view = nil Лишь обнулит переменную _view, что в свою очередь при следующем доступе к self.view приведет к повторному вызову loadView.

А dealloc в библиотеке Three20 выглядит примерно так

- (void)dealloc {
...
// You would think UIViewController would call this in dealloc, but it doesn't!
// I would prefer not to have to redundantly put all view releases in dealloc and
// viewDidUnload, so my solution is just to call viewDidUnload here.
[self viewDidUnload];

[super dealloc];
}
Кстати, делаю аналогично.
Спасибо за статью. Как раз вплотную подошел к выкуриванию ликов в UIViewController'ах.
А нет ли у Вас наглядного примера проекта с иллюстрацией изложенный положений?
Нет, такого проекта с иллюстрациями нет — все ошибки брались из памяти, по горячим следам.
Хорошее предложение. Постараюсь добавить к посту проект с примерами.
Если вас не затруднит.
А то понимание изложенного материала есть, а вот как его применить на практике — тут местами проблема…
Особенно интересует инициализация/деинициализация аутлетов и их корректная последующая выгрузка
Было бы очень здорово увидеть в примере сохранение состояния контроллера.
К вопросу о init vs initWithNib. Все верно, за одним исключением — UITableViewController и его наследники.

Если я правильно помню, [UITableViewController new] не работает аналогично [[UITableViewController alloc] initWithNib:nil ...] — в том смысле, что первый вариант игнорирует соответствующий xib/nib файл, если он есть.
Да. Есть такое. Внесу изменения. Спасибо за замечание.

Ошибка #3(p): Создание визуальных компонент в методе initWithNibName

Вы указали на ошибку, но не дали решения.
Допустим я хочу создать UIView (UIlabel, UIImage). Если я буду использовать viewDidLoad, то получается нужно делать release в viewDidUnload, что выглядит не совсем правильно.
Это выглядит правильно, только надо дублировать release еще и в dealloc (соответственно в viewDidUnload мало того, что делать [_label release] но также и _label = nil)

Однако это все имеет смысл только если оставлять созданный в viewDidLoad label retain'ed. В прочем большинстве случаев достаточно делать

— (void)viewDidLoad {

_label = [[UILabel alloc] initWithFrame:...]
[self.view addSubview:_label];
[_label release]; // вот тут освобождать
}

то label освободится сам при выгрузке вьюхи и явно ничего чистить не надо
Я так понял, _label это поле класса?
В этом случае данный подход это распространенная ошибка. Заключается она в том, что когда self.view обнулится _label будет содержать мусор. И первое же обращение к этому полю приведет к крешу.
Чтобы этого избежать надо всегда пользоваться простым правилом «поля класса никогда не должны быть авторелизными или заретейнеными не самим классом» (естественно, что week reference исключение отсюда).
Формально Вы правы. Однако есть теория, а есть практика. А практика заключается в том, чтобы не нагромождать сущности сверх нормы в попытке описать все возможные случаи. Давайте, например, в программу еще вставим проверку качества микросхем памяти (мало ли что случится, никто ведь не застрахован от аппаратных сбоев)? Можно напихать кода, отслеживающего все возможные случаи во всех комбинациях. А можно делать так:

1) Следить за неутечкой ресурсов (что я и описал в предыдущем комментарии)

2) Обращаться с UI элементами (вьюхами) только методами обслуживающего их контроллера, что собственно прямо предусмотрено парадигмой MVC. А контроллер всегда знает (isViewLoaded), загружен ли его view или нет и соответственно, легальны ли все его члены _label.
Как раз практика и показывает, что пока не программист не научит себя простым правилам он обречен постоянно иметь проблемы из-за неверного управления памятью. Не один десяток раз я сталкивался с такими на разных проектах.
Вот Вы сами себе противоречите. Говорите что не надо нагромождать сущности, но при этом говорите о дополнительных методах обслуживания. Вы, что при каждом обращении к какой-нибудь вьюшке контроллера проверяете isViewLoaded?
Разумеется не при каждой. Давайте разберемся, при каких обстоятельствах self.view может стать nil? Только в двух —

1) если в коде программы явно прописан такой вызов (я делаю так крайне редко и эти случаи обслуживаются очень отдельно с учетом всех последствий)

2) Программа получила предупреждение о нехватке памяти и выгрузила те из вьюшек, которые либо не были в данный момент видны (лежат на соседней вкладке tabbarcontrollerа, лежат на нижнем уровне стека UINavigationController, нигде не лежат — т.е. self.view.superview == nil (это заметим, не случается с модальными контроллерами, в которых и осуществляется основная привязка данных к вьюхам — тут можно вообще особо не стараться с отслеживанием isViewLoaded — практически шанс того, что вьюха модального контроллера будет выгружена из памяти стремится к нулю). Причем если контроллер был создан на основе xib или контроллер переопределил loadView то при этом будет вызван viewDidUnload — в нем можно все обнулить.

Вот как раз второй случай и рассмотрим. А точнее рассмотрим те из вьюшек, обращения к которым надо проверять с isViewLoaded. При внимательном рассмотрении оказывается, что таких контролов до обидного мало. В большинстве случаев они либо статичны — текст на них не меняется с момента создания (кнопки, лейблы) либо они сами умеют себя обслуживать — таблицы через делегаты или срабатывания кнопок — пока кнопки и таблицы живы они сами о себе позаботятся.

Ситуаций, когда валидность именно этого член класса (_label) к именно этому контроллеру имеет смысл проверять оказывается на практике ну очень мало. Настолько мало, что тут не имеет смысл городить более сложный огород. Да и вообще, как правило, вся информация на форме меняется в одном месте и делается через что-то вроде

-(void)updateData {
if (self.isViewLoaded) {

}
}
// кстати этот вариант сработал бы на любом языке, а вариант с обнулением _label
// сработает только в Obj-C, где средствами языка разработчику предоставляются
// поблажка — конструкция [nil somemethod] не является фатальной.
// Если бы этой поблажки не было бы, то пришлось бы еще и проверять на _label != nil
// – вас это тоже возмущало бы?

Безусловно, всякие случаи бывают и по всякому нужен свой подход. Вы — за системность подхода. Я больше склоняюсь практичности. Не такое это сложное дело — отловить это проблемное место, если где что и позабыл вдруг.
В том то и дело, что такой индивидуальный подход в нашем меняющемся мире зачастую неудобен. Сегодня контроллер модальный, завтра не модальный. Сегодня вы работате над проектом, завтра пришло 5 джуниоров, послезавтра они целый день дебажат и не могут понять откуда у них BAD_ACCESS. Ведь в чем суть проблемы то? В том, что в поле в определенный момент времени будет хранится мусор. И кто-нибудь, когда-нибудь этот мусор словит.
А реализуя системный подход, который предлагает автор, всего этого можно избежать.
Добавлю от себя еще одну ошибку которую сам часто допускал: определение координат у subviews в контроллере. Этим должен заниматься свой собственный производный от view объект. К примеру нужно красиво отобразить текст чтоб в зависимости от длинны все поднималось и опускалось, это нужно делать в методе -(void)layoutSubviews у view. А со стороны контрооллера просто вызывать метод у view типа showObject:. В итоге такой подход даст следующие преимущества: автоматическая анимация при перевороте контроллера, уменьшение связности между view и контроллером т/к/ взаимодействие с view идет не через IBOutlet а с помощью методов, все IBOutlet-ы хранятся во view не как проперти. Описание протокола даст возможность легко заменить view на другой, но это уже если потребуется. В итоге получится что xib будет в основном описывать не controller а view.

Общий код dealloc и viewDidUnload предпочитаю выносить в спец метод: -(void)cleanup.
благодарю за статью, очень сильный и полезный материал. спасибо!
Спустя 3 года наткнулся на вашу статью и хоть по прежнему не изобретена телепортация и не так уж и много звездолетов бороздят космические просторы, но наша жизнь чуточку стала лучше и теперь появились storyboards. И при создании UIViewController посылается сообщение — (id)initWithCoder:(NSCoder*)aDecoder а
init
initWithNibName:
для программного создания UIViewController уже не катят.

И чтобы не освобождать в viewDidUnload оутлеты можно создавать их с помощью слабой ссылочки __weak IBOutlet UILabel* label; (аналогично в пропертях)

Вдруг кому пригодится.
Хорошая статья — жаль что устарела слегка ведь viewDidUnload использовать вообще не стоит (ибо работает не так как планируется, да и слава богу устарел с iOS 6) + теперь есть идеальное место теперь где view.frame соответствует реальному значению это viewWillLayoutSubviews
еще одно дополнение: возможны ситуации когда отработавший viewDidAppear не является поводом вызова viewWillDisappear при убирании view (конкретно при плавном изменении прозрачности этот метод не вызывается), да и здесь считают, что на парность этих функций рассчитывать не стоит.
Sign up to leave a comment.

Articles