Главная iPhone Mac OS X Форум О себе

Изучая пустоту

После введения в основы языка следует перейти к рабочему примеру программы и рука об руку разобрать устройство каждой строчки кода. Но меня так достало, что изучение каждого языка начинается с создания приложения “Hello World!”, и часто от разбора такого примера не приходит понимание как устроено приложение и как работает код. Поэтому я предлагаю начать с пустого проекта и разобрать каждую его строчку, благо в новом проекте для Mac OS строк много и по каждой есть что сказать. А это куда полезней знакомства с функциями и операторами, о которых можно прочесть в справочном руководстве.

Но прежде чем перейти к объяснениям, хочу сообщить плохую новость. Все что я писал в прошлом посте о сборщике мусора это хорошо, но ОС на iPhone не поддерживает garbage collector, поэтому придется забыть об удобствах и не забывать считать ссылки.

Итак, запустите XCode, создайте новый проект на основе шаблона “Window-Based Application” и назовите его Empty.

XCode new project widnow

Откроется окно проекта со списком используемых в нем файлов, ресурсов и фреймворков.

XCode project widnow

Давайте рассмотрим каждый из них.

Фреймворки

В нашем приложении будут использоваться два фреймворка: Foundation и UIKit. В зависимости от выбранного при создании шаблона проекта фреймворки и их количество будет меняться, но постоянно присутствовать будет один — Foundation. Как можно понять из названия, это основа любой программы. Эта библиотека содержит множество классов по работе с данными в ваших приложениях:

  • работа со строками — NSString, NSFormater, NSCharacterSet
  • работа с XML — NSXMLNode, NSXMLParser
  • работа с коллекциями — NSArray, NSDictionary, NSEnumerator
  • работа с файлами — NSStream, NSFileManager
  • работа с HTTPNSURL, NSURLRequest, NSURLConnection
  • работа с потоками — NSTask, NSThread, NSLock
  • и многие другие

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

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

  • работа приложения - UIApplicationMain
  • работа с изображениями — UIImage, UIImageView
  • работа с акселерометром - UIAccelerometer
  • элементы интерфейса — UIButton, UIToolbar, UILabel, UITextField
  • обработка событий — UITouch, UIEvent
  • и многие другие

Empty.app

Исполняемый файл нашего приложения прячется именно под этим неприглядным именем. Не пытайтесь запустить его на компьютере (интересно, сколько человек после моей фразы все-таки запустило файл :)), не заработает.

Info.plist

Этот XML-файл содержит служебную информацию о приложении: имя программы, имя автора, номер версии, название файла иконки и прочее. Часть этих полей доступна для редактирования через окно свойств приложения, вызываемое через раздел Targets.

Target Info

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

Info.plist

Расшифрую назначение некоторых ключей:

  • 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.

Info.plist

Каким образом происходит создание интерфейса и связей между объектами, а также созданием экземпляров новых классов в 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, являющееся экземпляром класса, объявление которого не приведено в данном файле. Здесь нужно сделать паузу и дать пояснение о том как устроено взаимоотношение между объектами в приложении.

Object class hierarchy

Наше приложение состоит из окна, экземпляра класса 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 — копированием переменной.
    
    // assign
    property = newValue;
    
    // retain
    if (property != newValue)
    {
        [property release];
        property = [newValue retain];
    }
    
    // copy
    if (property != newValue)
    {
        [property release];
        property = [newValue copy];
    }
    
    
    Для случая программирования без garbage collector (наш случай) нужно явно указывать assign, retain или 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.

Комментарий

Комментарии

motiv 28.07.2020 21:20

Ждем статью про IB=)

ответить
Evgeniy Krysanov 28.07.2020 21:23

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

ответить
motiv 29.07.2020 0:57

буду ждать, пока пару дней еще не будет мака(в отпуске, с работы необходимо взять) Можно ли с Вами как-то связатся? Моя ася: 3891794444 (одна четверка лишняя)

ответить
Evgeniy Krysanov 29.07.2020 9:24

Можно на ты (до Вы мне еще бороду отрастить нужно:)) Связаться со мной можно в любой момент - 362-069-440

ответить
Сергей 8.08.2020 13:00

Отличный материал для изучения! Как раз решил этим заняться :)

ответить
Val 21.09.2020 0:47

Открыл — создал — нажал Build — потестировал на симуляторе. А как теперь это хозяйство загрузить в iPod? Где само приложение? В папке проекта куча подпапок и файлов…

ответить
Evgeniy Krysanov 21.09.2020 0:59

Для этого требуется оплатить свое участие в iPhone Developer Program, то бишь добровольно расстаться с 99$.

ответить
Пьежа 20.10.2020 11:48

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

ответить
Merlin 22.11.2020 17:32

Обновил вчера 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;

Спасибо.

ответить
Evgeniy Krysanov 23.11.2020 23:20

Совершенно непринципиально где будет стоять модификатор IBOutlet, главное чтобы он был хоть в одном месте: или в объявлении переменной в блоке фигурных скобок, либо в объявлении свойства. Можно даже совмещать оба способа, ошибки не вызовет.

ответить
sakrist 30.01.2020 14:53

Молодец, вынес пару интересных вещей!

ответить
Alexander 4.02.2020 22:09

Классные статьи.

ответить
leshque 6.07.2020 19:24

картинок не видно =(

ответить
Evgeniy Krysanov 6.07.2020 21:49

Исправлено. Переезд на новый хостинг.

ответить
leshque 6.07.2020 22:47

спасибо.

ответить
AviSergey 6.11.2020 3:49

Отличная статья, для начала очень здорово помогает. Спасибо!)

ответить

Форма комментирования для «Изучая пустоту»

Обязательное поле. Не больше 30 символов.

Обязательное поле

captcha image Пожалуйста, введите символы, которые вы видите на изображении

.