Разработка под Apple. Objective-C

Objective-C — это простой язык программирования, разработанный как язык объектно-ориентированного программирования. Objective-C, также как и C++ является расширением ANSI-C, и имеет поддержку таких возможностей, как описание классов, методов и свойств. В отличие от C++ Objective-C полностью совместим с языком С, то есть любой код, написанный на языке Си (это в первую очередь будет касаться сторонних библиотек), будет работать c Objective-C.

Настоятельно рекомендуется ознакомиться с основами языка Си (ссылка на предыдущую статью).

В самой первой статье на примере простого приложения для iOS мы уже сталкивались с классами, методами и объектами. Сегодня мы более подробно изучим данные конструкции и напишем более сложное приложение.

Разработка под Apple. Objective-C. Фото.
Начнем с методов, синтакси которых в общем виде выглядит так:

- (ТипВозвращаемыхДанных)названиеМетода:(ТипДанных)агрумент1продолжениеНазванияМетода:(ТипДанных)аргумент2;

И реальный пример:

- (NSArray *)shipsAtPoint:(CGPoint)bombLocation withDamage:(BOOL) damaged;

В данном случае представлен метод с двумя аргументами. Метод начинается с знака «-» для методов экземпляра класса или с «+» для методов класса. Подробнее об отличиях мы поговорим позже.

Далее идет тип возвращаемых значений, он может быть (void), если ничего не возвращается, или (id), если возвращается объект, при этом тип его может быть любым (NSString, NSArray, NSNumber, NSDictionary…). Однако рекомендуется по возможности явно указывать тип данных, для того, чтобы компилятор мог предупредить нас о возможной ошибке.

*Разрешается не писать тип возвращаемого значения и не ставить скобки в начале метода вообще, тогда это расценивается как (id). Также, когда мы указываем (IBAction), это аналогично (void), но при этом мы сообщаем Interface Builder, что этот метод будет вызван из интерфейса.

После идет название метода с маленькой буквы. Objective-C имеет не совсем обычные названия методов, которые разделены аргументами (в большинстве языков аргументы методов перечисляются после названия метода). В данном случае имя метода shipsAtPoint:withDamage:. Такой синтаксис во многом удобен, т.к. делает метод лучше читаемым.

К именам аргументов применяются те же правила, что и в С (не начинается с % и тп), при этом аргументы используются в реализации метода как локальные переменные.

Если имя метода слишком длинное, можно разделить его на строки, при этом Xcode умеет выравнивать их по знаку «:».

Как было сказано выше, есть instance methods (методы экземпляра класса) и class methods (методы класса).

Инстанс методы:

• Начинаются с «-»
• Оперируют с переменными экземпляра (инстанс-переменными) как с локальными переменными.
• Могут отправлять сообщения self и super. Разница в реализации: self использует вашу реализацию метода, super — реализацию метода суперкласса (не используется наследование).

В Objective-C объекты обмениваются между собой сообщениями, синтаксис сообщений: [получатель сообщение];

В качестве получателя выступает объект, а сообщением является метод. Сообщения могут содержать аргументы:

[объект метод:аргумент1 продолжениеНазванияМетода:аргумент2];

Например:

BOOL destroyed = [ship dropBomb:bombType at:dropPoint from:height];

Т.е. переменная destroyed получает возвращаемое методом dropBomb:at:from логическое значение типа BOOL. При этом реализуется метод объекта ship.

Методы класса адресуются классу и используются в трех случаях:

• для выделения памяти под объект, метод + (id)alloc;
• для создания объекта с помощью фабрик, например, + (Ship *)motherShip;
• методы для получения информации о структуре класса, + (int)

turrentsOnShipOfSize:(int)shipSize; (возвращает значение количества орудий на корабле определенного размера).

Методы класса не имеют доступа к инстанс-переменным.

Примеры вызова методов:

NSArray *myArray = [[NSArray alloc] init]; — создаем массив. При этом метод alloc является методом класса, а метод init — методом экземпляра класса (инстанса).

NSString *myString = [NSString stringWithString:@“Моя строка”]; — используем фабрику, при этом автоматически выделяется память и инициализируется инстанс. О преимуществах первого и второго подхода мы поговорим в статье управление памятью.

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

Разработка под Apple. Objective-C. Инстанс методы:. Фото.

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

Если взять в пример физические объекты, то «Транспортные средства» будут абстрактным суперклассом, «Легковые автомобили» — классом, а «BMW 318i» — экземпляром класса.

Класс мы описываем в .h (заголовки) и .m (реализация) файлах, синтаксис класса в файле заголовка (MyClass.h) выглядит так:

@interface MyClass : NSObject
{
NSString *name;
}
- (IBAction)valueChanged;

Здесь название класса MyClass, который наследует у суперкласса NSObject. Между фигурными скобками объявляются инстанс-переменные; а после скобок — методы и свойства.

В файле реализации (MyClass.m)

@implementation CalculatorBrain
- (IBAction)valueChanged {
//Какие-то действия
}

Cоздать экземпляр класса MyClass мы можем в другом классе, импортировав в его .h файл ссылку на #import "MyClass.h" наш класс и создав, его в файле реализации .m

@implementation MyApplicationViewController //какой-то класс
-(void)нашМетод {
MyClass *myClassObject = [[MyClass alloc] init]; //создали экземпляр класса
[myClassObject methodDefinedInMyClass]; //используем метод нашего класса
}

Свойства (property)

В редакции Objective-C 2.0 появились свойства (properties) и новый dot-синтаксис для доступа к ним. По сути свойства позволяют получать доступ к инстанс-переменным объекта используя dot-синтаксис: myObject.property

Свойства мы определяем в файле заголовка (.h) после фигурных скобок:

@interface MyClass : NSObject
{
double memoryStorage;
}
@property (double) memoryStorage; //свойство
- (IBAction)valueChanged; //методы

В файле реализации (.m) мы можем либо вручную прописать getter и setter для свойства:

– (double) memoryStorage {
return memoryStorage; //getter, просто возвращает значение memoryStorage
}
– (double) setMemoryStorage:(double)newValue {
memoryStorage = newValue; //setter
}

Либо использовать @synthesize memoryStorage, который синтезирует за нас стандартные сеттер и геттер. Если далее мы напишем свой геттер, например, то он будет использоваться, а сгенерированный геттер @synthesize будет проигнорирован. Также можно использовать

@synthesize (readonly) memoryStorage;

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

Если мы хотим создать private property, свойство к которому может получить доступ только наш класс, используется следующий синтаксис. В файле реализации добавляется перед @implementation:

#import "ClassName.h"
@interface ClassName()
@property NSString *myString;
@end
@implementation ClassName
B //реализация класса
@end

Важно не забыть поставить скобки «()» после имени класса.

Как вы могли заметить, dot-синтаксис в свойствах значительно напоминает работу со структурами в Си. И это неспроста, объекты в Objective-C это по сути те же struct (структуры) в Cи, только в отличие от структур, имеющие методы.

На этом мы заканчиваем с теорией и переходим к практике!

Калькулятор

Сегодня мы создадим гораздо более сложное приложение — Калькулятор, где мы создадим собственный класс, познакомимся с новыми контроллами и
элементами UI.

Для начала создадим новый проект в Xcode и назовем его Calculator.

Создадим View-based Application — Xcode сгенерирует для нас View и Controller, а модель (наш собственный класс) мы создадим сами:

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 1. View-based Application

Назовем приложение Calculator и сохраним проект в папке Developer, как мы делали в прошлом примере.

Итак, перед нами наш проект:

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 2. Проект Calculator

Слева в списке файлов CalculatorViewController.h и CalculatorViewController.m — файлы заголовков и реализации нашего контроллера. CalculatorViewController.xib — наш графический интерфейс (View).

Создадим Модель. Нажмем Cmd + N или в меню File ➤ New ➤ New File

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 3. Новый файл

Выберем Objective-C class, так как мы создаем наш собственный класс, нажмем далее и выберем суперкласс: NSObject и назовем наш класс CalculatorBrain. В итоге мы получим файлы CalculatorBrain.h и CalculatorBrain.m.

Начнем с нашего контроллера. Отроем файл CalculatorViewController.h.

Рекомендую также использовать Ассистент, который откроет CalculatorViewController.m параллельно с нашим файлом. Для этого нажмите Cmd + Alt + Return, а чтобы скрыть левую панель навигатора файлов, нажмите Cmd + 0:

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 4. Использование Ассистента

В контроллере нам нужно создать:

1) outlets (инстанс-переменные, которые указывают на объекты в нашем графическом интерфейсе);
2) actions (методы, которые посылаются из графического интерфейса);
3) инстанс-переменную, которая указывает на нашу модель CalculatorBrain.

Добавим outlet, который будет отображать результат или текущее число, которое мы ввели:

@interface CalculatorViewController : UIViewController {
IBOutlet UILabel *display;
}
@end

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

#import
#import "CalculatorBrain.h"
@interface CalculatorViewController : UIViewController {
IBOutlet UILabel *display;
CalculatorBrain *brain;
}
@end

Нам понадобится еще одна инстанс-переменная логического типа, для того, чтобы определить, что пользователь в процессе ввода числа, т.е. например, ввел 5 и далее нажал 3.

B CalculatorBrain *brain;
B BOOL userIsInTheMiddleOfTypingANumber;

Далее добавим методы. Всего в данный момент у нас предусмотрено два действия: нажатие кнопки с цифрами и нажатие кнопки с операцией:

}
- (IBAction)digitPressed:(UIButton *)sender;
- (IBAction)operationPressed:(UIButton *)sender;
@end

(UIButton *)sender в данном случае позволит нам узнать, какая кнопка нажата, так как передаст в сообщении объект UIButton в качестве аргумента.

Вот так это будет выглядеть:

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 5. Header-файл контроллера

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

Нажмите Cmd+T, чтобы открыть новую вкладку (контроллер нам еще понадобится, когда мы перейдем к его реализации). Выберите CalculatorViewController.xib в выпадающем списке:

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 6. CalculatorViewController.xib

Ассистент можно закрыть (Cmd + Return), а правую панель открыть (Cmd + Alt + 4). Если запомнить комбинации пока не удается, можно пользоваться кнопками в правом верхнем углу окна Xcode.

Наш контроллер расположен слева в виде светло-оранжевого полупрозрачного куба и называется File’s Owner, с ним мы и будем связывать элементы нашего UI, аналогично тому, как мы это делали в первой лекции.

Добавим UILabel и две кнопки, назовем первую «1», а вторую «+»:

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 7. Создаем графический интерфейс.

Теперь свяжем данные элементы с контроллером, перетягиванием мыши с нажатым Ctrl от контроллера к UILabel и от UIButton к контроллеру. Для «1» выберем метод digitPressed, для «+» — operationPressed. Скопируем кнопки, отвечающие за цифры и переименуем их, чтобы получить клавиатуру калькулятора от 0 до 9. Аналогично поступим с операцией.

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

*Однако будьте внимательнее и не перепутайте кнопки с цифрами с кнопками с операциями.

Выделив элемент вы можете поэкспериментировать с его внешним видом, а также познакомиться с другими вкладками, присутствующими в инспекторе (панель справа).

Вот, что у вас должно получиться в итоге:

Разработка под Apple. Objective-C. Калькулятор. Фото.

Рис. 8. UI калькулятора

Итак, перед тем как закончить работу с нашим контроллером, который обеспечит работу с интерфейсом и моделью, нам нужно написать реализацию модели (создать API, которое мы предоставим контроллеру).

Откроем новую вкладку и файл CalculatorBrain.h, используем инспектор, чтобы параллельно просматривать CalculatorBrain.m

В файле CalculatorBrain.h напишем:

@interface CalculatorBrain : NSObject {
B double operand;
}
@end

Чтобы устанавливать значение «операнда» мы можем реализовать метод — (void)setOperand:(double)aDouble; но мы используем свойства, которые рассматривали ранее:

}
@property double operand; //свойство для operand
- (double)performOperation:(NSString *)operation; //метод выполнить
операцию
@end

Контроллер может передавать нашей модели два типа значений — операцию и числовое значение. Для первого мы будем использовать метод performOperation, для второго свойство operand.

Перейдем к файлу реализации CalculatorBrain.m Синтезируем свойство operand и добавим метод:

@implementation CalculatorBrain
@synthesize operand;
- (double)performOperation:(NSString *)operation {
}
@end

Создадим обработку события «нажатие кнопки извлечения квадратного корня»:

- (double)performOperation:(NSString *)operation
{
if ([operation isEqual:@"√"])
{
operand = sqrt(operand);
}
return operand;
}

Мы только что отправили сообщение объекту operation, спросив у него соответствует ли он строке @"√". isEqual — метод класса NSObject, от которого наследуют все сабклассы, в том числе и NSString, экземпляром которого является operation.

Для операций вроде извлечения квадратного корня или 1/x получение результата возможно мгновенно, но для операций с двумя операндами 10 + 5 =
нам нужно хранить в памяти первый операнд.

Поэтому вернемся к файлу CalculatorBrain.h и добавим две инстанс-переменные:

@interface CalculatorBrain : NSObject {
double operand;
NSString *waitingOperation;
double waitingOperand;
}

Теперь в файле CalculatorBrain.m добавим поддержку операций с двумя операндами:

operand = sqrt(operand);
}
else
{
[self performWaitingOperation];
waitingOperation = operation;
waitingOperand = operand;
}
return operand;
}

Мы отправляем сообщение performWaitingOperation получателю self, т.е. объекту, который сейчас отправляет сообщение (данному экземпляру класса CalculatorBrain). performWaitingOperation будет частным (private) методом, к которому мы не будем иметь доступа из контроллера, поэтому не нужно объявлять этот метод в файле-заголовке CalculatorBrain.h.

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

@implementation CalculatorBrain
@synthesize operand;

- (void)performWaitingOperation {
if ([@"+" isEqual:waitingOperation])
{
operand = waitingOperand + operand;
}
else if ([@"*" isEqual:waitingOperation])
{
operand = waitingOperand * operand;
}
else if ([@"-" isEqual:waitingOperation])
{
operand = waitingOperand - operand;
}
else if ([@"/" isEqual:waitingOperation])
{
if (operand) {
operand = waitingOperand / operand;
}
}
}

Мы просто в зависимости от waitingOperation выполняем соответствующее действие, а при делении мы дополнительно проверяем, что делитель не равен «0».

С моделью мы закончили, перейдем к реализации контроллера. У нас уже открыт в одной из вкладок CalculatorViewController.m. В файле вы видите методы, которые сгенерировал Xcode для нам. Они нам сейчас не нужны, выделите всё между @implementation и @end и удалите.

Нам нужно реализовать методы нажатия на цифровую кнопку и кнопку операции, также нам нужно создать экземпляр класса CalculatorBrain:

- (CalculatorBrain *)brain {
if (!brain) brain = [[CalculatorBrain alloc] init];
return brain;
}

Нам не нужно несколько объектов класса CalculatorBrain, поэтому мы перед тем как создать новый проверяем не существует ли уже такой объект.

Реализуем метод operationPressed:

- (IBAction)operationPressed:(UIButton *)sender {
if (userIsInTheMiddleOfTypingANumber) {
self.brain.operand = [display.text doubleValue];
userIsInTheMiddleOfTypingANumber = NO;
}
NSString *operation = sender.titleLabel.text;
double result = [self.brain performOperation:operation];
display.text = [NSString stringWithFormat:@"%g", result];
}

Вначале мы проверяем не в середине ли ввода чисел мы находились, если да, то мы должны передать модели (self.brain) полный операнд (число, которое мы видим на дисплее), и так как мы нажали на кнопку операции userIsInTheMiddleOfTypingANumber получит значение NO.

Далее мы выполняем получаем значение titleLabel.text (надпись на нажатой кнопке) и передаем сообщение модели выполнить вычисление. После чего мы просто выводим результат на экран.

Теперь реализуем метод digitPressed:

- (IBAction)digitPressed:(UIButton *)sender {
NSString *digit = sender.titleLabel.text;
if (userIsInTheMiddleOfTypingANumber) {
display.text = [display.text stringByAppendingString:digit];
}
else
{
display.text = digit;
userIsInTheMiddleOfTypingANumber = YES;
}
}

В начале мы получили значение названия кнопки (в данном случае какую-то цифру), далее, если мы в середине ввода числа, то мы добавляем цифру в конец строки, используя стандартный метод NSString — stringByAppendingString, подробнее об этом методе можно посмотреть в документации. И выводим новое число на экран.

Если мы только начали ввод, то мы просто выводим число на экран и меняем значение userIsInTheMiddleOfTypingANumber на YES, т.к. мы начали ввод числа.

*По умолчанию значение userIsInTheMiddleOfTypingANumber при старте программы равно NO.

Мы закончили с калькулятором и можем запускать его в симуляторе (Cmd + R) или кнопка Build and Run.

Скачать готовый калькулятор.

Задание

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

1) Добавить возможность совершать операции с плавающей точкой. Для этого вам может потребоваться добавить еще один метод floatPressed, а для отлавливания плавающей точки (нам ведь не нужно числа вроде 12.34.1244.012) посмотрите в документации класс NSRange и его метод rangeOfString.
2) У нас отсутствуют такие операции как 1/x, +/–, C (сброс). Добавление этих операций достаточно тривиально, однако нужно учесть то, что делить на ноль, например, нельзя.
3) Реализуйте систему сообщений об ошибках (при делении на ноль, попытке извлечения квадратного корня из отрицательного числа и тп). Посмотрите класс UIAlertView, представляющий стандартные информационные сообщения в iOS.

Задания повышенной сложности:

1) Добавьте возможность работы с памятью, используя кнопки M+, MC (стереть число в памяти), MR (извлечь число из памяти).
2) Добавьте возможность работы с Sin/Cos, а также переключатель режимов Deg/Rad. Для переключателя можно использовать UISegmentedControl, а для выполнения операций — стандартные мат. операции sin(operand);