Современная электронная библиотека ModernLib.Net

Бестселлеры O`Reilly - iOS. Приемы программирования

ModernLib.Net / Программирование / Вандад Нахавандипур / iOS. Приемы программирования - Чтение (Ознакомительный отрывок) (стр. 2)
Автор: Вандад Нахавандипур
Жанр: Программирование
Серия: Бестселлеры O`Reilly

 

 


Название организации – довольно важная информация, которую, как правило, придется здесь указывать, но пока она нас не особенно волнует. В поле Company Identifier (Идентификатор компании) запишите com.mycompany. Если вы действительно владеете собственной компанией или пишете приложение для фирмы, являющейся вашим работодателем, то замените mycompany настоящим названием. Если просто экспериментируете, придумайте какое-нибудь название. В разделе Devices (Устройства) выберите вариант Universal (Универсальное).

5. Как только зададите все эти значения, просто нажмите кнопку Next (Далее).

6. Система предложит сохранить новый проект на диске. Выберите желаемое местоположение проекта и нажмите кнопку Create (Создать).

7. Перед запуском проекта убедитесь, что к компьютеру не подключено ни одного устройства iPhone или iPad/iPod. Это необходимо, поскольку, если к вашему Mac подключено такое устройство, то Xcode попытается запускать приложения именно на устройстве, а не на эмуляторе. В таком случае могут возникнуть некоторые проблемы с профилями инициализации (о них мы поговорим позже). Итак, отключите от компьютера все устройства с системой iOS, а затем нажмите большую кнопку Run (Запуск) в левом верхнем углу Xcode. Если не можете найти кнопку Run, перейдите в меню Product (Продукт) и выберите в меню элемент Run (Запуск).

Ура! Вот и готово простое приложение, работающее в эмуляторе iOS. Может быть, оно и не кажется особенно впечатляющим: в эмуляторе мы видим просто белый экран. Но это лишь первый шаг к освоению огромного iOS SDK. Давайте же отправимся в это непростое путешествие!

<p>Определение переменных и понятие о них</p>

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

тип данных, представляющий собой либо примитив (например, целое число), либо объект;

• имя;

• значение.

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

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

• NSInteger и NSUInteger. Переменные этого типа могут содержать целочисленные значения, например 10, 20 и т. д. Тип NSInteger может содержать как положительные, так и отрицательные значения, но тип NSUInteger является беззнаковым, на что указывает буква U в его названии. Не забывайте, что слово «беззнаковый» в терминологии языков программирования означает, что число ни при каких условиях не может быть отрицательным. Отрицательные значения могут содержаться только в числовом типе со знаком.

• CGFloat. Содержит числа с плавающей точкой, имеющие десятичные знаки, например 1.31 или 2.40.

• NSString. Позволяет сохранять символьные строки. Такие примеры мы рассмотрим далее.

• NSNumber. Позволяет сохранять числа как объекты.

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

• NSDictionary и NSMutableDictionary. Это соответственно неизменяемый и изменяемый варианты хеш-таблиц. В хеш-таблице вы можете хранить ключ и ассоциировать этот ключ со значением. Например, ключ phone_num может иметь значение 0 55524 87700. Для считывания значений достаточно ссылаться на ассоциированные с ними ключи.

• NSArray и NSMutableArray. Неизменяемые и изменяемые массивы объектов. Массив – это упорядоченная коллекция элементов. Например, у вас может быть 10 строковых объектов, которые вы хотите сохранить в памяти. Для этого хорошо подойдет массив.

• NSSet, NSMutableSet, NSOrderedSet, NSMutableOrderedSet. Это типы множеств. Множества напоминают массивы тем, что могут содержать в себе наборы объектов, но в отличие от массива множество может включать в себя только уникальные объекты. Массив может содержать несколько экземпляров одного и того же объекта, а в множестве каждый объект может присутствовать только в одном экземпляре. Рекомендую вам четко усвоить разницу между массивами и множествами и использовать их правильно.

• NSData и NSMutableData. Неизменяемые и изменяемые контейнеры для любых данных. Такие типы данных очень вам пригодятся, если вы, например, хотите выполнить считывание содержимого файла в память.


Одни из рассмотренных нами типов данных являются примитивами, другие – классами. Вам придется просто запомнить, какие из них относятся к каждой из категорий. Например, тип данных NSInteger является примитивом, а NSString – классом. Поэтому из NSString можно создавать объекты. В языке Objective-C, как и в C и C++, существуют указатели. Указатель – это тип данных, в котором сохраняется адрес в памяти. По этому адресу уже хранятся фактические данные. Вы уже, наверное, знаете, что указатели на классы обозначаются символом астериска (*):


NSString *myString = @"Objective-C is great!";


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


/* Присваиваем переменной myFloat значение PI */

CGFloat myFloat = M_PI;


Если вам нужен указатель на эту переменную, соответствующую числу с плавающей точкой, то вы можете поступить так:


/* Присваиваем переменной myFloat значение PI */

CGFloat myFloat = M_PI;


/* Создаем переменную указателя, которая направлена на переменную myFloat */

CGFloat *pointerFloat = &myFloat;


Мы получаем данные от исходного числа с плавающей точкой путем простого разыменования (myFloat). Если получение значения происходит с применением указателя, то требуется использовать астериск (*pointerFloat). В некоторых ситуациях указатели могут быть полезны – например, при вызове функции, которая задает в качестве аргумента значение с плавающей точкой, а вы хотите получить новое значение после возврата функции.

Но вернемся к теме классов. Пожалуй, следует разобраться с ними немного подробнее, пока мы окончательно не запутались. Итак, приступим.

<p>Как создавать классы и правильно пользоваться ими</p>

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

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

• Класс должен иметь имя, соответствующее Соглашению об именованиях методов в Cocoa.

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

• У класса должна быть реализация, в которой вы прописываете все возможности, которые вы «обещали» предоставить согласно интерфейсу класса.

NSObject – это корневой класс, от которого наследуют практически все другие классы. В этом примере мы собираемся добавить класс под названием Person в проект, который был создан в подразделе «Создание и запуск вашего первого приложения для iOS» данного раздела. Далее мы добавим к этому классу два свойства, firstName и lastName, которые относятся к типу NSString. Выполните следующие шаги, чтобы создать класс Person и добавить его в ваш проект.

1. Откройте проект в Xcode и в меню File (Файл) выберите New-File (Новый– Файл).

2. Убедитесь, что слева, в разделе iOS, вы выбрали категорию Cocoa Touch. После этого выберите элемент Objective-C Class (Класс для Objective-C) и нажмите Next (Далее).

3. В разделе Class (Класс) введите имя Person.

4. В разделе Subclass of (Подкласс от) введите NSObject.

Когда справитесь с этим, нажмите кнопку Next (Далее). На данном этапе Xcode предложит вам сохранить этот файл. Просто сохраните новый класс в том каталоге, где находятся ваш проект и все его файлы. Это место выбирается по умолчанию. Затем нажмите кнопку Create (Создать) – и дело сделано.

После этого в ваш проект будут добавлены два новых файла: Person.h и Person.m. Первый файл – это интерфейс вашего класса Person, а второй – файл реализации этого класса. В Objective-C.h-файлы являются заголовочными. В таких файлах вы определяете интерфейс каждого файла. В.m-файле пишется сама реализация класса.

Теперь рассмотрим заголовочный файл нашего класса Person и определим для этого класса два свойства, имеющие тип NSString:


@interface Person: NSObject


@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *lastName;


@end


Как и переменные, свойства определяются в особом формате в следующем порядке.

1. Определение свойства должно начинаться с ключевого слова @property.

2. Затем следует указать квалификаторы свойства. Неатомарные (nonatomic) свойства не являются потокобезопасными. О безопасности потоков мы поговорим в главе 14. Вы можете указать и другие квалификаторы свойств: assign, copy, weak, strong или unsafe_unretained. Чуть позже мы подробнее поговорим и о них.

3. Затем укажите тип данных для свойства, например NSInteger или NSString.

4. Наконец, не забудьте задать имя для свойства. Имена свойств должны соответствовать рекомендациям Apple.

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

strong – свойства этого типа будут сохраняться во время исполнения. Они могут быть только экземплярами классов. Иными словами, вы не можете сохранить значение в свойстве типа strong, если значение является примитивом. Можно сохранять объекты, но не примитивы.

• copy – аналогичен strong, но при выполнении присваивания к свойствам этого типа среда времени исполнения будет делать копию объекта в правой части операции присваивания. Объект, находящийся в правой части этой операции, должен соответствовать протоколу NSCopying или NSMutableCopying.

• assign – значения объектов или примитивов, задаваемые в качестве значения свойства типа assign, не будут копироваться или сохраняться этим свойством. Для свойств примитивов этот квалификатор будет создавать адрес в памяти, в котором вы сможете поместить информацию примитива. В случае с объектами свойства такого типа будут просто указывать на объект в правой части равенства.

• unsafe_unretained – аналогичен квалификатору assign.

• weak – практически аналогичен квалификатору assign, но с одним большим отличием. При работе с объектами, когда объект, присвоенный свойству такого типа, высвобождается из памяти, среда времени исполнения будет автоматически устанавливать значение этого свойства в nil.

Итак, у нас есть класс Person с двумя свойствами, firstName и lastName. Вернемся к файлу реализации делегата нашего приложения (AppDelegate.m) и создадим объект типа Person:


#import "AppDelegate.h"

#import "Person.h"

@implementation AppDelegate


– (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


Person *person = [[Person alloc] init];


person.firstName = @"Steve";

person.lastName = @"Jobs";


self.window = [[UIWindow alloc]

initWithFrame: [[UIScreen mainScreen] bounds]];

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}


В этом примере мы выделяем и инициализируем наш экземпляр класса Person. Возможно, вы еще не понимаете, что это значит, но в подразделе «Добавление функционала к классам с помощью методов», приведенном далее, мы подробно об этом поговорим.

<p>Добавление нового функционала к классам с помощью методов</p>

Методы – это строительные блоки, из которых состоят классы. Например, класс Person может иметь логические возможности – обозначим их как «ходить», «дышать», «есть» и «пить». Обычно такие функции инкапсулируются в методах.

Метод может принимать параметры. Параметры – это переменные, передаваемые вызывающей стороной при вызове метода и видимые только этому методу. Например, в упрощенном мире у нашего класса Person был бы метод walk. Но вы могли бы добавить к этому методу параметр или аргумент и назвать его walkingSpeed. Этому параметру вы бы присвоили тип CGFloat. Теперь, если другой программист вызовет этот метод в вашем классе, он может указать, с какой скоростью будет идти Person. Вы как автор класса напишете соответствующий код, который будет обрабатывать различные скорости ходьбы Person. Не переживайте, если у вас возникает ощущение «как-то много работы получается». Рассмотрим следующий пример. В нем я добавил метод в файл реализации того класса Person, который мы создали в подразделе «Как создавать классы и правильно пользоваться ими» данного раздела.


#import "Person.h"


@implementation Person


– (void) walkAtKilometersPerHour:(CGFloat)paramSpeedKilometersPerHour{

/* здесь пишем код для этого метода */

}


– (void) runAt10KilometersPerHour{

/* Вызываем метод walk в нашем собственном классе и передаем значение 10 */

[self walkAtKilometersPerHour:10.0f];

}

@end


Типичный метод в языке Objective-C имеет следующие качества.

1. Префикс указывает компилятору, является ли данный код методом экземпляра (—) или методом класса (+). К методу экземпляра можно обратиться лишь после того, как программист выделит и инициализирует экземпляр вашего класса. Получить доступ к методу класса можно, вызвав его непосредственно из этого класса. Не волнуйтесь, если на первый взгляд это кажется сложным. В этой книге мы рассмотрим многочисленные примеры методов, пока просто следите за ходом рассказа.

2. Тип данных для метода, если метод возвращает какое-либо значение. В примере мы указали тип данных void. Так мы сообщаем компилятору, что не собираемся возвращать от метода какое-либо значение.

3. Первая часть имени метода, за которой идет первый параметр. Метод может и не иметь параметров. Методы, не принимающие параметров, довольно широко распространены.

4. Список последующих параметров, идущих за первым.

Рассмотрим пример метода с двумя параметрами:


– (void) singSong:(NSData *)paramSongData loudly:(BOOL)paramLoudly{

/* Параметры, к которым мы можем обратиться здесь, в этом методе, таковы:


paramSongData (для доступа к информации о песне)

paramLoudly сообщает нам, должны мы петь песню громко или нет

*/

}


Важно учитывать, что каждый параметр каждого метода обладает внешним и внутренним именем. Внешнее имя входит в состав метода, а внутреннее имя – это фактическое название (или псевдоним) параметра, которое может использоваться в пределах реализации метода. В предыдущем примере внешнее имя первого параметра – singSong, а внутреннее – paramSongData. Внешнее имя второго параметра – loudly, а внутреннее – paramLoudly. Имя метода и внешние имена его параметров вместе образуют сущность, которая называется селектором метода. В данном случае селектор упомянутого метода будет иметь вид singSong: loudly:. Как будет объяснено далее в этой книге, селектор является идентификатором каждого метода в среде времени исполнения. Никакие два метода в рамках одного и того же класса не могут иметь одинаковые селекторы.

В нашем примере мы определили в файле реализации класса Person (Person.m) три метода:

walkAtKilometersPerHour:;

• runAt10KilometersPerHour;

• singSong: loudly:.

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


#import 


@interface Person: NSObject


@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *lastName;


– (void) walkAtKilometersPerHour:(CGFloat)paramSpeedKilometersPerHour;

– (void) runAt10KilometersPerHour;


/* Не предоставляем метод singSong: loudly: для доступа извне.

Этот метод является внутренним для нашего класса. Зачем же нам открывать к нему доступ? */


@end


Имея такой файл интерфейса, программист может вызывать методы walkAtKilometersPerHour: и runAt10KilometersPerHour извне класса Person. А метод singSong: loudly: так вызывать нельзя, поскольку он не предоставлен в файле интерфейса. Итак, продолжим: попробуем вызвать все три этих метода из делегата нашего приложения и посмотрим, что получится:


– (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


Person *person = [[Person alloc] init];


[person walkAtKilometersPerHour:3.0f];

[person runAt10KilometersPerHour];


/* Если раскомментировать следующую строку кода, то компилятор выдаст

вам ошибку и сообщит, что такого метода в классе Person не существует */

//[person singSong: nil loudly: YES];


self.window = [[UIWindow alloc]

initWithFrame: [[UIScreen mainScreen] bounds]];

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}


Итак, теперь мы умеем определять и вызывать методы экземпляров. А что насчет методов классов? Сначала разберемся, что такое методы классов и чем они отличаются от методов экземпляров.

Метод экземпляра – это метод, относящийся к экземпляру класса. Например, в нашем случае вы можете создать экземпляр класса Person дважды и получить в гипотетической игре, которую разрабатываете, двух разных персонажей. Один персонаж будет ходить со скоростью 3 км/ч, другой – 2 км/ч.

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

Напротив, методы класса работают только с самим классом. Например, в вашей игре есть экземпляры класса Light, отвечающего за подсвечивание сцен в вашей игре. У этого класса может быть метод dimAllLights. Вызвав этот метод, программист погасит в игре все источники света независимо от того, где они находятся. Рассмотрим пример метода класса, применяемого с нашим классом Person:


#import "Person.h"


@implementation Person


+ (CGFloat) maximumHeightInCentimeters{

return 250.0f;

}


+ (CGFloat) minimumHeightInCentimeters{

return 40.0f;

}


@end


Метод maximumHeightInCentimeters – это метод класса, возвращающий гипотетический максимальный рост любого персонажа в сантиметрах. Метод класса minimumHeightInCentimeters возвращает минимальный рост любого персонажа. Вот как мы предоставим оба этих метода в файле интерфейса нашего класса:


#import 


@interface Person: NSObject


@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *lastName;

@property (nonatomic, assign) CGFloat currentHeight;


+ (CGFloat) maximumHeightInCentimeters;

+ (CGFloat) minimumHeightInCentimeters;


@end

Мы добавили к нашему классу Person еще одно свойство, принимающее значения с плавающей точкой. Оно называется currentHeight. С его помощью экземпляры этого класса могут хранить информацию о своей высоте в памяти (для справки) – точно так же, как имя и фамилию.

А в делегате нашего приложения мы продолжим работать с методами вот так:

– (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


Person *steveJobs = [[Person alloc] init];

steveJobs.firstName = @"Steve";

steveJobs.lastName = @"Jobs";

steveJobs.currentHeight = 175.0f; /* Сантиметры */


if (steveJobs.currentHeight >= [Person minimumHeightInCentimeters] &&

steveJobs.currentHeight <= [Person maximumHeightInCentimeters]){

/* Высота этого персонажа находится в пределах допустимого */

} else {

/* Высота этого персонажа находится вне пределов допустимого */

}


self.window = [[UIWindow alloc]

initWithFrame: [[UIScreen mainScreen] bounds]];

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}

<p>Соблюдение требований, предъявляемых другими классами, с помощью протоколов</p>

В языке Objective-C существует концепция под названием «протокол». Протоколы встречаются и во многих других языках, но называются везде по-разному; например, в Java аналогичная сущность называется «интерфейс». Как понятно из названия, протокол – это набор правил, которым класс должен соответствовать, чтобы его можно было использовать тем или иным образом. Если класс выполняет правила определенного протокола, то принято говорить, что он соответствует этому протоколу. Протоколы отличаются от самих классов тем, что не имеют реализации. Это просто правила. Например, у любой машины есть колеса, дверцы и цвет кузова, а также многие другие свойства. Определим эти свойства в протоколе Car. Просто выполните следующие шаги, чтобы создать заголовочный файл, который может содержать наш протокол Car.

1. Откройте ваш проект в Xcode и в меню File (Файл) выберите New-File (Новый – Файл).

2. Убедитесь, что слева, в разделе iOS, вы выбрали категорию Cocoa Touch. После этого выберите элемент Objective-C Protocol (Протокол для Objective-C) и нажмите Next (Далее).

3. В разделе Class (Класс) введите имя Car, затем нажмите кнопку Next (Далее).

4. Далее система предложит вам сохранить ваш протокол на диске. Просто выберите для этого место (как правило, в каталоге с вашим проектом) и нажмите кнопку Create (Создать).

После этого Xcode создаст для вас файл Car.h с таким содержимым:


#import 


@protocol Car 

@end


Продолжим и определим свойства для протокола Car, как мы обсуждали ранее в этом разделе:


#import 


@protocol Car 


@property (nonatomic, copy) NSArray *wheels;

@property (nonatomic, strong) UIColor *bodyColor;

@property (nonatomic, copy) NSArray *doors;


@end


Теперь, когда наш протокол определен, создадим класс, обозначающий автомобиль, – например, Jaguar, – а потом обеспечим соответствие этого класса протоколу. Просто выполните все шаги, перечисленные в подразделе «Как создавать классы и правильно пользоваться ими» данного раздела, после чего обеспечьте его соответствие протоколу Car следующим образом:


#import 

#import "Car.h"


@interface Jaguar: NSObject 

@

end


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

Auto property synthesis will not synthesize property declared in a protocol

Это означает, что ваш класс Jaguar пытается соответствовать протоколу Car, но на самом деле не реализует всех требуемых свойств и/или методов, описанных в этом протоколе. Теперь вы уже знаете, что в протоколе могут содержаться необходимые и факультативные (опциональные) элементы, которые вы помечаете ключевыми словами @optional или @required. По умолчанию действует квалификатор @required, и поскольку мы явно не указываем квалификатор для этого протокола, компилятор неявно выбирает @required за нас. Следовательно, класс Jaguar теперь обязан реализовывать все аспекты, требуемые протоколом Car, вот так:


#import 

#import "Car.h"


@interface Jaguar: NSObject 


@property (nonatomic, copy) NSArray *wheels;

@property (nonatomic, strong) UIColor *bodyColor;

@property (nonatomic, copy) NSArray *doors;


@end


Отлично. Теперь мы понимаем основы работы с протоколами, то, как они работают и как их определить. Далее в этой книге мы подробнее поговорим о протоколах, а на данный момент вы получили довольно полное представление о них.

<p>Хранение элементов в коллекциях и получение элементов из коллекций</p>

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


NSArray *stringsArray = @[

@"String 1",

@"String 2",

@"String 3"

];


__unused NSString *firstString = stringsArray[0];

__unused NSString *secondString = stringsArray[1];

__unused NSString *thirdString = stringsArray[2];


  • Страницы:
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15