Objective c что это
Перейти к содержимому

Objective c что это

Зачем учить Objective-C в 2022 году: умелое управление памятью и другие операции на низком уровне

Мобильные разработчики в КРОК решают реальные проблемы заказчиков и конечных пользователей: создают приложения для удалённой проверки остроты зрения, интеграции с умными очками для полиции Дубая или доступа к корпоративной социальной сети. В этой статье команда мобильной разработки рассказала, почему Objective-C до сих пор жив и зачем его учить в 2022 году.

Глубокое понимание процессов

Большая часть внутреннего кода macOS, XCode и ядра iOS написаны на C и С++. Objective-C очень хорошо с ними «стыкуется», потому что формально это не язык программирования, а большой препроцессор к чистому Cи.

Поверх Objective-C строится вся экосистема Apple. Тот же Swift во многом написан поверх него, и чтобы понимать, как система работает изнутри и почему Swift выглядит так, как выглядит, и работает так, как работает, «базу» полезно знать.

Например, Objective-C помогает понять, что не все NSProxy это NSObject и в чём разница между Int, NSInteger и NSNumbe. А также за счёт чего работает Swizzling, что такое Selector, как работает responder chain и так далее. В Swift это сильно инкапсулировано и абстрагировано, а потому не очень хорошо видно.

Кроме того, Objective-C, как и любой Cи-язык, помогает разобраться в том, как работают ссылки, указатели и память в принципе. В нём приходится работать со ссылками напрямую, поэтому достаточно написать 1-2 небольших проекта, чтобы понять тему.

Плюсы на собеседовании

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

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

Objective-C до сих пор используется в разработке

Во-первых, кажется, у любого проекта, который существует больше 2-3 лет, часть кодовой базы написана на Objective-C. Это может быть хоть скрытый слой, который вынесен в зависимость и не особо стыкуется с общей кодовой базой — но он есть и с ним периодически нужно работать: править баги или добавлять фичи.

Во-вторых, пока некоторые приложения не получается переписать на Swift — это требует времени и денег, которые компания или заказчик не готовы тратить. Поэтому база Objective-C разрастается.

В-третьих, если необходима сложная работа с сетью, памятью, ресурсами устройства, то приходится использовать языки с более низким уровнем абстракции, чем у Swift. Это могут быть Objective-C, Objective-C++ или просто C и С++ — в зависимости от того, насколько низкий уровень нам нужен.

Подобное встречается довольно редко, например в работе с мессенджерами (Telegram), потоковым вещанием (Zoom) или видеосервисами (Кинопоиск). В таких случаях тоже можно абстрагироваться и либо вынести часть логики в отдельную зависимость, либо перенести рендеринг или всю логику пакетирования данных на бэкенд. Но не всегда.

Некоторые вещи сложнее делать на Swift, чем на Objective-C

Иногда решать задачи на Swift долго и неудобно — проще воспользоваться костылями, которые давно есть в Objective-C.

Objective-C с нуля

У любого желающего писать программы для продукции фирмы Apple в жизни наступает такой момент, когда ему приходиться изучить новый язык программирования — Objective-C. Когда-то этот счастливый момент постиг и меня. А чтобы лучше запомнить основные особенности этого языка, решил законспектировать свои мысли во время осмысления его документации, которыми и делюсь с вами.

Банальная теория возникновения ООП

Проблема повторного использования написанного кода и его переносимость постоянно заставляет программистов искать все новые пути его упорядочивания, структуризации и абстрагирования. Для решения этих проблем создаются новые парадигмы программирования, шаблоны проектирования, новые языки, компиляторы и стандартные библиотеки к ним, программные платформы и фреймворки. Так образовались парадигма подпрограмм (процедур), реализуемая при помощи процессорных команд CALL\RET и стека (по сути, перенос потока выполнения по адресу произвольной, а не следующей за текущей команды, с последующим возвратом). Затем, парадигма модулей (каждый файл – отдельная единица трансляции), породившая двухэтапную трансляцию: компиляция модулей, а затем их компоновка (статическая или динамическая) в исполняемый модуль.

В следствии увеличения объема кода в проектах и сложностей его поддержки, с 1960х начинает образовываться новая, объектно-ориентированная парадигма программирования, разбившая программы на еще более мелкие составляющие – типы данных. Ее суть заключается во взаимодействии сущностей (объектов) посредством посылки друг другу сообщений. Каждый объект является переменной определенного программистом типа данных (так называемого класса). Определение такого специального пользовательского типа данных (класса) заключается в двух вещах: определении набора данных (инвариантов, членов) и набора подпрограмм (методов), которые будут их обслуживать.

Класс обычно оформляется как определенный программистом тип, основанный на встроенных (языковых) типах данных и\или других классах. Для языка С, не поддерживающего объектно-ориентированную парадигму, это может быть структура (struct). А набор подпрограмм реализуется как обычные функции, обязательно принимающие как минимум один параметр — указатель на набор данных, подлежащих обработке.

Основным преимуществом объектно-ориентированного подхода стала возможность создавать новые классы на основе уже написанных (добавлять инварианты и методы, переопределять методы, использовать определенные в базовом классе методы как свои), названное наследованием.

Набор методов представляет собой интерфейс для взаимодействия с инвариантами. Невозможность непосредственной модификации данных класса (без задействования его интерфейса) отражает принцип инкапсуляции. На рисунке показан класс и его объекты. Имеется инвариант x типа float и к нему интерфейс (метод) doubleX, возвращающий значение инварианта.

Бывает, что необходимо послать сообщение объекту, который на него определенно отвечает (т.е. вызвать для объекта класса такой метод, который он реализовал), но, по ситуации, конкретный класс этого объекта неизвестен. Например, каждому элементу списка указателей на объекты класса Auto нужно послать сообщение Move, а известно что в списке находятся указатели на объекты не только класса Auto, но также и указатели на производные (наследованные) классы Ford и Subaru. Это возможно сделать только благодаря принципу полиморфизма, заключающегося в том, что при посылке определенного сообщения объекту из некой иерархии классов, в которой все объекты способны принять такое сообщение, этот объект реагирует на него соответственно своему, а не базовому для данной иерархии классу.

Первым языком с поддержкой объектно-ориентированного подхода стал Simula67. Затем появился Smalltalk. А в 80х начал оформляться С++ — основной язык современного системного программирования. Его расширение и усовершенствование в 90х породило ряд парадигм и шаблонов проектирования, и оказало необратимое влияние на современное видение объектно-ориентированного подхода, в том числе, и на язык Objective-C.

Чуть-чуть истории

Objective-C возник в 80-x как модификация С в сторону Smalltalk. Причем модификация эта состояла в добавлении новых синтаксических конструкций и специальном препроцессоре для них (который, проходя по коду преобразовывал их в обычные вызовы функций С), а также библиотеке времени выполнения (эти вызовы обрабатывающей). Таким образом, изначально Objective-C воспринимался как надстройка над C. В каком-то смысле это так и до сих пор: можно написать программу на чистом С, а после добавить к ней немного конструкций из Objective-C (при необходимости), или же наоборот, свободно пользоваться С в программах на Objective-C. Кроме того, все это касается и программ на С++. В 1988 NeXT (а в последствии Apple) лицензировала Objective-C и написала для него компилятор и стандартную библиотеку (по сути SDK). В 1992 к усовершенствованию языка и компилятора подключились разработчики проекта GNU в рамках проекта OpenStep. С тех пор GCC поддерживает Objective-C. После покупки NeXT, Apple взяля их SDK (компилятор, библиотеки, IDE) за основу для своих дальнейших разработок. IDE для кода назвали Xcode, а для GUI – Interface Builder. Фреймворк Cocoa для GUI разработок (и не только) на сегодня является наиболее значимой средой разработки программ на Objective-C.

Особенности Objective-C

  • interface Начинает объявление класса или категории (категория – расширение класса дополнительными методами без наследования)
  • @implementation Начинает определение класса или категории
  • @protocol Начинает объявление протокола (аналог класса С++, состоящего из чисто виртуальных функций)
  • end Завершает объявление\определение любого класса, категории или протокола
  • @private Ограничивает область видимости инвариантов класса методами класса (аналогично С++)
  • protected Стоит по умолчанию. Ограничивает область видимости инвариантов класса методами класса и методами производных классов (аналогично С++)
  • @public Удаляет ограничения на облать видимости (аналогично С++)
  • try Определяет блок с возможной генерацией исключений (аналогично С++)
  • @throw Генерирует объект-исключение (аналогично С++)
  • catch () Обрабатывает исключение, сгенерированное в предшествующем блоке try (аналогично С++)
  • finally Определяет блок после блока try, в который предается куправление независимо от того, было или нет сгенерировано исключение
  • @class Сокращенная форма объявления класса (только имя (аналогично С++))
  • selector(method_name) Возвращает скомпилированный селектор для имени метода method_name
  • @protocol(protocol_name) Ворзвращает экземпляр класса-протокола с именем protocol_name
  • @encode(type_spec) Инициализирует строку символов, которая будет использована для шифрования данных типа type_spec
  • @synchronized() Определяет блок кода, выполняющегося только одной нитью в любой определенный момент времени

Обмен сообщениями

Чтобы заставить объект выполнить какой-нибудь метод нужно послать ему сообщение, именуемое так же, как и требуемый метод. Такое сообщение называется селектор метода. Синтаксис посылки таков:

В сообщении можно передавать параметры для вызываемого метода:

Перед каждым параметром необходимо ставить двоеточие. Сколько двоеточий – столько и параметров. Имя метода может продолжаться после каждого такого двоеточия-параметра:

Методы с неограниченным количством аргументов вызываюся следующим синтаксисом:

Посылка сообщения, как и любая функция C, возвращает определенное (может void) значение:

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

Как работает передача сообщений

Посылка сообщения транслируется в С-функцию с прототипом:

Тип SEL, по сути, определен как char const *, но лучше воспринимать его как int, поскольку во время выполнения все селекторы индексируются целыми значениями согласно глобальной таблице селекторов.

Пользуясь инвариантом isa объекта receiver (при использовании фреймворка Foundation, базового для Cocoa, все классы должны наследовать класс NSObject, поэтому наличие isa неизбежно), эта функция просматривает локальный список селекторов класса с целью определить, отвечает ли объект данного класса на сообщение method. Если такой селектор находится, то управление передается соответствующему методу класса, которому передается id объекта (указатель на его инварианты) и указанные после селектора параметры функции objc_msgSend(). Значение, возвращенное методом, отдается как результат посылки сообщения. Если у объекта-приемника данный селектор отсутствует, функции objc_msgSend() просматривает список селекторов его базового класса.

При такой схеме вызов, например:

Так как в глобальной таблице селекторов 12 соответствует строке “addObject:”. Далее функция objc_msgSend() выполняет поиск по списку селекторов объекта receiver и, найдя его (пусть это объект класса NSArray, который реализовал метод с селектором 12), производит вызов типа:

Объявление метода

Интересно отметить, что прототип метода addObject из предыдущего раздела в объявлении класса выглядел так:

То есть принимал всего один параметр. Но, исходя из принципа объектно-ориентированной парадигмы, что методы – это подпрограммы, обрабатывающие определенные наборы данных, методу необходимо передавать адресс данных, подлежащих обработке. Поэтому такой параметр передается во всякий метод класса неявно. Компилятору об этом дополнительном параметре дает понять минус («-«), стоящий первым в прототипе метода. Такой метод (с минусом впереди) называется методом объекта (или экземпляра), т.к. может быть вызван только для объекта какого-нибудь класса. В теле метода этот указатель на экземпляр данных (или адрес объекта, которому послали сообщение) доступен посредством зарезервированного слова self (аналог this в С++), а указатель на экземпляр базового класса – через зарезервированное слово super. Кроме того, в метод объекта также передается неявный параметр _cmd – селектор этого метода из глобальной таблицы селекторов. С точки зрения программиста С++ все методы объектов в Objective-C как-будто объявлены с ключевым словом virtual, и всегда следуют динамическому полиморфизму.

Если в начале прототипа метода поставить знак плюс (“+”), то такой метод будет считаться методом класса, и, естественно, не будет принимать неявный параметр self (это аналогично объявлению static-метода в С++). А без инварианта isa объекта, на который указывает self, указатель super работать, конечно, тоже не будет.
Таким образом, прототип любого метода объявляется так:

Если метод возвращает некий объект (тип id) или класс (тип Class), можно воспользоваться вложенным синтаксисом вызова:

Здесь объекту класса UILabel из фреймворка UIKit устанавливается значение инварианта text равное строке @”Hello world”. Эта строка, в свою очередь, образована конкатенацией строк @”Hello” и @” world”. Первая является результатом посылке сообщения stringWithString классу NSString с параметром-константой @”Hello”. Такой вызов возвращает объект класса NSString, инициализированный строкой-параметром. Затем этому объекту посылается сообщение stringByAppendingString с параметром @” world”. Результат посылки этого сообщения и есть объект класса NSString, содержащий конкатенацию значения объекта-приемника и строкового аргумента. Этот объект и попадает как параметр в сообщение setText: объекта myLabel.

Объявление класса

Объявим простой класс комплексного числа в файле Complex.h:

Как видим, все объявление заключено в ключевые слова interface и end. Первым делом объявляются инварианты (в фигурных скобках). Вне фигурных скобок объявляются методы. Метод description отсутствует в объявлении класса не случайно. Дело в том, что он, как и метод dealloc и init, присутствует в определении класса. При посылке объекту класса Complex сообщения description будет рассмотрен его локальный список селекторов, куда, после компиляции, попадут селекторы всех методов, реализованных классом этого объекта, даже не объявленные в интерфейсной части. То есть init, description и dealloc будут вызывать абсолютно корректно.

Создание объектов

В связи с тем, что все объекты распределяютя в динамической памяти, cоздание объекта приходится проводить в два этапа: 1) выделении памяти (сообщение alloc) и 2) инициализация инвариантов (конструкторы класса).

После создания объекта им можно смело пользоваться:

Некоторые классы обладают методом для быстрого (в один этап) создания собственных экземпляров. Такие методы являются методами класса, возвращают указатель на объект своего класса и их имя обычно начинается с названия самого класса. Например метод:

Возвращает уже готовую строку, инициализированную соответствующей сторокой с завершающим нулем, без вызовов alloc и init:

Время жизни объекта

Как только указатель на объект выходит за свою область видимости, память, выделенная под него, безвозвратно теряется (если, конечно, это был последний указатель на тот объект) и происходит утечка. Дабы избежать таких нежелательных последствий в Objective-C поддерживается парадигма подсчета ссылок на ресурсы. Таким образом, у каждого объекта есть целочисленный счетчик, который показывает количество ссылающихся на него указателей. По достижению этим счетчиком нуля, память, выделенная для данного объекта, возвращается системе. После вызова метода класса alloc, этот счетчик равен единице. Чтобы увеличить его значение необходимо послать объекту сообщение retain, а чтобы уменьшить – release. Все эти методу реализует NSObject, который любой наш класс непременно наследует. Интересно отметить, что значение счетчика для статических объектов класса NSString (например @”I am a string”) равно -1, то есть максимально возможное. Вот пример работы со счетчиком:

Реализация init очень важна. Это конструктор класса. Конструкторы отличаются тем, что возвращаеют id и их названия всегда начинается со слова init, а конструктор по умолчанию – это и есть просто init. Схема любого конструктора примерно следующая:

Вот типичный специализированный (не по умолчанию) конструктор для класса с двумя членами типа некоторого класса и одним целочисленным инвариантом:

Реализация release и retain для NSObject идеологически примерно следующая, и ее не нужно переопределять в производных классах, в силу отсутствия доступа к инварианту счетчика ссылок:

То есть самому объекту посылается сообщение dealloc, в реализации метода которого он может, по необходимости, уменьшить счетчики своих инвариантов и передать аналогичное сообщение объекту базового класса, чтобы он сделал то же самое. Очевидно, реализация метода dealloc для NSObject освободит память, выделенную объекту. Обычно dealloc для какого-нибудь класса выглядит так:

Методы доступа

Правильная работа с подсчетом ссылок очень важна при возврате адреса объекта из метода или инициализации инварианта формальным параметром. Обычно такими вещами занимаются так называемые методы доступа, возвращающие и устанавливающие инварианты объектов. Принято именовать метод, возвращающий значение инварианта, так же как и инвариант, а имя метода, устанавливающего его значение, начинать со слова set:

Так как инвариант _re относится ко встроенному типу, никаких сложностей с изменением его значения не возникает. Но если инвариант – объект некоторого класса – то простым присваиванием не обойтись, ведь надо учитывать счетчики ссылок. Для решения этой проблемы применяются следующие три метода:

Вариант №3 не очень удачный потому, что засоряет текущий самовыгружаемый пул, а обычно это не очень желательно (см. следующий раздел).
Метод доступа для чтения значения инварианта всегда очень прост:

Самовыгружаемый пул в нитях программы

Теперь попробуем вернуть из метода созданный внутри него объект:

Строка формата соответствует стандарту языка С. Но если в ней необходимо указать тип id, то используется спецификатор формата %@. Каким образом метод, разбирающий формат, понимает какие символы подставить вместь id? Он просто подставит то, что вернет метод описания description данного объекта. Этот метод изначально объявлен для класса NSObject. NSString переопределил его на вывод своего строкового содержания. Переопределив его, любой объект может представлять свое строковое содержание. Например, так это может сделать класс комплексного числа с двумя инвариантами типа double:

После выполнения метода sayHelloToName:withSurname: определенно произойдет утечка памяти, так как вызывающий код скорей всего не догадывается, что возвращенному объекту нужно после обработки послать сообщение release. Даже если он догадается это сделать, возможно, что возвращался указатель на инвариант объекта, а значит его уничтожение чревато серьезными последствиями. Хотелось бы иметь механизм самоосвобождения объектов когда либо в будующем, чтобы пользовательский код вообще не думал об их освобождении. Решается эта проблема с помощью объекта класса NSAutoreleasePool – самовыгружаемого пула объектов.

После создания объекта такого класса всем объектам, созданным после него, можно послать сообщения autorelease. При этом данный объект помещается в текущий (последний созданный) самовыгружаемый пул. Когда некий пул получит сообщение release, то он отошлет такое же сообщение и всем своим объектам, уменьшая их счетчик ссылок (по сути, уничтожая). Таким образом. Объект, помещенный в самовыгружаемый пул, продолжает жить и занимать память во все время жизни пула. Это удобно для небольших временных объектов, но может с течением времени занять значительную часть памяти. Потому рекомендуется циклы, способные порождать большое количество временных объектов, которые отправляются в самовыгружаемый пул, обрамлять локальными (вложенными) пулами.

Любая нить в программе, использующей Cocoa, должна создавать объект класса NSAutoreleasePool в самом начале (прежде создания других объектов), и в самом конце его уничтожать (после уничтожения всех других объектов). Функция main(), являющаяся главной нитью любой программы на Objective-C, при использовании фреймворка Cocoa должна всегда выглядеть вот так:

А корректный метод sayHelloToName:withSurname: теперь будет выглядеть вот так:

К слову, метод drain самовыгружаемого пула аналогичен release с той лишь разницей, что, кроме освобождения себя самомго и всех содержащихся объектов, еще дает подсказку сборщику мусора вступить в игру. Однако, это актуально только для Mac OS 10.4 и выше, так как на iOS сборки мусора нет.

Определение класса

Теперь рассмотрим файл Complex.m с определением методов класса Complex:

Конструктор по умолчанию вызывает специализированный конструктор с определенными начальными параметрами. Метод complexWithRe:andIm: возвращает инициализированный объект класса Complex, размещенный в текущем самовыгружаемом пуле. То же самое делает и метод description, возвращая объект класса NSString. Вот пример программы, где используется класс Complex:

Категории и расширения

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

А пользоваться этим можно вот так:

Расширения несут добрую службу как безымянные категории:

Протоколы

Протокол Objective-C – это формализованное объявление группы методов, которые, по желанию, может реализовать любой класс (аналог класса в С++, где все методы объявлены со спецификатором virtual … = 0). В версии языка 2.0 методы протокола могут быть требуемыми (спецификатор @required, он считается умалчиваемым) и выборочными (спецификатор @optional). Если какой либо класс реализовал требуемые методы протокола, то он называется классом, поддерживающим данный протокол. Протокол, и класс, его поддерживающий, объявляются вот так:

Oбъекту класса MyPrinter можно гарантированно посылать сообщения print и switchedOn, и, после проверки на respondsToSelector:, можно посылать сообщение loadPaper:, та как в его реализации должны присутствовать определения одноименных методов. Объявление объекта класса, поддерживающего какой-либо протокол осуществляется так:

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

А чтобы объявить объект неизвестного класса (id), соответствующий некоторому протоколу, пишут так:

Исключения

Есть два основных подхода к обработке ошибок: глобальная статусная переменная, значение которой информирует об успешности выполнения предыдущей операции, и генерация исключений. Суть обоих в том, что код, в котором произошла ошибка, надеется, что решить ее сможет вызвавший его код, поэтому возвращает управление ему, сообщая о произошедшей ситуации как можно более подробно. Objective-C поддерживает оба эти подхода.

Исключение – это объект некоторого класса. Он (даже своим типом) несет в себе некоторую информацию о произошедшей ситуации. Для удобства в Cocoa имеется класс NSException, который можно инициализировать двумя объектами NSString и одним объектом произвольного класса (тип id):

Сгенерировать исключение и, тем самым, запустить механизм раскрутки стека вызовов, можно с помощью оператора @throw. Чтобы перхватить сгенерированное исключение, участок кода, где возможна его генерация, необходимо заключить в специальный блок с заглавием try (такие блоки могут быть вложенными). А затем, после этого блока, поставить блок с заглавием catch(), где в круглых скобках указать тип предполагаемого исключения. Блоков catch() после блока try может быть несколько. После генерации исключения управление, раскручивая стек, выходит из блока try и, проверяя по очереди все блоки catch(), попадает именно в тот блок catch(), в фигурных скобках которого стоит такой тип, к которому тип исключения приводится неявно (точное совпадение, указатель на базовый класс или id). Если исключение по типу не совпало ни с одним блоком catch(), управление продолжает раскрутку стека. Если после блока с заглавием try стоит блок с заглавием finally, то управление передастся ему независимо от того, произошло ли в блоке try исключение (и обработан какой-нибудь блок catch()), или выполнилась его последняя инструкция. Ниже приведен пример работы с объектом класса Cup в методе fill которого происходит исключение:

В блоке finally удобно освобождать ресурсы, выделенные в блоке try, но не освобожденные по причине сгенерированного исключения.

Свойства

Для версии Objective-C 2.0 нашa реализация класса Complex явно избыточна: в ней слишком много методов доступа и их определение – сплошная рутина. Перепишем его с использованием свойств:

Стань X-кодером: Начинаем кодить под Mac OS с помощью Objective-C

Несмотря на то, что для МакОСи можно создавать приложения на C/C++ или Java, Objective-C фактически стал стандартом при разработке под эту операционку. Это произошло благодаря удобному и функциональному API для этого языка — фреймворку Cocoa. Об ObjectiveC и Cocoa мы сегодня и поговорим.

C превращается. в C с объектами

Objective-C появился через год после плюсов, в 1986 году, благодаря работе Brad Cox и Tom Love в компании Stepstone. Они хотели соединить высокий уровень абстракции и повторного использования кода, характерные для объектно-ориентированного программирования, с производительностью и простотой синтаксиса языка С.

Для этого им пришлось модифицировать C, добавив некоторые фичи из Smalltalk, позволяющие работать с объектами. Получившееся объектно-ориентированное расширение языка С впоследствии стало носить название Objective-C — «объектный C». Чтобы понять, как этот продукт скрещивания двух древних языков программирования попал на современные маки и разнообразные мобильные устройства Apple, нужно вспомнить историю яблочной компании. Apple была основана в 1980 Стивом Джобсом и Стивом Возняком.

После многих лет успеха на посту главы компании Джобса сменил бывший управляющий компании Coca-Cola Джон Скалли (John Sculley). Стив покинул Apple и через некоторое время основал новую компанию, назвав ее NeXT. Среди основных задач NeXT было создание операционной системы нового поколения. Для ее разработки было решено использовать среди прочих средств и Оbjective-C — он стал основой для API. Вскоре операционка от NeXT была готова и получила название NeXTStep. Собственно, это и было первое серьезное применение ObjC. Позже NeXT объединила усилия с Sun Microsystems для создания следующей версии NeXTStep — OPENStep (в настоящий момент продолжает свое существование только GNUStep, поддерживаемая сообществом свободного программного обеспечения). В середине 1990-х Джон Скалли покинул Apple, которая стремительно теряла свои позиции на рынке. Для спасения ситуации компания решила создать новую операционную систему взамен устаревающей Mac OS. После нескольких неудачных попыток Apple решила купить компанию, которая уже имеет свою операционку. Этой компанией, как ты уже наверно догадался, стала NeXT. Так Джобс снова оказался в Apple, на основе NeXTStep была создана Mac OS X, а Objective-C занял свое место среди инструментов Mac-разработчика и в API Mac OS X.

Разговорчивые объекты

Итак, Objective-C — объектно-ориентированное расширение языка С. А что в нем, собственно, есть такого, чего нет в тех же плюсах? Среди основных достоинств Objective-C нужно перечислить следующие:

  • Синтаксис Objective-C очень прост. Чтобы его изучить, С-программисту понадобится всего пара дней. Он является именно расширением языка С, в него просто добавлены новые возможности для объектноориентированного программирования. Таким образом, абсолютно любая программа на С является программой и на Objective-C (что для С++, вообще-то, неверно).
  • Objective-C — message-oriented language, то есть объекты в нем общаются между собой не с помощью явного вызова инкапсулированных функций, как в C++ или Java, а при помощи отправки друг другу сообщений.

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

Язык Objective-C предоставляет широкие возможности для работы с метаинформацией; так, у любого объекта непосредственно на этапе выполнения можно спросить его класс, список методов (с типами передаваемых аргументов) и instance-переменных, проверить, является ли класс потомком заданного и поддерживает ли он заданный протокол (протокол — это список сообщений, на которые отвечает данный объект, некоторое подобие абстрактных классов C++ или интерфейсов Java) и т.д.

Здравствуй, мир!

Линус Торвальдс как-то сказал: «Разговоры — дешевка. Покажите мне код!». Показываю! Вот код HelloWorld , а на Obj-C. Кстати, если у тебя нет под рукой Mac OS X или хакинтоша, можешь попробовать в действии Objective-C и Cocoa c помощью GCC и проекта OpenStep.

Наша первая прога на ObjC

#import <Cocoa/Cocoa.h>
void main()
<
NSLog(@»Hello world!»);
>

Наш HelloWorld сильно напоминает C++, не так ли? Но есть и свои особенности, которые ты, конечно, сразу заметил.

  • #import — директива препроцессора, аналогичная #include, но, в отличие от последней, она не позволяет включить заголовок более одного раза, то есть работает так же, как стражи включения в C++ (#include, разумеется, тоже поддерживается).
  • Cocoa.h — заголовок, содержащий описания функций, классов и констант фреймворка Cocoa, о котором я говорил в начале.
  • NSLog — одна из таких функций. Она выводит текстовое сообщение в stdout вместе с timestamp’ом в начале строки. Помнишь NeXTStep? Префикс «NS» как раз оттуда :).

Строки в Objective-C — отдельная история. NSLog получает на вход не C-строку, а объект класса NSString. Заметил собаку перед строкой в NSLog? Это указание компилятору ObjC создать константу типа NSString — в Сocoa используются не zerro-terminated строки, как в C, а объекты класса NSString или его наследников (NSMutableString например). Создатели Cocoa позаботились о том, чтобы в NSString были реализованы все методы работы со строками, которые тебе чаще всего приходится использовать. Строки можно склеивать, разбивать по разделителю, искать в них подстроки, делать форматированный вывод и много чего другого.

На скриншоте ты можешь видеть код нашего простенького примера в среде XCode и результат его выполнения. XCode — это среда разработки, используемая при написании приложений для Mac OS и iOS. В ней довольно удобно работать, но, как и ко всему новому, к ней нужно привыкнуть. Кроме редактора и отладчика в нее интегрированы средства построения пользовательского интерфейса — «Interface Builder», управления версиями — «SCM» и еще множество всяких полезных фишек. В качестве компилятора используется допиленный Apple GCC. В качестве отладчика — GDB. В настоящий момент на сайте Apple свободно доступна для скачивания XCode 3, пробная версия XCode 4 доступна только для зарегистрированных Appleразработчиков. В общем, качай, устанавливай, пробуй.

Рабочий класс

Раз язык объектно-ориентированный, значит, в нем должны быть объекты. Давай посмотрим, как их создавать и использовать в Objective-C. Каждый класс в коде на Objective-C разделен на интерфейс и реализацию, которые принято хранить в отдельных файлах. Заголовочные файлы получают расширение «h», а файлы с реализацией — «m».

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

// Класс Dog — наследник NSObject
// Разместим его интерфейс в «Dog.h»
@interface Dog : NSObject
<
// Здесь размещаем поля объекта
@private
int Age;
@public
int Color;
>
// После этого идет описание сообщений,
// на которые отвечает объект.
// Перед сообщениями экземпляров ставится «-»,
// а перед сообщениями класса
// (аналог статических методов в C++) — знак «+».
// В описании сообщения указывается имя
// сообщения, список передаваемых параметров
// и тип возвращаемого значения.
— (void) voice;
— (void) setAge: (int) age;
— (int) getAge;
@end
// Реализация сообщений нашего класса (Dog.m)
@implementation Dog
— (void) voice
<
NSLog(@»Woof woof»);
>
// Вообще, для этого есть свойства,
// но о них как-нибудь в следующий раз
— (void) setAge: (int) age
<
Age = age;
>
— (int) getAge
<
return Age;
>
@end

Для вызова метода используется следующий синтаксис:

[my_class_pointer message_name: arg1 arg2_name: arg2 arg3_name: arg3]

Немного непривычно после точек и стрелок C++ и Java, но к такому синтаксису быстро привыкаешь и даже начинаешь считать его удобным. Что интересно, мы можем посылать сообщения нулевому указателю (nil). Результат всегда будет nil.

После того, как класс описан и определена реализация для методов-обработчиков сообщений, можно создавать объекты этого класса.
Для того, чтобы создать объект, нужно сделать две вещи — выделить для него память и инициализировать ее. В Objective-C эти два действия разделены. Это может показаться излишним, но на самом деле придает процессу создания объектов дополнительную гибкость.
Чтобы создать объект Dog, нам потребуется следующая строчка:

Dog * dog1 = [[Dog alloc] init];

alloc — метод класса NSObject. Он выделит необходимое количество памяти под наш объект и вернет указатель на него. После этого мы можем выполнить инициализацию, послав только что созданному объекту сообщение init. Мы не переопределяли обработку сообщений alloc и init, поэтому будут использованы реализации из NSObject. На практике init часто приходится переопределять для того, чтобы произвести какую-то начальную настройку объекта или передать дополнительные параметры. init играет роль конструктора в ObjC.

Теперь, когда мы создали экземпляр класса, мы имеем полное право отправлять ему сообщения:

int age = [dog1 getAge];
[dog1 voce];

Для того, чтобы уничтожить объект и освободить выделенную ему память и (возможно) другие ресурсы, ему нужно отправить сообщение release:

Управление памятью реализовано в ObjC в виде подсчета ссылок. alloc создает объект с числом ссылок, равным единице. release уменьшает количество ссылок для данного объекта на 1. Объект будет уничтожен, когда счетчик достигнет 0. Если тебе когданибудь приходилось использовать COM, то такая реализация управления памятью должна быть тебе хорошо знакома.

Значение счетчика можно увеличивать, посылая объекту сообщение retain. Кстати, начиная с Objective-C 2.0, стала доступна сборка мусора, ее можно совмещать с классическим управлением памятью с помощью подсчета ссылок. Кроме явного уничтожения объекта, ты можешь добавить его в пул временных объектов (NSAutoreleasePool), послав сообщение autorelease. Так часто поступают, например, при возвращении объектов методы классов Cocoa (метод stringByAppendingString объектов NSString, например).

NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
NSString *str;
// Проинициализируем строчку и добавим
// ее в pool
str = [[[NSString alloc] init] autorelease];
// .
[pool drain]; // Здесь объект str будет уничтожен

В этом кратком обзоре ObjC следует, пожалуй, сказать еще об исключениях Objective-C. Обработка ошибок времени выполнения в С трудоемка и может порождать множество других ошибок. На самом деле в C есть два варианта обработки ошибок времени выполнения — возвращение значения функцией и изменение значения глобальной переменной (errno например). В обоих случаях необходимо проверять результат работы каждой функции, выполнение которой может вызвать ошибку.

Код нормального процесса исполнения и код обработки ошибок смешивается, что не есть хорошо. К счастью, в ObjC, как и в C++, есть механизм исключений.

Вот пример обработки исключительной ситуации в ObjC:

Обработка исключений в Objective-C

Cup * cup = [[Cup alloc] init];
@try
<
[cup fill];
>
@catch ( NSException * exc )
<
NSLog ( @»Exception caught: %@», exc );
>
@finally
<
[cup release];
>

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

«Кокао» для кодера

После экскурса по Objective-C можно поговорить о доступных фреймворках МакОСи. Куда же без них? Фреймворк сделает за тебя всю грязную работу, чтобы ты мог спокойно заниматься основной задачей.

Для разработки нативных приложений под МакОСь распространение получили два фреймворка — Cocoa и Carbon. Если ты пишешь под Мак или под iOS на Objective-C, то твой выбор — Cocoa.

Cocoa — это продукт эволюции программных сред NeXTSTEP и OPENSTEP, которые разрабатывались компанией NeXT. Перекочевав на Mac OS X, фреймворк сильно изменился, и в нем появилось множество новых классов. Все они, как в С# или Java, являются наследниками одного класса — NSObject.

В Cocoa представлены базовые типы, такие как NSNumber и NSString, различные контейнеры (NSArray, NSDictionary), обертки для системных объектов и т.д. Давай-ка опробуем Cocoa и Objective-C в действии и создадим небольшое консольное Cocoa-приложение.
В Сети существует множество веб-сервисов. Некоторые из них просто предоставляют информацию в удобном формате, например, в XML.

Одним из таких сервисов является сервис Российского Центробанка, предоставляющий информацию о текущих курсах валют. По адресу www.cbr.ru/scripts/XML_daily.asp можно получить XML’ку следующего вида:

Создадим класс ObjC RCBDayly, который будет скачивать эту XML’ку, парсить ее и предоставлять данные в удобном для нас виде. Исходник нашего класса ты можешь увидеть на врезке.

На этом примере ты можешь видеть, как легко в Cocoa работать с сетью и XML. При создании подобных приложений можно обойтись, конечно, и средствами POSIX, а вот если ты хочешь создать GUI-приложение, без Cocoa тебе не обойтись. Но об этом как-нибудь в другой раз.

Заключение

Разнообразные гаджеты от Apple производятся и продаются по всему миру в огромном количестве. Всех их объединяет то, что на них работает Mac OS X или iOS и, если ты хочешь освоить разработку под эти операционки, знания Objective-C тебе очень пригодятся. До встречи в эфире!

Исходник нашего XML-парсера

// Импортируем классы Cocoa
#import <Cocoa/Cocoa.h>
// Опишем интерфейс нашего класса
@interface RCBDayly : NSObject
<
@private
// NSMutableDictionary — аналог map в C++
NSMutableDictionary * Valutes;
>
// Конструктор принимает на вход URL веб-сервиса
-(RCBDayly ) initWithContentsOfURL:(NSURL)url;
// Для доступа к данным класса будем использовать
// метод getValueForCharCode.
-(NSString ) getValueForCharCode:
(NSString *) char_code;
@end
// А теперь — реализация
@implementation RCBDayly
-(RCBDayly
) initWithContentsOfURL:(NSURL) url
<
// Проинициализируем базовый класс — NSObject
[super init];
// Подгружаем xml,ку с указанного URL
NSError * err = nil;
NSXMLDocument * cbr_xml =
[[NSXMLDocument alloc] initWithContentsOfURL:url
options:0 error:&err];
if (err != nil && [err code] != 0)
<
// Не повезло. Возможно, сервис недоступен, или
// XML-парсер не смог разобрать документ.
// Выяснить, что именно произошло, нам поможет
// locolizedDescription из NSError
NSLog(@»Error:%@», [err localizedDescription]);
// Освободим память, выделенную под наш объект
[self release];
return nil;
>
// Создадим NSMutableDictionary
Valutes = [[NSMutableDictionary alloc] init];
// Нам потребуется массив XML-элементов
NSArray * nodes = nil;
// Заполним его элементами «Valute» из XML’ки
nodes = [[cbr_xml rootElement]
elementsForName: @»Valute»];
// Для каждой валюты получим ее CharCode и
// Value (курс)
for (int i = 0; i < [nodes count]; ++i)
<
NSXMLElement * valute =
(NSXMLElement *)[nodes objectAtIndex: i];
NSArray * names = nil;
names = [valute elementsForName: @»CharCode»];
NSArray * values = nil;
values = [valute elementsForName: @»Value»];
// У валюты есть и имя, и курс?
if ([names count] > 0 && [values count] > 0)
<
// Добавим пару CharCode — Value в наш
// NSMutableDictionary
[Valutes setObject:
[(NSXMLElement *)
[values objectAtIndex: 0] stringValue]
forKey:[(NSXMLElement *)
[names objectAtIndex: 0] stringValue]];
>
// Вернем указатель на себя, как любой нормальный
// конструктор
return self;
>
// В этом методе мы просто обеспечиваем
// доступ к private-полю Valutes
-(NSString
) getValueForCharCode:
(NSString*) char_code
<
return [Valutes objectForKey: char_code];
>
@end
int main(int argc, char *argv[])
<
// Создадим пул для временных объектов,
// которые генерит, например, [NSURL
URLWithString]
NSAutoreleasePool * pool =
[[NSAutoreleasePool alloc] init];
// Создадим объект класса RCPDayly и
// инициализируем его нужным URL
RCBDayly * dayly_values =
[[RCBDayly alloc] initWithContentsOfURL:
[NSURL URLWithString:
@»http://www.cbr.ru/scripts/XML_daily.asp»]];
if (dayly_values == nil)
<
// Произошла ошибка при инициализации класса
return -1;
>
// Почем сегодня доллар? 🙂
NSLog([dayly_values getValueForCharCode:@»USD»]);
[pool release]; // Освободим пул, а вместе с ним
// и все временные объекты.
return 0;
>

Cocoa Framework

Cocoa — набор классов и функций Objective-С, предоставляющих доступ к сервисам Mac OS X для пользовательских приложений. Поскольку для взаимодействия с классами Cocoa необходима среда выполнения ObjC, разработка с использованием этого фреймворка возможна только на Objective-C. Для Сocoa существует возможность кроссплатформенной разработки под Linux и Windows благодаря проектам GNUStep и cocotron.

Carbon Framework

Carbon — процедурный фреймворк Mac OS X, предназначенный для использования в приложениях на C/C++. Он предоставляет обратную совместимость с более ранними версиями Mac OS (например, Mac OS 9).

В настоящее время возможности Carbon для работы с Mac OS X становятся все скромнее. Так, например, невозможно получить доступ к GUI из приложения Carbon для 64-битного окружения — Apple отдает предпочтение Cocoa.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *