Изучая пустоту
После введения в основы языка следует перейти к рабочему примеру программы и рука об руку разобрать устройство каждой строчки кода. Но меня так достало, что изучение каждого языка начинается с создания приложения “Hello World!”, и часто от разбора такого примера не приходит понимание как устроено приложение и как работает код. Поэтому я предлагаю начать с пустого проекта и разобрать каждую его строчку, благо в новом проекте для Mac OS строк много и по каждой есть что сказать. А это куда полезней знакомства с функциями и операторами, о которых можно прочесть в справочном руководстве.
Но прежде чем перейти к объяснениям, хочу сообщить плохую новость. Все что я писал в прошлом посте о сборщике мусора это хорошо, но ОС на iPhone не поддерживает garbage collector, поэтому придется забыть об удобствах и не забывать считать ссылки.
Итак, запустите XCode, создайте новый проект на основе шаблона “Window-Based Application” и назовите его Empty.
Откроется окно проекта со списком используемых в нем файлов, ресурсов и фреймворков.
Давайте рассмотрим каждый из них.
Фреймворки
В нашем приложении будут использоваться два фреймворка: Foundation и UIKit. В зависимости от выбранного при создании шаблона проекта фреймворки и их количество будет меняться, но постоянно присутствовать будет один — Foundation. Как можно понять из названия, это основа любой программы. Эта библиотека содержит множество классов по работе с данными в ваших приложениях:
- работа со строками — NSString, NSFormater, NSCharacterSet
- работа с XML — NSXMLNode, NSXMLParser
- работа с коллекциями — NSArray, NSDictionary, NSEnumerator
- работа с файлами — NSStream, NSFileManager
- работа с HTTP — NSURL, NSURLRequest, NSURLConnection
- работа с потоками — NSTask, NSThread, NSLock
- и многие другие
Это далеко не полное перечисление всех классов, входящих в состав Foundation. Можно сказать, что работа этого фреймворка скрыта от глаз пользователей, но именно на этих классах строится сердце любой программы.
UIKit, в свою очередь, содержит набор классов отвечающих за отображение элементов пользовательского интерфейса и обработку событий в нашей программе:
- работа приложения - UIApplicationMain
- работа с изображениями — UIImage, UIImageView
- работа с акселерометром - UIAccelerometer
- элементы интерфейса — UIButton, UIToolbar, UILabel, UITextField
- обработка событий — UITouch, UIEvent
- и многие другие
Empty.app
Исполняемый файл нашего приложения прячется именно под этим неприглядным именем. Не пытайтесь запустить его на компьютере (интересно, сколько человек после моей фразы все-таки запустило файл :)), не заработает.
Info.plist
Этот XML-файл содержит служебную информацию о приложении: имя программы, имя автора, номер версии, название файла иконки и прочее. Часть этих полей доступна для редактирования через окно свойств приложения, вызываемое через раздел Targets.
Другие настройки, а также добавление специальной информации придется выполнять вручную через удобный встроенный редактор XML-кода.
Расшифрую назначение некоторых ключей:
- CFBundleDevelopmentRegion — этот ключ определяет язык региона разработчика, и по умолчанию также является языком приложения. Если для пользовательских настроек не будет найден соответствующий файл локализации, то в качестве основного языка принимается это значение. Ключу может быть присвоена строка в произвольном формате: english, en и пр; при создании файлов локализации нужно использовать соответствующие выбранному формату имена (о них в следующих постах)
- CFBundleName — короткое название приложения, длиной не более 16 символов. Шаблон ${PRODUCT_NAME} берет значение PRODUCT_NAME из настроек проекта
- CFBundleDisplayName — полное название приложения
- CFBundleExecutable — название исполняемого файла. Шаблон ${EXECUTABLE_NAME} берет значение EXECUTABLE_NAME из настроек проекта
- CFBundleIconFile — имя файла иконки без расширения .icns
- CFBundleInfoDictionaryVersion — определяет версию формата файла plist (не меняйте это значение)
- CFBundleIdentifier — уникальный строковый идентификатор вашего приложения. Идентификатор использует систему обратной доменной записи. Например, com.apple.quicktime-movie, public.html. Домен (com, public и др.) используются для идентификации позиции в доменной иерархии, но не предполагает под собой группировку схожих по функциональности или другим признакам объектов. Это означает, что вы можете использовать любую схему именования, придерживаясь лишь нескольких исключений (public и dyn домены не использовать). Обычно, идентификаторы для компаний начинаются с домена com и имеют следующий вид: com.company.app. Предлагаю придерживаться этой схеме и давать своим приложениям идентификатор согласно этого формата. Например, для нашего проекта значение CFBundleIdentifier будет com.pyobjc.empty
- CFBundlePackageType — четырехбуквенный код определяющий тип нашей сборки: APPL — приложение, FMWK — фреймворк и пр. По умолчанию APPL
- CFBundleVersion — номер сборки (build version number). Может содержать цифры, разделенные точкой, так и буквы. Например, 0.11.5d
- CFBundleShortVersionString — версия вашего приложения. Обычно строка, состоящая из трех цифр, разделенных точкой, например, 1.0.3. Этот ключ по умолчанию не определен
Вы также можете по ходу работы своей программы создавать свои property list файлы, для хранения небольшой по объему порции информации в качестве конфигурационных настроек или для сохранения состояния программы между запусками.
Empty_Prefix.pch
Этот заголовочный файл подключается ко всем вашим файлам в проекте. Это все равно, что иметь строчку #import <Empty_Prefix.pch> в начале каждого файла.
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#endif
MainWindow.xib
Данный файл содержит информацию об интерфейсе программы, о связях между объектами, а также инициализует некоторые из них. Это больше чем простое описание элементов и их взаимного расположения на форме. Интерфейс описывает еще и создание экземпляров объектов и связи между ними при загрузке формы в программу. Приложение может содержать несколько nib-файлов (xib и nib одно и то же). Редактирование файла производится с помощью приложения Interface Builder, входящего в состав XCode.
Каким образом происходит создание интерфейса и связей между объектами, а также созданием экземпляров новых классов в Interface Builder я расскажу в своих ближайших постах.
Точка входа
Главный файл нашего приложения — main.m. После старта программы все управление передается в этот файл. Разберемся что он делает.
#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
В первой строчке мы импортируем заголовочный файл фреймворка UIKit, чтобы получить гарантированный доступ к функции UIApplicationMain, используемой в теле программы, даже в случае отсутствия подключения данного фреймворка в файле Empty_Prefix.pch.
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Помните в прошлом посте я говорил об autorelease? Все объекты после вызова autorelease попадают в AutoreleasePool. Как только приходит время: заканчивает работу функция, где был вызван autorelease, или программа — всем объектам, содержащимся в этом пуле, будет отправлено сообщение release. Как раз в этой строке происходит инициализация данного пула.
Я бы удивлен, что нигде дальше по ходу программы переменная pool явно не участвует и не передается, а документация и интернет по этому вопросу молчит. Судя по тем скупым сведениям в описании класса, что я нашел, Cocoa ожидает, что объект NSAutoreleasePool всегда присутствует в программе в главном потоке. Я предполагаю, что созданный объект система находит сама и использует его в процессе.
int retVal = UIApplicationMain(argc, argv, nil, nil);
В этом месте стартует главный цикл нашей программы. Функция UIApplicationMain создает синглтон класса UIApplication. Этот объект ответственнен за обработку событий в вашем приложении: он обрабатывает поступающие сообщения и передает их соответствующему объекту. Кроме того, UIApplication содержит список всех открытых окон в приложении. Обычно UIApplication не наследуется напрямую, а делегирует свои полномочия другому классу через протокол UIApplicationDelegate, который должен описать реализацию одного или нескольких методов. И уже через него осуществляет передачу сообщений о происходящий событиях: будь то запуск приложения, предупреждения о недостатке памяти или прерывание работы программы.
В дополнение к параметрам argc и argv, вызов UIApplicationMain включает два строковых параметра-названия классов: имя главного класса, дочернего от UIApplication, и имя делегата, которому будет передано управление приложением. Если первый параметр равен nil, UIKit полагает что это значение UIApplication. Если второй параметр указан как nil, то указание делегата должно происходить в nib-файле.
При старте UIApplicationMain читает файл Info.plist, получает имя файла интерфейса, хранящегося в переменной NSMainNibFile. Nib файл хранит набор элементов пользовательского интерфейса, которые используется в программе, а также ряд других объектов. В нашем Info.plist значение переменной NSMainNibFile равно MainWindow. UIApplicationMain загружается интерфейс нашей программы и все связи, что в нем описаны.
В последних двух строках main.m происходит освобождение объекта NSAutoreleasePool и выход из программы с кодом возврата.
Следующий файл для рассмотрения это целый класс — EmptyAppDelegate. Начнем с заголовочного файла.
EmptyAppDelegate.h
#import <UIKit/UIKit.h>
@class EmptyViewController;
@interface EmptyAppDelegate : NSObject <UIApplicationDelegate> {
IBOutlet UIWindow *window;
}
@property (nonatomic, retain) UIWindow *window;
@end
Я буду описывать только те строки, что новы для нас и вызывают вопросы.
@class EmptyViewController;
Данная строка дает понять компилятору, что в коде будет использовано имя EmptyViewController, являющееся экземпляром класса, объявление которого не приведено в данном файле. Здесь нужно сделать паузу и дать пояснение о том как устроено взаимоотношение между объектами в приложении.
Наше приложение состоит из окна, экземпляра класса UIWindow. Обычно, окно в приложении для iPhone только одно, хотя не запрещено делать их несколько. Например, сама система использует несколько окон, отображая статусбар, предупреждения в отдельных окнах, но вам все же следует при необходимости воспользоваться возможностями UIKit, например, UIAlertView, вместо того, чтобы городить свои окна. Также нет смысла делать окно не во весь экран.
Стандартная инициализация окна выглядит следующим образом:
UIWindow* aWindow = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
Но при создании экземпляра UIWindow через Interface Builder этот код скрывается от программиста в nib-файле.
Окно может содержать от одного до нескольких View, экземпляров UIView, которые могут занимать как всю площадь окна, так и отдельные части. Кроме того, сами View могут содержать множество других экземпляров UIView. Родительский объект в этой иерархии зовется superview, а все его дети — subview. Subview определяет прямоугольный регион и несет ответственность за перерисовку всех элементов в нем, а также за обработку событий, относящихся к области его владения.
UIWindow является потомком UIView, но все же вы не должны манипулировать свойствами отвечающими за отображение UIWindow напрямую, а только через дочерние UIView.
UIViewController, экземпляром которого судя по названию является EmptyViewController (он нигде явно не объявлен, так как его инициализация и использование в программе ложится на наши плечи), занимается обработкой событий, таких как анимация при смене View, изменение View при повороте iPhone и прочие вещи. UIViewController содержит ссылку на View, которым управляет. Подробно его работу здесь не рассматриваю, так как он в нашей программе не используется.
@interface EmptyAppDelegate : NSObject <UIApplicationDelegate>
В этой строке идет обычное объявление класса, производного от NSObject, с одним различием — класс EmptyAppDelegate реализует протокол UIApplicationDelegate.
IBOutlet UIWindow *window;
Переменная window, объявленная как Interface Builder Outlet, имеет связь с объектом пользовательского интерфейса. С помощью такого соединения вы можете посылать сообщения непосредственно элементу пользовательского интерфейса, с которым связана данная переменная. Например, вы можете изменить его положение, разрешить или запретить доступ к нему, или вообще убрать из видимости.
Представьте, что переменная text является объектом UITextField, описывающего текстовое поле. С помощью IB можно связать данную переменную с элементом TextField в окне приложения. Таким образом, все изменения что вы делаете с переменной в вашем классе будут сразу отображаться в поле ввода.
Подобным же образом связываются методы, но через IBAction, но об этом позже.
@property (nonatomic, retain) UIWindow *window;
А тут мы определяем свойство window. Но коробят непонятные параметры в скобках. Давайте разберемся.
Эти параметры определяют свойство и указывает как оно будет доступно, т.е. как будут работать set- и get-методы. Возможные модификаторы:
- nonatomic — atomic свойства обеспечивают надежный доступ к переменным, но при частых обращениях к ним отрицательно сказываются на производительности программы в целом (при работе без сборщика мусора; с garbage collector таких “тормозов” не наблюдается). Поэтому следует всегда использовать nonatomic атрибут, кроме случаев создания многопоточных приложений, где надежность доступа к значениям переменных важнее скорости работы с ними.
- getter=getterName, setter=setterName — используйте эти атрибуты, чтобы задать индивидуальные имена set- и get-методам
- readonly — свойство будет доступно только для чтения (будет отсутствовать set-метод)
- readwrite — по умолчанию, если не указано readonly, свойство доступно как для записи значений, так и для чтения
- assign, retain, copy — определяет способ получения значения в set-методе из передаваемого параметра: assign — простое присвоение, retain — получением ссылки, copy — копированием переменной.
Для случая программирования без garbage collector (наш случай) нужно явно указывать assign, retain или copy атрибут у свойств.// assign property = newValue; // retain if (property != newValue) { [property release]; property = [newValue retain]; } // copy if (property != newValue) { [property release]; property = [newValue copy]; }
EmptyAppDelegate.m
Последний файл, рассматриваемый нами, содержит реализацию класса EmptyAppDelegate.
#import "EmptyAppDelegate.h"
@implementation EmptyAppDelegate
@synthesize window;
— (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
[window makeKeyAndVisible];
}
— (void)dealloc {
[window release];
[super dealloc];
}
@end
Код не требует объяснения, кроме двух строк.
— (void)applicationDidFinishLaunching:(UIApplication *)application
Этот метод запускается сразу после старта приложения. Это идеальное место для инициализации переменных и восстановления предыдущего состояния приложения. В нашем классе реализация метода занимает одну строчку
[window makeKeyAndVisible];
в которой созданный в Interface Builder объект класса UIWindow получает “фокус” и становится главным окном приложения.
Command+R
Пожалуй на этом я закончу свои объяснения. Если что-то осталось непонятным, то спрашивайте в комментариях, иначе - Command+R.
Комментарии
Ждем статью про IB=)
Неприменно напишу, правда, следующий пост будет немного разгрузочным, пока разбираюсь в деталях.
буду ждать, пока пару дней еще не будет мака(в отпуске, с работы необходимо взять) Можно ли с Вами как-то связатся? Моя ася: 3891794444 (одна четверка лишняя)
Можно на ты (до Вы мне еще бороду отрастить нужно:)) Связаться со мной можно в любой момент - 362-069-440
Отличный материал для изучения! Как раз решил этим заняться :)
Открыл — создал — нажал Build — потестировал на симуляторе. А как теперь это хозяйство загрузить в iPod? Где само приложение? В папке проекта куча подпапок и файлов…
Для этого требуется оплатить свое участие в iPhone Developer Program, то бишь добровольно расстаться с 99$.
Вот это да! Уже две недели бьюсь головой об стену, пытаясь хоть чуть-чуть распетрать буржуйскую документацию от яблок, а тут нахожу этот блог, где все на рашшине и даже с картинками! Ух! Весьма и весьма благодарен! Ну ващще!
Обновил вчера SDK до версии 2.2 и заметил, что немного изменилось формирование дефолтового проекта. А именно параметр IBOutlet при объявлении переменной window почему-то перекочевал в @property… Это теперь новые правила объявления или я что-то где-то накосячил? )
–––— SDK 2.1
@interface EmptyAppDelegate : NSObject < UIApplicationDelegate > { IBOutlet UIWindow *window; }
@property (nonatomic, retain) UIWindow *window;
–––— SDK 2.2
@interface EmptyAppDelegate : NSObject < UIApplicationDelegate > { UIWindow *window; }
@property (nonatomic, retain) IBOutlet UIWindow *window;
Спасибо.
Совершенно непринципиально где будет стоять модификатор IBOutlet, главное чтобы он был хоть в одном месте: или в объявлении переменной в блоке фигурных скобок, либо в объявлении свойства. Можно даже совмещать оба способа, ошибки не вызовет.
Молодец, вынес пару интересных вещей!
Классные статьи.
картинок не видно =(
Исправлено. Переезд на новый хостинг.
спасибо.
Отличная статья, для начала очень здорово помогает. Спасибо!)
Форма комментирования для «Изучая пустоту»