Статьи

Начало работы с Objective-C

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


Objective-C и Cocoa — два ключевых компонента платформы iOS. Несмотря на то, что платформа iOS все еще относительно молода, Objective-C был создан в начале 1980-х годов в StepStone Брэдом Коксом и Томом Лавом. Язык был создан, чтобы объединить надежный и гибкий язык программирования Си с элегантным языком Smalltalk. Objective-C является строгим надмножеством C и, в отличие от C, является языком программирования высокого уровня. Ключевое различие между C и Objective-C состоит в том, что последний является объектно-ориентированным языком программирования, тогда как C является процедурным языком программирования.

Как iOS в конечном итоге использовала язык, который был разработан в 1980-х годах? Вскоре после того, как NeXT был основан Стивом Джобсом, он лицензировал Objective-C от StepStone. NeXT создал NeXTSTEP, инструментарий пользовательского интерфейса для операционной системы NeXT, разработанный в Objective-C. Несмотря на то, что NeXTSTEP предоставил революционный набор инструментов, операционная система NeXT получила лишь незначительную популярность на рынке. В 1996 году Apple приобрела NeXT, а NeXTSTEP стал Какао. Последний стал массовым с выпуском OS X в марте 2001 года, а затем с выпуском iPhone и операционной системы iOS.


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


Разработчики, плохо знакомые с экосистемами iOS и OS X, часто путаются в отношениях между Objective-C, Cocoa (OS X) и Cocoa Touch (iOS). Что такое Cocoa Touch и как он относится к Objective-C? Cocoa — это родной интерфейс программирования приложений (API) Apple для платформ iOS и OS X. Objective-C — это язык, на котором работает Какао. Хотя эта статья в основном посвящена языку программирования Objective-C, мы подробнее рассмотрим API-интерфейсы Cocoa и Cocoa Touch позже в этой серии.


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

Давайте посмотрим на пример: тостеры. Перед изготовлением тостера инженеры создают проект, который является эквивалентом класса. Каждый тостер, созданный на основе чертежа, является экземпляром или уникальным экземпляром класса. Несмотря на то, что каждый тостер создан из одного и того же проекта (класса), у каждого из них свое состояние (цвет, количество слотов и т. Д.) И поведение.

Состояние экземпляра хранится и определяется его переменными экземпляра или атрибутами объекта, если хотите. Это подводит нас к другому ключевому шаблону объектно-ориентированного программирования: инкапсуляции . Инкапсуляция означает, что внутреннее представление объекта является частным и известно только самому объекту. На первый взгляд это может показаться серьезным ограничением. Однако результатом является модульный и слабо связанный код.

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

Другая мощная парадигма объектно-ориентированного программирования — наследование классов . Когда класс A является подклассом класса B, класс A наследует атрибуты и поведение класса B. Считается, что класс B является родительским классом или суперклассом класса A. Наследование также способствует повторному использованию кода и модульности.

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

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


Давайте применим наши знания на практике, создав новый проект Xcode для работы с ним. Создайте новый проект в Xcode, выбрав New> Project … из меню File .

Как и в предыдущей статье, выберите шаблон проекта инструмента командной строки в категории « Приложения » в разделе « OS X ».

Установите для « Имя продукта» значение « Книги» и присвойте проекту название организации и идентификатор компании. Для этого проекта важно установить тип проекта Foundation . Причина такого выбора станет ясна позже в этой статье.

Сообщите Xcode, где вы хотите сохранить проект, и нажмите кнопку « Создать» . Вы можете заметить, что проект выглядит иначе, чем проект, который мы создали для изучения C. Давайте на минутку посмотрим, в чем различия.


Проект содержит несколько файлов и папок, чем инструмент командной строки, который мы создали в предыдущей статье. В дополнение к main.m и Books.1 , есть две новые папки, Supporting Files и Frameworks , каждая из которых содержит один элемент.

Вспомогательные файлы содержат файл с именем Books-Prefix.pch . Расширение файла .pch говорит нам, что это предварительно скомпилированный заголовочный файл . Его цель станет ясна позже в этой серии.

Папка Frameworks содержит рамки, с которыми связан проект. Что такое фреймворк? Каркас — это пакет или каталог, который содержит библиотеку, включая ее ресурсы, такие как изображения и заголовочные файлы. Концепция заголовочного файла станет ясна через минуту. Папка Frameworks в настоящее время содержит один элемент, Foundation.framework .

При создании проекта вы устанавливаете тип проекта в Foundation , что означает, что проект связан с платформой Foundation. Основа Foundation — это фундаментальный набор классов Objective-C. Позже в этой серии мы более подробно рассмотрим основы Foundation.


Настало время создать свой первый класс. Всякий раз, когда вы создаете новый файл ( Файл> Новый> Файл … ), вам предоставляется список шаблонов файлов. Выберите Какао из раздела OS X и выберите шаблон класса Objective-C, чтобы создать новый класс Objective-C. Нажмите Далее, чтобы продолжить.

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

Нажмите Next, чтобы продолжить, и скажите Xcode, где вы хотите сохранить новый класс. Обязательно сохраните новый класс где-нибудь в вашем проекте XCode.

Xcode добавил два новых файла в проект, Book.h и Book.m. Book.h является заголовочным файлом класса Book и предоставляет интерфейс класса, как мы видели ранее. Интерфейс класса содержит свойства и методы класса, а также определяет суперкласс класса. Book.m является файлом реализации класса и определяет его поведение, реализуя методы, объявленные в заголовочном файле класса.

Откройте Book.h и изучите его содержимое. Помимо некоторых комментариев вверху, файл заголовка содержит только три строки кода. Первая строка импортирует заголовочный файл платформы Foundation. Это гарантирует, что класс Book имеет доступ к классам и протоколам, объявленным в платформе Foundation.

1
#import <Foundation/Foundation.h>

Вторая и третья линия образуют пару. В Objective-C каждый интерфейс класса начинается с @interface и заканчивается @end , которые являются директивами компилятора, то есть командами или инструкциями для компилятора. За директивой @interface следует имя класса, двоеточие и суперкласс класса — если применимо. Как мы видели ранее, родительский класс или суперкласс — это класс, от которого он наследует атрибуты и поведение.

1
2
@interface Book : NSObject
@end

NSObject является корневым классом большинства классов Objective-C. Первые две буквы, NS , относятся к его происхождению, NeXTSTEP, как мы видели ранее в этой статье. Унаследовавшись от NSObject , классы ведут себя как классы Objective-C и наследуют базовый интерфейс для системы времени выполнения.

Прежде чем мы внесем изменения в класс Book , давайте кратко рассмотрим Book.m , файл реализации класса. Вместо импорта основы Foundation, файл реализации импортирует файл заголовка класса Book . Почему это необходимо? Файл реализации должен знать, какие свойства и методы объявлены в заголовочном файле, прежде чем он сможет реализовать поведение (то есть методы) класса. За оператором импорта следует реализация класса, обозначенная @implementation и @end .

Класс Book не очень полезен в своей текущей реализации. Перейдите к заголовочному файлу и добавьте три свойства year , title и author и добавьте метод с именем bookInfo .

Свойства объявляются с ключевым словом @property и могут быть объявлены в любом месте блока класса @interface . За ключевым словом @property следует тип и имя свойства. Не забывайте звездочку перед свойствами title и author , потому что объект Cocoa всегда упоминается как указатель.

01
02
03
04
05
06
07
08
09
10
11
#import <Foundation/Foundation.h>
 
@interface Book : NSObject
 
@property int year;
@property NSString *title;
@property NSString *author;
 
— (NSString *)bookInfo;
 
@end

Объявление метода немного напоминает прототип функции, но есть ряд ключевых отличий. Объявление метода начинается со знака минус, указывающего, что это метод экземпляра. Методы класса начинаются со знака плюс. За знаком минус следует тип возврата метода в скобках, экземпляр NSString и имя метода. Объявление метода заканчивается точкой с запятой.

Я уверен, что вам интересно, что такое NSString и почему на него нужно ссылаться как на указатель. Класс NSString является членом платформы Foundation. Он объявляет интерфейс для объекта, который управляет неизменной строкой. В предыдущей статье мы видели, что строка в C может быть представлена ​​массивом символов, и это именно то, что делает класс NSString , он управляет массивом символов под капотом. Преимущество использования NSString том, что он значительно облегчает работу со строками.

Теперь, когда мы объявили метод bookInfo в заголовочном файле класса, пришло время реализовать его в файле реализации класса. Откройте Book.m и добавьте следующий фрагмент кода где-нибудь в блоке @implementation . Прежде чем мы остановим реализацию bookInfo , нам нужно поговорить об объектном обмене сообщениями.

1
2
3
4
— (NSString *)bookInfo {
    NSString *bookInfo = [NSString stringWithFormat:@»%@ was written by %@ and published in %i», self.title, self.author, self.year];
    return bookInfo;
}

Мы уже знаем, что поведение класса определяется через его методы. Чтобы вызвать метод объекта, объекту отправляется сообщение. Изучите следующий фрагмент кода, чтобы понять эту концепцию. Давайте разберем это построчно. В первой строке мы объявляем новую строку и присваиваем ей постоянную строку, заключая строку в двойные кавычки и добавляя перед ней знак @ .

1
2
3
NSString *string = @»This is a string of characters.»;
int length = [string length];
NSLog(@»The length of the string is %i.\n» length);

Во второй строке мы отправляем сообщение length в экземпляр строки. Другими словами, мы вызываем метод length для экземпляра строки, и метод возвращает целое число. Целое число присваивается переменной length типа int . В последней строке мы записываем переменную длины в консоль, вызывая функцию NSLog как мы видели в предыдущей статье.

Отправка сообщений объектам — это то, что вы будете делать много, поэтому важно понимать синтаксис. Хотя синтаксис выглядит странно, если вы новичок в Objective-C, это не так сложно понять. Между квадратными скобками находится объект слева и имя сообщения или метода справа.

1
[object message];

Методы, принимающие аргументы, выглядят немного иначе, но общий синтаксис идентичен. Например, у класса NSString есть другой метод с именем substringFromIndex: Двоеточие в конце имени указывает, что этот метод принимает аргумент. Вызов этого метода для строки выглядит следующим образом:

1
NSString *substring = [string substringFromIndex:5];

Objective-C известен своими длинными и подробными именами методов. Взгляните на следующий пример, который включает имя метода с несколькими аргументами. Вы должны признать, что имя метода четко указывает, что метод делает. Имя метода разбивается на куски, каждый из которых принимает аргумент. Когда мы начнем работать с iOS SDK, обмен объектными сообщениями действительно начнется.

1
NSString *anotherString = [string stringByPaddingToLength:5 withString:@»some string» startingAtIndex:2];

Прежде чем мы продолжим, мы должны вернуться к реализации bookInfo . Реализация метода начинается с повторения объявления метода. Задняя точка с запятой заменяется парой фигурных скобок, которые оборачиваются вокруг реализации метода. Сначала мы объявляем новую строку bookInfo и присваиваем ей новую строку, созданную с атрибутами нашего экземпляра книги ( title , author и year ). В конце метода bookInfo мы возвращаем новую строку bookInfo , потому что это то, что ожидает метод — строка в качестве возвращаемого типа.

Три вещи требуют пояснения. Во-первых, метод stringWithFormat: является методом класса, а не методом экземпляра. Мы знаем это, потому что метод вызывается для самого класса, NSString , а не для экземпляра класса. Методы класса распространены в объектно-ориентированных языках программирования. Во-вторых, спецификатор формата для объекта представлен символом @ (которому предшествует знак процента). И title и author являются объектами, если быть точными. В-третьих, ключевое слово self всегда ссылается на экземпляр класса. В этом случае self ссылается на экземпляр Book к которому принадлежит метод bookInfo .

Если вы работали с другими объектно-ориентированными языками, доступ к переменным экземпляра в Objective-C может привести к путанице. Мы не обращаемся напрямую к переменной экземпляра, когда пишем self.title . Это не что иное, как ярлык [self title] . Последнее означает, что мы используем метод getter, чтобы запросить экземпляр переменной экземпляра с именем title . То же самое верно для установки переменной экземпляра. Взгляните на следующий фрагмент кода. Как видите, использование self.title — не более чем синтаксический сахар.

1
2
3
4
// This assignment …
self.title = @»The Hobbit»;
// … is equivalent to …
[self setTitle:@»The Hobbit»];

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

Однако мощь и полезность типа данных id идет намного дальше. Тип данных id является ключевым компонентом динамической типизации и динамического связывания Objective-C. Важно понимать, что тип данных id не содержит никакой информации о самом объекте, кроме того, что это объект.

В Objective-C каждый объект знает, к какому классу он принадлежит (через переменную isa ), и это очень важно. Это почему? Одной из сильных сторон Objective-C является ее динамическая типизация, что означает, что проверка типов выполняется во время выполнения, а не во время компиляции.

Однако, поскольку тип данных id не сообщает компилятору ничего о классе, к которому принадлежит объект, сам объект должен предоставить эту информацию компилятору.

Имейте в виду, что вполне допустимо статически вводить объект в Objective-C, явно указав класс объекта вместо использования типа данных id .

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

Что это значит и почему это важно? Это означает, что метод, вызываемый в ответ на сообщение, отправленное объекту, определяется во время выполнения, когда известно и сообщение, и объект. Это то, что известно как динамическое связывание.

В Objective-C ключевое слово nil определяется как null объект, то есть id со значением 0 . Под капотом нет никакой разницы между nil , Nil и NULL , и можно отправлять сообщения каждому из них без исключения.

Соглашение заключается в использовании nil для объектов, Nil для классов и NULL противном случае. Возможность отправлять сообщения nil , Nil и NULL имеет свои преимущества, но также имеет и недостатки. Для получения дополнительной информации о nil , Nil и NULL , посмотрите на этот вопрос о переполнении стека.


Откройте main.m и добавьте оператор import для импорта файла заголовка класса Book . Вместо использования угловых скобок мы используем двойные кавычки для импорта файла заголовка класса Book . Двойные кавычки используются для локальных файлов, тогда как угловые скобки используются для глобальных включений, используя путь включения проекта.

1
2
#import <Foundation/Foundation.h>
#import «Book.h»

Сразу под вызовом NSLog добавьте следующий фрагмент, чтобы создать экземпляр класса Book .

1
2
3
4
Book *book1 = [[Book alloc] init];
book1.title = @»The Hobbit»;
book1.author = @»JRR Tolkien»;
book1.year = 1937;

В первой строке мы объявляем переменную типа Book и инициализируем ее. Это хороший пример для иллюстрации вызовов вложенных методов. Первый метод, вызываемый в классе Book — это alloc . Детали этого звонка не важны. Суть в том, что для нового объекта выделяется память и объект создается.

Из-за вложенности вызовов метод init вызывается для нового объекта, созданного методом alloc . Метод init init выполняет анализ нового объекта, настраивая его и подготавливая его к использованию. Метод init возвращает экземпляр и, в нашем примере, присваивает его переменной book1 .

Следующие три строки должны быть уже знакомы, мы устанавливаем название, автора и год публикации новой книги.

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

1
2
3
4
5
6
Book *book2 = [[Book alloc] init];
[book2 setTitle:@»The Fellowship of the Ring»];
[book2 setAuthor:@»JRR Tolkien»];
[book2 setYear:1954];
 
NSArray *books = [[NSArray alloc] initWithObjects:book1, book2, nil];

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

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

initWithObjects: принимает любое количество объектов, которые вы хотите сохранить в массиве. Список объектов всегда должен заканчиваться nil .

Я уже несколько раз упоминал, что Objective-C является строгим надмножеством C и что мы можем свободно комбинировать C и Objective-C. Посмотрим, как это работает. Мы начнем с использования простого оператора if/else чтобы проверить, содержит ли массив какие-либо объекты. Отправив массиву сообщение count , он вернет количество объектов, которые он содержит.

Если массив содержит объекты, мы используем цикл for для перебора объектов в массиве. Во время каждой итерации мы запрашиваем у массива объект с индексом i и отправляем объекту — экземпляру класса Book — сообщение bookInfo . Как мы видели ранее, bookInfo возвращает экземпляр NSString , который мы записываем на консоль.

1
2
3
4
5
6
if ([books count] > 0) {
    for (int i = 0; i < [books count]; i++) {
        Book *aBook = [books objectAtIndex:i];
        NSLog(@»%@», [aBook bookInfo]);
    }
}

Я уверен, что вы немного перегружены Objective-C. Это нормально. Хотя Objective-C является не чем иным, как тонким слоем поверх языка C, многое еще происходит.

Хотя в Objective-C есть нечто большее, чем обсуждалось в этой статье, теперь вы знаете основы и готовы приступить к работе с iOS SDK. В следующей статье мы рассмотрим iOS SDK и рассмотрим его различные компоненты.