Разработка под Apple. Шаг второй

«Каждый паттерн описывает некую повторяющуюся проблему и ключ к ее разгадке, причем таким образом, что этим ключом можно пользоваться при решении самых разнообразных задач».
Christopher Alexander

Сегодня выходит вторая статья, посвященная разработке под iOS. На прошлой неделе мы рассмотрели основные инструменты, которые будем использовать для разработки, а также написали наше первое приложение, в котором использовались два компонента: контроллер (FirstLaunchViewController) и вид (FirstLaunchViewController.xib). Прежде чем приступить к изучению синтаксиса Objective-C рассмотрим паттерн проектирования, который мы будем применять для разработки под iOS/Mac OS — Model-View-Controller или MVC.

Разработка под Apple. Шаг второй. Christopher Alexander. Фото.

Язык программирования Objective-C [часть 1]

Паттерн (шаблон) проектирования — это повторяемая архитектурная конструкция приложения. В нашем случае шаблон показывает отношения и взаимодействия между классами или объектами.

Упрощенно мы можем представить взаимодействие элементов MVC как данные (Model), которые мы отображаем в нашем интерфейсе (View) с помощью контроллера (Controller).

Таким образом мы разделяем всю нашу программу на три «лагеря»:

•  Model — что представляет из себя наше приложение, но не как оно отображается. То есть, если бы мы создавали игру «Космический бой», в модели хранилась бы информация о типе кораблей, уроне от оружия, броне и тп.

•  Controller — как приложение отображается. Так, например, если мы создаем универсальное приложение для iPhone/iPad, у нас будет одна модель, но разные контроллеры.

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

На схеме ниже представлено взаимодействие элементов MVC.

Разработка под Apple. Шаг второй. Язык программирования Objective-C [часть 1]. Фото.

Зеленые стрелки отображают инициатора взаимодействия (Controller), а не направление передачи данных. Model и View не взаимодействуют напрямую друг с другом. Controller может напрямую отправлять сообщения модели, и модель также может передавать данные обратно контроллеру, но только в виде возвращаемых значений в ответе на полученное сообщение.

Тем не менее, существует один способ, когда Model может сообщить контроллеру о том, что данные изменились, например корабль сдвинулся (расположенный вне экрана), или котировки ценных бумаг изменились и надо обновить наш View, или какой-то их фоновых процессов завершился и надо сообщить об этом пользователю. Для таких случаев используется система уведомлений Notification & KVO (Key Value Observing). Model также не знает ничего о том как эти данные будут отображаться, она просто сообщает об изменениях контроллеру, который решает, что с ними дальше делать.

Разработка под Apple. Шаг второй. Язык программирования Objective-C [часть 1]. Фото.

Controller может напрямую взаимодействовать с интерфейсом (View). В примере прошлой статьи мы определяли два типа элементов в Interface Builder IBOutlet, IBAction. Первый — это расположение наших окон (window), видов (UIView), текстовых полей (UILabel, UIText), кнопок (надписи на кнопках также являются объектами UILabel). Контроллер «расставляет» эти элементы, устанавливает первоначальные значения и меняет их во время выполнения приложения.

Но контроллер также реагирует на наши действия в интерфейсе (помните, в нашем примере при изменении положения слайдера информация об этом отправлялась контроллеру). Однако, View не взаимодействует с контроллером напрямую. Значительная часть интерфейса может быть разработана кем-то другим (Apple Inc, например), и наш интерфейс генерируется контроллером, поэтому View не знает о том, что происходит в нашем контроллере.

Существует три способа, для взаимодействия, направленного от View к контроллеру.

1)  Первый называется Target Action. В контроллере мы определяем цель (Target), например, нажатие кнопки. И когда это событие происходит View отправляет действие (Action) нашей цели (Target):

Разработка под Apple. Шаг второй. Язык программирования Objective-C [часть 1]. Фото.

2)  Второй способ называется делегирование (Delegate). Он используется когда нам необходимо синхронизировать данные View с нашим Controller. Многие View имеют инстанс-переменную, которая называется delegate, и контроллер отправляет сообщение виду о том, что он будет его представителем (Delegate). Как правило в данном случае View получает информацию о трех состояниях: должен сделать, сделал, сделаю. Например, когда мы скроллим на iPhone и View передает информацию о текущей позиции, а также «спрашивает», какие данные он должен отобразить при скролле.

Если данный способ пока непонятен — ничего страшного, в дальнейших примерах мы его обязательно рассмотрим.

Разработка под Apple. Шаг второй. Язык программирования Objective-C [часть 1]. Фото.

View никогда не хранят отображаемые ими данные. И для этого есть несколько причин:

•  Скорость работы — экран, в особенности экран iPhone, ограничен по размеру и отображает незначительную часть информации, в нашей игре он будет отображать 2–3 корабля, и ему не нужно будет беспокоиться об остальных 20ти.

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

Таким образом, мы подошли к третьему способу.

3)  Третий способ называется Data Source, он похож на предыдущий. Как понятно из названия, таким образом View получает данные у контроллера, который в свою очередь берет их у модели. Самостоятельно контроллер также не хранит данные. Этот способ используется, например в Контактах на айфоне, когда View отображает информацию об общем количестве записей или хочет получить данные из определенной ячейки таблицы. Мы также будем использовать этот вариант в дальнейших наших примерах.

Разработка под Apple. Шаг второй. Язык программирования Objective-C [часть 1]. Фото.

C Programming Language

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

Структура программы

Программа, составленная на языке Си может содержать одну или более функций, которые представляют собой ряд последовательных инструкций. Многие функции (к примеру, извлечение квадратного корня), которые нам понадобятся, уже написаны и находятся в библиотеках (например, в библиотеках, входящих в iOS SDK).

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

function(параметры)

{              Начало функции

…;          Здесь помещаются инструкции, которые могут использовать «параметры»

}             Здесь функция заканчивается

Из функции мы можем вызывать другую функцию. Комментарии отделяются либо знаком “ // ”, и весь текст до конца строки считается комментарием, либо помещаются между /* и */. Тогда весь текст внутри данных символов считается комментарием.

Точку с запятой “ ; ” необходимо ставить после каждой отдельной инструкции.

Типы данных

Мы рассмотрим четыре типа данных, которые будем использовать:

int: целые числа от –32768 до +32767

float: число с плавающей точкой от –3.4Е–38 до 3.4E+38

double: число с плавающей точкой от 1.7E–308 до 1.7E+308

char: символьная переменная, которая может хранить один символ — букву, цифру или другой символ.

Переменные и константы

Переменная задается <тип данных> название_переменной:

int yearsOld;

float tempCelcium;

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

Константа задается с помощью директивы #define ИМЯ_КОНСТАНТЫ, помещенной перед функцией main(). При этом имя константы состоит из заглавных букв:

#define NUMBER_PI 3.14

Далее в программе мы сможем использовать NUMBER_PI вместо значения 3.14. Если переменной можно при выполнении программы присваивать различные значение, то константа имеет постоянное значение.

Операторы

В Си используются следующие основные операторы:

+  сложение

вычитание

*  умножение

/ деление

% получение остатка от деления нацело (например, 13%6 = 1, или 178/12 = 10, или 21%7 = 0).

Операторы присваивания

variable = 3 + 5; //операция присваивания, в результате которой variable = 8.

Cуществуют сокращенные операторы присваивания:

variable += 5; эквивалентна variable = variable + 5;

variable –= 5; эквивалентна variable = variable – 5;

variable *= 5; эквивалентна variable = variable * 5;

variable /= 5; эквивалентна variable = variable / 5;

variable %= 5; эквивалентна variable = variable % 5;

Условия и циклы

Рассмотрим инструкции, используемые для построения логики программы.

Инструкция if

Проверяет условие, и если оно выполняется — выполняет участок кода:

if (условие)

{

…; //инструкции

}

для проверки условия используются:

а == b — равно

a > b — больше

a < b — меньше

a >= b — больше или равно

a <= b — меньше или равно

Если условие не выполняется, то содержимое скобок {…;} игнорируется. Однако, можно задать выполнение других инструкций в случае невыполнения условия с помощью конструкции if…else:

if(a >=0) {

float result = sqrt(a);

return result;

} else {

float result = sqrt(–a);

return result;

}

Таким образом, мы извлекли квадратный корень с помощью Си функции sqrt, но перед этим проверили чтобы значение a было положительным, если же оно отрицательное, мы извлекли sqrt из “–а”.

Конструкция switch/case/default

Если нам нужно учесть более трех возможных вариантов, конструкция if может оказаться слишком перегруженной. Для таких случаев целесообразнее применить конструкцию switch/case/default:

errorCode = какой-то код ошибки от 1 до 10;

switch(errorCode) {

case ‘1’:

//ошибка №1: неправильно…

break;

case ‘2’:

//ошибка №2: убедитесь…

break;

………

……… //другие case ‘#’:

………

case ‘10’:

//ошибка №10: попробуйте выполнить действия…

break;

default:

//неизвестная ошибка

}

Таким образом значение errorCode сравнивается со значениями, стоящими после case и в соответствие с ними выполняются определенные инструкции. break говорит о прекращении цикла после того, как соответствие найдено. default содержит инструкции на случай, если ни одного совпадения значение errorCode и аргумента после case не будет найдено.

Цикл for

Используется когда известно точное количество повторов, которое нужно выполнить:

for (repeat = 1; repeat <= 10; repeat++) {

//выполняется какой-то код, например печать значение repeat

}

в результате мы получим числа от 1 до 10.

Агрументы в for (инициилизируем начальное значение; указываем условие; устанавливаем приращение).

Цикл do…while

Аналогичен циклу for, только условие проверяется в конце, таким образом цикл выполнится как минимум один раз:

do

{

int stopAtTen;

stopAtTen = 1;

stopAtTen++;

//печать stopAtTen

}

while (stopAtTen <=10);

В результате напечатаются числа от 1 до 11, так как условие, когда stopAtTen > 10 и цикл должен будет прекратиться, проверится после выполнения программы.

Цикл while

Аналогичен циклу do…while, только проверка условия происходит перед началом цикла:

while (условие) {

//инструкции;

}

Массивы (Arrays)

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

int someNumbers[5]; //массив из 5ти элементов от 0 до 4 типа int.

Нумерация элементов в массиве начинается с 0. Ввод значений в массив может осуществляться как по-отдельности:

someNumbers[0] = 34; //теперь нулевой элемент равен 34,

либо сразу всем элементам при определении массива:

int someNumbers[5] = {34, 45, 12, 98, 12};

Строка — это массив символьных значений:

char someName[20] = “Иванов Иван Иванович”;

Структуры

Структура — это взаимосвязанное множество переменных. Определяется структура ключевым словом struct:

struct название_структуры {

тип_данных название_элемента_структуры;

тип_данных название_элемента_структуры_2;

тип_данных название_элемента_структуры_3;

};

Например,

struct CD {

char name[20];

char description[40];

float cost;

int year;

};

Теперь мы можем создать переменную disc:

struct CD disc = {“Избранное”, “Мой playlist из iPod”, 125.30, 2011};

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

disc.name; //мы указали имя структурной переменной, поставили точку и указали имя элемента структуры, в результате получили значение “Избранное”.

Указатели

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

Однако, мы можем получить адрес переменной в памяти, где хранится её значение, используя оператор получения адреса (& — амперсанд) перед названием переменной:

addressOfName = &name; //получаем адрес переменной name.

Указателем называется переменная, которая содержит значение адреса элемента памяти, где хранится значение другой переменной. Для создания указателя используется синтаксис:

type *variableName;

type — тип переменной, адрес которой будет содержаться в указателе.

*  — звездочка определяет указатель.

variableName — имя переменной типа «указатель».

С помощью указателя мы можем не только получить адрес какой-либо переменной, но также и ее значение:

int *taxPointer;

int tax;

taxPointer = &tax; //мы получили значение адреса переменной tax с помощью &

tax = 25; //присвоим значение переменной tax

напечатать taxPointer; //напечатается адрес переменной tax

напечатать *taxPointer;  //напечатается значение 25

tax = 40;

напечатать *taxPointer; //напечатается значение 40

Таким образом, мы можем получать значение переменной tax, зная ее адрес в памяти, и в случае изменения значения переменной tax мы получаем ее последнее значение, так как taxPointer хранит не значение переменной tax, а её адрес.

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

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

Ссылки:

Практический курс по Си (https://kpolyakov.narod.ru/school/c.htm)

Керниган, Ричи. Язык C (https://lib.ru/CTOTOR/kernigan.txt)