Начало см. PC Week/RE, N 16/2004, c. 30
ГЕНЕАЛОГИЯ
Одной из особенностей операционной системы Mac OS X является крайне запутанная история ее развития, в результате чего эта система впитала в себя самые разные течения и подходы и, как следствие, обеспечила совместимость с родившимися в различных условиях программами и подсистемами.
История Mac OS X
Одним из основных прародителей Mac OS X явилась классическая система для компьютеров Apple, первая версия которой вышла еще в 1984 г. Эта система, первоначально называвшаяся просто System, а затем переименованная в Mac OS, совмещала перспективные и даже революционные по тем временам принципы (графический интерфейс пользователя, встроенную поддержку сетевых файловых сервисов и сетевой печати, многозадачность и др.) с вынужденными компромиссными решениями (многозадачность была кооперативной, отсутствовала защита памяти программ, механизмы распределения памяти между программами были не совсем автоматическими, допускался прямой доступ программ к оборудованию). Впрочем, причины компромисности классической Mac OS вполне понятны - ведь она должна была работать на очень слабых по сегодняшним меркам компьютерах (скажем, самый первый Макинтош снабжался ОЗУ объемом всего лишь 128 Кб, процессором частотой 2 МГц и к тому же не имел жесткого диска).
Однако к середине 1990-х компания Apple попыталась создать ОС следующего поколения, которая должна была соответствовать новым взглядам на "правильную операционную систему" при сохранении полной совместимости с написанными для ее предшественницы программами. Несколько лет труда разработчиков и реализация целого ряда независимых проектов показали, что удовлетворительно решить задачу не удается: система оказывалась либо слабо совместимой со старыми программами, либо страдала с точки зрения устойчивости.
В результате было принято кардинальное решение - перейти на принципиально новую архитектуру ОС, реализовав совместимость со старыми программами при помощи отдельной подсистемы, исполняемой как один из сервисов ОС.
В качестве основы этой будущей системы были выбраны разработки компании NeXT, активы которой и были приобретены в самом конце 1996 г.
История NeXT
Фирма NeXT Computer была создана в 1985 г. Стивом Джобсом после его ухода из основанной им Apple. Цель новой компании состояла в разработке совершенного во всех отношениях настольного компьютера - и именно поэтому в устройствах и ПО NeXT были реализованы многие самые перспективные идеи того времени, так что при разговоре о компьютерах NeXT постоянно приходится использовать слова "первый" и "впервые в мире" . В самой первой версии компьютеров NeXT Cube, появившейся в 1988 г., использовались сверхмощные по тем временам 32-разрядные 25 МГц процессоры Motorola 68030 (позже появились еще более скоростные модификации вплоть до 33 МГц Motorola 68040), в стандартной конфигурации имелся математический сопроцессор Motorola 68882 и даже встроенный DSP-процессор Motorola 56001; с 1990 г. стал поставляться компьютер с графическим адаптером NeX Tdimension на базе процессора i860, при помощи которого впервые в мире была реализована технология Display Post-script, когда экран компьютера (16 дюймов, 1120x832 точек) представлял собой по сути динамически обновляемый файл Postscript, да еще отрисовываемый в 32-разрядном цвете. NeXT Cube (опять-таки вперые в мире!) стандартно снабжались портами Ethernet и SCSI-контроллерами с внешними интерфейсами и были ориентированы на применение казавшихся тогда столь перспективными магнитооптических (МО) дисков. Стандартный объем ОЗУ составлял 8 Мб.
В 1990 г. вышла более компактная версия компьютера - NeXTstation: в ней МО-диски были заменены на более быстрые винчестеры (объемом от 100 Мб до 1,5 Гб), добавлен стандартный дисковод для 3,5-дюймовых гибких дисков и отсутствовали разъемы для плат расширения.
С точки зрения операционной системы платформа NeXT тоже представляла собой нечто выдающееся. Мы не будем подробно обсуждать ее мультимедийные возможности, комплектные программы и GUI, а остановимся лишь на ее основных подсистемах. Главное, что ее отличало, - это базирование на ядре UNIX (впервые в мире для настольных компьютеров), причем использовано было микроядро Mach.
Обсуждавшееся ранее ядро Darwin является близким потомком ядра NEXTSTEP, поэтому мы не будем повторяться в описании преимуществ UNIX-систем на базе микроядер.
В NEXTSTEP имелась еще одна очень интересная особенность - объектно-ориентированность. В отличие от всех коммерческих операционных систем, где объектная подсистема является надстройкой над набором гораздо более примитивных необъектных API, объекты NEXTSTEP пронизывали всю систему целиком, от уровня драйверов устройств до GUI, и, таким образом, объектное программирование для NeXT становилось совершенно естественным. Скажем, при создании драйвера нового устройства программист мог воспользоваться "обобщенным" драйвером (generic) того или иного типа, изменив лишь отдельные свойства и переопределив некоторые методы.
Так выглядел объявленный в 1988 г. NeXT Cube. Компьютер
укомплектован 17-дюймовым монитором (разрешение 1120_832
точки и четыре градации серого), снабженным динамиком и микрофоном
Подобно тому как для UNIX естественным языком программирования является Си с присущими ему библиотеками функций, прозрачно "перетекающими" в вызовы ОС, для NEXTSTEP абсолютно логично использование языка Objective-C и работа с иерархией объектов, тесно связанной с API этой системы. Более того, полноценное программирование на Objective-C вообще невозможно без объектных иерархий NEXTSTEP, поскольку любой объект в этом языке должен унаследовать отдельные свойства и методы от базового класса объекта - прародителя иерархии.
Несколько позже компания NeXT, испытывая серьезные проблемы с продажей компьютеров NeXTcube и NeXTstation, адаптировала свой объектный API (называвшийся тогда точно так же, как и система, - NEXTSTEP) для использования на других платформах. В результате родилось семейство продуктов OPENSTEP, в которое входили две полноценные операционные системы (для компьютеров NeXT и PC-совместимых) и доступная для нескольких аппаратных платформ объектная подсистема, исполняемая поверх стандартных для данных платформ ОС.
Более компактная модель, NeXTstation, вышла в 1990 г.
После приобретения активов NeXT компанией Apple объектный API был несколько расширен и переименован в Yellow Box. Аналогично изменились названия и уже существовавших к этому моменту продуктов семейства OPENSTEP для других платформ. И только при подготовке к анонсу Mac OS X название Yellow Box было изменено на Cocoa. На сегодня поддержка всех вариантов Yellow Box, кроме имеющегося в Mac OS X, свернута.
Cocoa Framework
Компания Apple предпочитает называть отдельные подсистемы, подобные Cocoa, термином framework (инфраструктура), избегая термина API. Это вполне объяснимо: framework является довольно сложной подсистемой, способной включать в себя кроме традиционных библиотек функций наборы классов, виртуальные машины, драйверы и массу других вещей. Однако мы будем использовать хотя и неточный, но более привычный термин API.
В процессе преобразования Yellow Box в Cocoa этот объектный API довольно сильно изменился. Прежде всего, он был несколько унифицирован, так что список базовых языков программирования для Cocoa заметно расширился: к Objective-С добавились С++ и Java. Сегодня уже Objective-С все более отходит на второй план, хотя полный отказ от него пока не представляется возможным (именно поэтому, кстати, Apple включает в Mac OS X модифицированный вариант компилятора gcc, поскольку стандартный его вариант с недавнего времени лишился поддержки Objective-C). Допускаются также и многие другие языки, например Си, Бейсик и Фортран, однако в силу их "необъектности" работа с ними на уровне системных вызовов несколько затруднена.
Приложения, построенные на базе API Cocoa, включают базовые блоки трех типов: frameworks (инфраструктуры), bundles (связки-пакеты) и наборы объектов интерфейса пользователя; мы далее все эти типы будем несколько некорректно называть "наборами классов". Любое из вновь создаваемых приложений может использовать наборы классов, разработанные для других приложений, а также применять классы, наследуемые из стандартных frameworks API Cocoa.
Основой Cocoa служат два набора классов: Foundation (Foundation.framework) и Application Kit (AppKit.framework), причем второй базируется на первом.
Foundation содержит служебные классы, необходимые для работы с операционной системой на низком уровне. Наиболее важны из них следующие:
- базовый корневой класс NSObject (как видите, в именах классов сохранились следы названия системы - прародителя Mac OS X);
- классы, представляющие данные различных типов (таких, как строки, числа или символьные массивы, при этом обеспечивается преобразование и форматирование данных);
- наборы классов, предназначенных для хранения других объектов (так называемые классы-контейнеры - списки, очереди, словари, динамические массивы и т. д.), включая методы для работы с ними (реализующие операции просмотра, поиска, обработки исключительных ситуаций и т. д.);
- классы, обеспечивающие доступ к функциям уровня ядра системы (управление задачами, портами, таймерами, потоками, блокировками и т. д.);
- классы, связанные с управлением самими объектами (удаленный вызов объектов, обмен объектами между процессами, синхронизация процессов, распределение памяти, сбор мусора и т. д.);
- классы, необходимые для реализации ввода-вывода (включая работу с файловыми системами, обработку URL, динамическую загрузку ресурсов и кода);
- служебные классы, представляющие системные параметры (дата, время, конфигурация и др.), базовые классы для реализации отката-наката, распределенной системы обмена сообщениями и т. д.
Большинство классов, входящих в Foundation, обеспечено развитой системой методов, отвечающих, в частности, за создание и уничтожение объектов, сбор мусора, работу с помещаемыми в контейнеры объектами и пр. Принятые при разработке этой иерархии объектов соглашения и особенности применяемых языков программирования (в первую очередь Objective-C) позволяют избежать многих неоднозначностей при написании программы, причем приветствуется весьма лаконичный стиль программирования.
С выходом в 1990 г. модели NeXTdimension в мир персональных
компьютеров пришел Display Postscript и графика глубиной цвета 32 бита
Классы AppKit служат для построения пользовательских интерфейсов и включают все средства, необходимые для создания управляемого событиями (event-driven) графического интерфейса. Всего в этот набор входит более сотни классов, однако не все они применяются пользователем непосредственно.
Кроме того, API Cocoa включает еще довольно много специализированных служебных наборов классов, отвечающих за исполнение сценариев, администрирование сетей и т. д.
Использование API Cocoa предполагает следование концепции "визуального" программирования, требующей помимо объектно-ориентированного подхода соблюдения еще нескольких принципов. Некоторые из них сегодня стали общепринятыми, другие остаются специфическими для Cocoa.
Подход модель - элемент интерфейса - элемент управления (Model - View - Controller, MVC). В рамках этого широко распространенного принципа определяются три группы объектов. Члены первой группы (модели) хранят данные и отвечают за логику их обработки. Вторая группа объектов (видимые элементы интерфейса) используется для отображения данных и создания интерфейса пользователя. Объекты третьей группы являются связующим звеном между первыми двумя, передавая моделям запросы, получаемые от элементов интерфейса, а элементам интерфейса - данные моделей, предназначенные для показа пользователю.
Подход целевого воздействия. С каждым элементом управления связан свой собственный экземпляр объекта, который и получает сообщения о любом действии пользователя над этим элементом.
Делегирование. При действии пользователя с управляющим элементом происходит вызов каких-то методов соответствующего данному элементу экземпляра объекта. Но однотипных управляющих элементов может быть много, и для каждого необходимо задавать свои собственные процедуры обработки событий. Делегирование, используемое в Cocoa (и соответственно в Objective-C), является специальной разновидностью наследования и позволяет наращивать или изменять функциональность того или иного объекта без описания для него дополнительного наследующего класса. Например, окно, работа с которым представляет собой несколько нестандартную процедуру, не требует создания нового класса (наследующего стандартное окно); все, что нужно, - это "подкрутить" стандартные методы в данном конкретном экземпляре (делегате) "стандартного" класса.
Objective-C
Мы уже отмечали, что между языком программирования Objective-C и API Cocoa (или, если угодно, Yellow Box) существует тесная связь. Многие широко применяемые элементы языка Objective-С по своей сути не принадлежат самому языку, а представляют собой некие необходимые для работы механизмов Objective-С базовые элементы иерархий классов API Cocoa, так что для эффективного использования Cocoa крайне желательно хотя бы поверхностно ознакомиться с Objective-С.
Для непосвященного Objective-С вообще может показаться весьма странным языком программирования. При внешней близости к Си и отчасти к С++ он почти на каждом шагу нарушает их принципы. Но на самом деле Objective-C - язык очень логичный, если его принимать "как он есть", без оглядки на собратьев.
Среди самых необычных вещей, присутствующих в этом языке, следует, наверное, отметить понятие категорий (Categories) и протоколов (Protocols).
Представьте ситуацию, когда вам необходимо слегка расширить уже существующий класс, добавив к нему несколько новых методов. В объектно-ориентированных системах один объект может "расширять" другой разными способами - он может встроить его в самого себя (в качестве объекта-члена) или стать его наследником. Правда, можно еще создать экземпляр-делегат с измененной функциональностью, но это не решает сформулированной выше задачи - создания класса-прототипа, чтобы на его базе затем получать новые классы и порождать экземпляры классов. Однако в Objective-C есть еще альтернативная возможность - на основе существующего класса построить так называемую категорию и определить для нее новые методы (или переопределить уже существующие). В результате в списке методов уже имеющегося класса появятся новые методы!
Применение категорий оправданно в нескольких случаях. В первую очередь это расширение классов, написанных другими разработчиками и недоступных в виде текстов. Кроме того, категории могут оказаться полезными при создании программы большой группой программистов или даже для реализации стратегии "инкрементной компиляции". Важен сам принцип: категории позволяют расширять функциональность класса, не меняя его описания.
Понятно, что реализовать работу механизма категорий нельзя без использования позднего (динамического) связывания, когда при каждом обращении к объекту-экземпляру происходит точная идентификация класса, к которому принадлежит объект (возможно, с учетом категорий), и вызываются соответствующие методы. Примерами динамического связывания могут служить механизмы наследования в VBA или механизм виртуальных функций С++. Язык Objective-C следует стратегии динамического связывания повсеместно, даже при явном указании класса, к которому принадлежит данный объект (иначе такой класс не поддавался бы расширению при помощи механизма категорий).
Еще одна интересная особенность Objective-C - применение так называемых протоколов.
Представьте, что вам надо описать вызов метода класса, описание которого либо еще не существует в природе (например, класс пока не разработан), либо оно в принципе недоступно (например, создаваемая процедура являетсякаким-то универсальным средством обработки разнотипных объектов, или в программе вызывается заранее неопределенный объект другой программы). Как вы можете это сделать? Только при помощи протоколов. Протокол - это список описаний методов, "оторванных" от конкретного класса. Протоколы могут быть неформальными и формальными.
Неформальные протоколы описывают методы как универсальные, применимые к любым объектам (и поэтому неконтролируемые на этапе выполнения программы). Можно сказать, что неформальные протоколы - это специальная разновидность категорий корневого класса иерархии API Cocoa NSObject.
Формальные протоколы позволяют гораздо аккуратнее задавать "область применимости" описанных в них методов к тем или иным классам. Каждый формальный протокол имеет уникальное имя, и при определении класса можно явно указать, что этот класс поддерживает данный протокол. В частности, иногда удобно создавать наборы классов, вообще лишенных собственных методов, но поддерживающих общие для всего семейства протоколы.
Практика показывает, что основное предназначение протоколов - выделение общих черт в классах, не входящих в общую иерархию (разумеется, за исключением ее части, объединяющей системные классы-прародители).
В процессе выполнения программы можно определить, поддерживает ли данный класс тот или иной протокол, и даже получить список всех поддерживаемых классом методов. Это позволяет строить универсальные процедуры, оперирующие совершенно разнородными объектами и адаптирующие свое поведение в зависимости от их природы.
Таким образом, своеобразная парадигма объектно-ориентированного программирования Objective-C хоть в какой-то степени реализует высказанные в недавней статье Эдуарда Пройдакова пожелания к ООП (PC Week/RE, N 6/2004, с. 42).
Если у вас проснулся интерес к API Cocoa, вы легко удовлетворите его на сайте компании Apple по адресу: http://developer.apple.com/documentation/Cocoa/Cocoa.html.
Окончание следует