Skip to content

Latest commit

 

History

History
1264 lines (888 loc) · 135 KB

Russian.md

File metadata and controls

1264 lines (888 loc) · 135 KB

UIKit

Чем отличаются bounds и frame?

frame – это прямоугольник описываемый положением location(x, y) и размерами size (width, height) вьюхи относительно ее superview в которой она содержится.
bounds – это прямоугольник описываемый положением location(x, y) и размерами size (width, height) вьюхи относительно ее собственной системы координат (0, 0).

Когда bounds будет отлично от нуля?

Рассмотрим на примере UIScrollView: Bounds будет отлично от нуля, когда contentOffset у скролла не равен (0;0)

Как работает UITableView?

Ячейки таблицы, которые больше не отображаются на экране, не выбрасываются из памяти. Их можно использовать повторно, указав идентификатор в процессе инициализации. Когда ячейка, отмеченная для повторного использования, пропадает с экрана, UITableView помещает ее в очередь для повторного использования в дальнейшем. Когда dataSource запрашивает у UITableView новую ячейку и указывает идентификатор, UITableView сначала проверяет очередь ячеек для повторного использования на предмет наличия необходимой. Если ячейка не была обнаружена, то создается новая, которая затем передается dataSource'у.

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

Разница между points и pixels?

-Pixels (px) - точки на экране. -Points (pt) - плотность точек на экране.

Как поймать неприятный лаг? На что обращать внимание?

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

Причиной торможения может быть:

  • Перегруженный main thread
  • Инстанциирующиеся ячейки. Если у вас таблица состоит больше, чем из одного вида ячеек, то при отсутствии в очереди нужной, она сначала создастся, это требует ресурсов. Особенно при разархивации из nib
  • Все касающееся прорисовки, подсчет высоты и переиспользуемые ресурсы
  • Тени и большое количество blur
  • Дробные значения фреймов у subviews

Autolayout

Auto Layout занимается динамическим вычислением позиции и размера всех view на основе constraints — правил заданных для того или иного view. Самый большой и очевидный плюс для разработчика в использовании Auto Layout в том, что исчезает необходимость в подгонке размеров приложения под определенные устройства. Auto Layout изменяет интерфейс в зависимости от внешних или внутренних изменений. Минус Auto Layout состоит в том, что вычисление конкретных значений сводится к задаче решения системы линейных уравнений, поэтому добавление каждого нового констрейнта ощутимо увеличивает сложность расчета конкретных значений.

SDK

NSCoding, archiving

NSCoder — это абстрактный класс, который преобразует поток данных. Используется для архивации и разархивации объектов. Протокол <NSCoding> позволяет реализовать архивирование или разархивирование данных. Например, у нас есть обьект мы его можем сохранить, а при следующей загрузке приложения подгрузить обратно. Часто программе требуется хранить состояние объектов в файле для дальнейшего их полного либо частичного восстановления, а также работы с ними. Такой процесс называют сериализацией. Многие современные языки и фреймворки предоставляют для этого вспомогательные средства.

Сохранить состояние объекта в Cocoa Framework можно двумя способами при помощи:

  • архивации (archivation)
  • сериализации (serialization)

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

  • encodeWithCoder : кодирует приемник с помощью данного архиватора. (обязательный)
  • encodeWithCoder:(NSCoder *)encoder  
  • initWithCoder : возвращает объект инициализированный из данных в данном разархиваторе
  • initWithCoder:(NSCoder *)decoder

Создание архивов

Самый простой способ создать архив - использовать метод archiveRootObject:toFile: архиватора. Этот метод класса создает временный экземпляр архиватора и записывает объект в файл.

MapView *myMapView;
result = [NSKeyedArchiver archiveRootObject:myMapView toFile:@"/tmp/MapArchive"];

Чтение архивов

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

MapView *myMapView;
myMapView = [NSKeyedUnarchiver unarchiveObjectWithFile:@"/tmp/MapArchive"];

Второй метод предполагает создание экземпляра объекта NSKeyedUnarchiver.

Как работают push-уведомления?

Push- уведомление — это короткое сообщение, состоящее из токена девайса, полезной нагрузки (payload) и ещё некоторой информации. Полезная нагрузка — это актуальные данные, которые будут отправляться на девайс. Схема работы выглядит следующим образом:

  1. Устройство запрашивает у Apple Push Notification Service (APNS) token своеобразный ключ, который можно считать «адресом»
  2. Приложение отправляет token на сервер, который занимается отправкой push-уведомлений.
  3. Когда произойдёт какое-либо событие для вашего приложения, сервер отправит push-уведомление в APNS.
  4. APNS отправит push-уведомление на девайс пользователя.

Для разработки push-уведомлений, надо учитывать следующие моменты:
• Push-уведомления не работают в симуляторе, поэтому для тестирования нужен настоящий девайс.
• Регистрация в iOS Developer Program. Для каждого приложения, в котором будет интегрирован механизм push-уведомлений, необходимо создать новый App ID и provisioning profile для сборки приложений, а также SSL-сертификат для отправки push-уведомлений. Эти действия выполняются на iOS Provisioning Portal. • Необходимо создать provisioning profile и SSL-сертификат.
• Опционально сервер, подключенный к интернету. Push-уведомления обычно отправляются сервером, собственным, или сторонним (например, Firebase). Также (в основном в целях отладки) push-уведомления можно отправлять прямо с компьютера, например, с помощью программы Pusher

Тонкие моменты при работе с push-уведомлениями:

  1. Нет гарантий, что push-уведомления будут доставлены, даже если APNS примет их.
  2. Как только ваш сервер сформировал push-уведомление, он безответно отправляет его в APNS. Нет способа узнать статус доставки уведомления конечному пользователю после отправки. Время доставки может варьироваться от нескольких секунд до получаса.
  3. APNS будет пытаться доставить последнее отправленное уведомление, когда девайс станет доступен для приёма. Но эти попытки ограничены по времени. После тайм-аута push-уведомление будет потеряно.

Memory warning

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

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
	/*
	Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later.
	*/
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

Жизненный цикл приложения

Not running (не запущенное) — приложение не было запущено или его работа была прекращена.
Inactive (неактивное) — приложение работает, но не принимает события (например, когда пользователь заблокировал телефон при запущенном приложении).
Active (активное) — нормальное состояние приложения при его работе.
Background (фоновое) — приложение больше не на дисплее, но оно все еще выполняет код.
Suspended (приостановленное) — приложение занимает память, но не выполняет код.

Жизненный цикл ViewController

load view — создает вью, которой управляет контроллер. Вызывается при создании контроллера. Вы можете переопределить этот метод, чтобы создать свои вью вручную.

viewDidLoad — вью создано и загружено в память, но нет bounds. Хорошее место для инициализации и настройки объектов, используемых во вью контроллере.

viewWillAppear — вью будет добавлено в иерархию, определены bounds, но ориентация экрана не определена. Вызывается каждый раз, когда появляется вью.

viewWillLayoutSubviews — вызывается каждый раз, когда frame изменился, например, при смене ориентации. Если вы не используете autoresizing masks или constraints, вы, вероятно, хотите обновить сабвью здесь.

viewDidLayoutSubviews — вызывается уведомить контроллер, что его вью только что залэйаутил сабвью.

viewDidAppear — вью добавлено в иерахию и появилось на экране. Хорошее место для выполнения задач, связанных с анимацией вью. Метод вызывается после того, как анимация загрузки вью закончена. Иногда хорошим кейсом в этом методе будет вытаскивать данные из кордаты и отображать на вью или запрашивать данные с сервера.

viewWillDissapear — вью уходит с экрана. Вызывается как при закрытии вью контроллера, так и при переходе дальше по иерархии, например, при пуше нового контроллера в NavigationController

viewDidDissapear — вью ушло с экрана. Вызывается как при закрытии вью контроллера, так и при переходе дальше по иерархии.

Паттерны

Observer

Паттерн, который помогает реагировать на изменения происходящие в объекте, – все подписанные на него объекты тут же узнают про изменение. Идея проста: объект который мы называем Subject – дает возможность другим объектам, которые реализуют интерфейс Observer, подписываться и отписываться от изменений происходящих в Subject. Когда изменение происходит – всем заинтерeсованным объектам высылается сообщение, что изменение произошло. В нашем случае – Subject – это издатель газеты, Observer это мы с вами – те кто подписывается на газету, ну и собственно изменение – это выход новой газеты, а оповещение – отправка газеты всем кто подписался.

Способы реализации паттерна Observer

Notification – механизм использования возможностей NotificationCenter самой операционной системы. Использование NSNotificationCenter позволяет объектам коммуницировать, даже не зная друг про друга. Это очень удобно использовать когда у вас в параллельном потоке пришел push-notification, или же обновилась база, и вы хотите дать об этом знать активному на даный момент View. Чтобы послать такое сообщение стоит использовать конструкцию типа:

NSNotification *broadCastMessage = [NSNotification notificationWithName:@"broadcastMessage" object:self];

Как видим мы создали объект типа NSNotification в котором мы указали имя нашего оповещения: "broadcastMessage", и собственно сообщили о нем через NotificationCenter. Чтобы подписаться на событие в объекте который заинтересован в изменении стоит использовать следующую конструкцию:

NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(update:) name:@"broadcastMessage" object:nil];

Мы подписываемся на событие и вызывается метод, который задан в свойстве selector.

Singleton

Существует в системе в единственном экземпляре => не может быть повторно создан. Объект, к которому обращаются много объектов. Примеры синглтонов в системе:

[NSUserDefaults standardUserDefaults];   
[UIApplication sharedApplication];   
[UIScreen mainScreen];   
[NSFileManager defaultManager];   

Минусы Singleton

  1. Глобальное состояние. Про вред глобальных переменных вроде бы уже все знают, но тут та же самая проблема. Когда мы получаем доступ к экземпляру класса, мы не знаем текущее состояние этого класса, и кто и когда его менял, и это состояние может быть вовсе не таким, как ожидается. Иными словами, корректность работы с синглтоном зависит от порядка обращений к нему, что вызывает неявную зависимость подсистем друг от друга и, как следствие, серьезно усложняет разработку.
  2. Зависимость обычного класса от синглтона не видна в публичном контракте класса. Так как обычно экземпляр синглтона не передается в параметрах метода, а получается напрямую, через GetInstance(), то для выявления зависимости класса от синглтона надо залезть в тело каждого метода — просто просмотреть публичный контракт объекта недостаточно.
  3. Наличие синглтона понижает тестируемость приложения в целом и классов, которые используют синглтон, в частности. Во-первых, вместо синглтона нельзя подставить Mock-объект, а во-вторых, если синглтон имеет интерфейс для изменения своего состояния, то тесты начинают зависеть друг от друга. Говоря же проще — синглтон повышает связность, и все вышеперечисленное, в том или ином виде, есть следствие повышения связности.

Lazy initialization

Приём в программировании, когда некоторая ресурсоёмкая операция (создание объекта, вычисление значения) выполняется непосредственно перед тем, как будет использован её результат. Таким образом, инициализация выполняется «по требованию», а не заблаговременно. Аналогичная идея находит применение в самых разных областях: например, компиляция «на лету» и логистическая концепция «Точно в срок». Частный случай ленивой инициализации — создание объекта в момент обращения к нему — является одним из порождающих шаблонов проектирования.

Достоинства

  • Инициализация выполняется только в тех случаях, когда она действительно необходима
  • Ускоряется начальная инициализация

Недостатки

  • Невозможно явным образом задать порядок инициализации объектов
  • Возникает задержка при первом обращении к объекту

Пример

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CellIdentifier";
    cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        // ленивая загрузка
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
	                              reuseIdentifier:CellIdentifier];
    }
    cell.textLabel.text = someText;
    return cell;
}

Factory Method

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

Responder chain

Это цепочка по которой проходит событие от отправителя к получателю, от First Responder, по иерархии контроллеров, до root view controller, window object и последнего - app object.

  • UIControl Actions (например, нажатие кнопки)
  • User events: (touches, shakes, motion, etc...)
  • System events: (low memory, rotation, etc...)

Архитектура

MVC

Концепция MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента:

  • Модель (англ. Model). Модель предоставляет знания: данные и методы работы с этими данными, реагирует на запросы, изменяя своё состояние. Не содержит информации, как эти знания можно визуализировать.
  • Представление, вид (англ. View). Отвечает за отображение информации (визуализацию). Часто в качестве представления выступает форма (окно) с графическими элементами.
  • Контроллер (англ. Controller). Обеспечивает связь между пользователем и системой: контролирует ввод данных пользователем и использует модель и представление для реализации необходимой реакции.

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

Активная модель

Активная модель — модель оповещает представление о том, что в ней произошли изменения, а представления, которые заинтересованы в оповещении, подписываются на эти сообщения. Это позволяет сохранить независимость модели как от контроллера, так и от представления.

Пассивная модель

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

MVVM

Этот паттерн удобен в проектах, где используются такие фреймворки, как ReactiveCocoa i RxSwift, в которых есть концепция «связывания данных» — связывание данных с визуальными элементами в двустороннем порядке. В этом случае, использование паттерна MVC является очень неудобным, поскольку привязка данных к представлению (View) — это нарушение принципов MVC.

View (ViewController) и Model имеют «посредника» — View Model. View Model — это независимое от UIKit представления View. View Model вызывает изменения в Model и самостоятельно обновляется с уже обновленным Model, и, так как связывание происходит через View, то View обновляется тоже.

Недостатком является то, что «вместо 1000 строк в ViewController может выйти 1000 строк в ViewModel». Также одна из проблем использования фреймворков для «реактивного программирования» — достаточно просто все поломать и может пойти очень много времени на багфиксинг. Кому-то может показаться, что RxSwift, например, упрощает написание кода, но достаточно заглянуть в стек вызовов друга «rx-» метода, чтобы оценить это «упрощение». Можно сюда же добавить проблемы с документацией и постоянные проблемы с автокомплитом в xCode.

MVP

MVP-паттерн «эволюционировал» из MVC и состоит из таких трех компонентов:

  • Presenter (независимый посредник UIKit)
  • Passive View (UIView и/или UIViewController)
  • Model

Этот паттерн определяет View как получающий UI-события от пользователя и тогда вызывает соответствующий Presenter, если это нужно. Presenter же отвечает за обновление View с новыми данными, полученными из модели.

Достоинства

  • лучшее разделение кода
  • хорошо тестируется

Недостатки

  • сравнительно с MVC имеет значительно больше кода
  • разработка и поддержка занимают больше времени

Viper

От вышеперечисленных паттернов отличается тем, что не относится к категории MVC. Вместо привычных 3-х слоев он предлагает 5:

  1. View
  2. Interactor
  3. Presenter
  4. Entity
  5. Router

View: отвечает за отображение данных на экране и оповещает Presenter о действиях пользователя. Как правило слоем View в данной модели является ViewController. Пассивен, сам никогда не запрашивает данные, только получает их от презентера. Не содержит в себе логики отображения: принимает от presenter готовые к отображению данные, например, готовые строки текста, и размещает их на себе. События пользователя передает в presenter, который их обрабатывает.

Interactor: содержит бизнес-логику, связанную с получением данных (Entities).

Presenter: получает от View информацию о действиях пользователя и преображает ее в запросы к Router’у, Interactor’у, а также получает данные от Interactor’a, подготавливает их и отправляет View для отображения

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

Router: несет ответственность за переходы между VIPER-модулями.

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

ООП

Киты ООП

Абстракция

– это придание объекту характеристик, отличающих от всех других объектов, при этом игнорируя ее некоторые детали. Имея дело с составным объектом, вы имеете дело с абстракцией. Если вы рассматриваете объект как «дом», а не как комбинацию стекла, древесины и гвоздей, вы прибегаете к абстракции. Если вы рассматриваете множество домов как «город», вы прибегаете к другой абстракции.

Инкапсуляция

– скрытие методов и переменных от других методов или переменных или других частей программы. Целособразно:

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

Наследование

– процесс, посредством которого один объект может приобретать свойства другого и добавлять к ним черты, характерные только для него. Польза наследования в том, что оно дополняет идею абстракции. Наследование упрощает программирование, позволяя создать универсальные методы для выполнения всего, что основано на общих свойствах дверей, и затем написать специфические методы для выполнения специфических операций над конкретными типами дверей. Некоторые операции, такие как Open() или Close(), будут универсальными для всех дверей: внутренних, входных, стеклянных, стальных — каких угодно.

@interface Unicycle : NSObject {
  Pedal *pedal;
  Tire *tire;
}
@end

Полиморфизм

– возможность объектов с одинаковой спецификацией иметь различную реализацию (использование одного имени для решения двух или более схожих, но технически разных задач). Если функция описывает разные реализации (возможно, с различным поведением) для ограниченного набора явно заданных типов и их комбинаций, это называется ситуативным полиморфизмом (ad hoc polymorphism). Ситуативный полиморфизм поддерживается во многих языках посредством перегрузки функций и методов. Если же код написан отвлеченно от конкретного типа данных и потому может свободно использоваться с любыми новыми типами, имеет место параметрический полиморфизм. Некоторые языки совмещают различные формы полиморфизма, порой сложным образом, что формирует самобытную идеологию в них и влияет на применяемые методологии декомпозиции задач. Например, в Smalltalk любой класс способен принять сообщения любого типа, и либо обработать его самостоятельно (в том числе посредством интроспекции), либо ретранслировать другому классу — таким образом, несмотря на широкое использование перегрузки функций, формально любая операция является неограниченно полиморфной и может применяться к данным любого типа.

Плюсы и минусы ООП

Общие положения

Преимущества

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

Недостатки

  • ООП порождает огромные иерархии классов, что приводит к тому, что функциональность расползается или, как говорят, размывается по базовым и производным членам класса, и отследить логику работы того или иного метода становится сложно.

Полиморфизм

Преимущества

  • Можно создавать новые классы с помощью протокола, «ведущие себя» аналогично родственным, что, в свою очередь, позволяет достигнуть расширяемости и модифицируемости, что помогает снижать сложность программ, разрешая использование того же интерфейса для задания единого класса действий. "Один интерфейс, множество методов"
  • Возможность создавать переменные и методы с одинаковым именем, но ведущие себя по-разному в зависимости от контекста (локальные переменные и перекрытие методов)
  • Обработка разнородных структур данных. Программы могут работать, не утруждая себя изучением вида объектов. Новые виды могут быть добавлены в любой момент.
for (id object in array) {
  NSLog(@"%@", object);
}
  • Реализация родовых компонент. Алгоритмы можно обобщать до такой степени, что они уже смогут работать более, чем с одним видом объектов. Доведение полуфабрикатов. Компоненты нет надобности подстраивать под определенное приложение. Их можно сохранять в библиотеке в виде полуфабрикатов (semifinished products) и расширять по мере необходимости до различных законченных продуктов. Расширение фреймворка. Независимые от приложения части предметной области могут быть реализованы в виде фреймворка и в дальнейшем расширены за счет добавления частей, специфичных для конкретного приложения.

Недостатки

  • На скорости выполнения программ может неблагоприятно сказаться реализация полиморфизма, которая основана на механизмах позднего связывания вызова метода с конкретной его реализацией в одном из производных классов.
  • Многоразовое использование требует от программиста познакомиться с большими библиотеками классов. А это может оказаться сложнее, чем даже изучение нового языка программирования. Библиотека классов фактически представляет собой виртуальный язык, который может включать в себя сотни типов и тысячи операций.
  • В некоторых языках все данные являются объектами, в том числе и элементарные типы, а это не может не приводить к дополнительным расходам памяти и процессорного времени.
  • Изменение поведения во время выполнения. На этапе выполнения один объект может быть заменен другим. Это может привести к изменению алгоритма, в котором используется данный объект.

Инкапсуляция

Преимущества

  • Сокрытие данных от несанкционированного доступа
  • Инкапсуляция повышает надежность работы программного кода, поскольку гарантирует, что определенные данные не могут быть изменены за пределами содержащего их класса.

Недостатки

  • Функции представляют собой черные ящики, которые трансформируют ввод в вывод. Если я понимаю ввод и вывод, то я понимаю функцию. Это не означает, что я могу написать эту функцию.
  • В то время как состояние в языках программирования является нежелательным, реальный мир богат на состояния. Я очень интересуюсь состоянием моего банковского счета. И когда я вкладываю или снимаю с него деньги, я ожидаю, что его состояние будет корректно обновлено. Выбранный ООЯП подход “спрятать состояние от программиста” является наихудшим возможным выбором. Вместо показа состояния и поиска путей для минимизации неудобств от него, они прячут его поглубже.
  • Очень трудно изучать классы, не имея возможности их «пощупать». Только с приобретением мало-мальского опыта можно уверенно себя почувствовать при работе с использованием ООП. Поэтому проектирование классов — задача куда более сложная, чем их использование. Проектирование класса, как и проектирование языка, требует большого опыта. Это итеративный процесс, где приходится учиться на своих же ошибках.

Наследование

Преимущества

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

Недостатки

  • Унаследовавшись от одного предка, класс уже не может наследоваться от других. Изменение предка так же становится опасным.
  • О степени понятности кода судить трудно. Чтобы точно знать, какие операции разрешены для новой структуры, программист должен рассмотреть объявление ис-ходной структуры. Трудность состоит в следующем: чтобы понять класс, сконструированный с помощью наследования, программист должен постоянно переключаться «взад-вперед» между двумя (или более) описаниями классов. Она известна как проблема «вверх-вниз». В сложных иерархиях классов поля и методы обычно наследуются с разных уровней. И не всегда легко определить, какие поля и методы фактически относятся к данному классу.
  • Наследование – уничтожение инкапсуляции. Любой класс всегда неявно объявляет свой интерфейс — то, что доступно при использовании класса извне. Если у нас есть класс Ключ и у него публичный метод Открыть, который вызывает приватные методы Вставить, Повернуть и Вынуть, то интерфейс класса Ключ состоит из метода Открыть. Когда мы унаследуем какой-то класс от класса Ключ, он унаследует этот интерфейс. Кроме этого интерфейса, у класса есть также реализация — методы Вставить, Повернуть, Вынуть и их вызов в методе Открыть. Наследники Ключа наследуют вместе с интерфейсом и реализацию. И вот здесь таятся проблемы.

Что такое SOLID?

SOLID (сокр. от англ. Single responsibility, Open-closed, Liskov substitution, Interface segregation и Dependency inversion) - акроним, введённый Майклом Фэзерсом для первых пяти принципов, названных Робертом Мартином в начале 2000-х, которые означали пять основных принципов ООП и проектирования.

Принцип единственной ответственности

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

Принцип открытости / закрытости

означает, что программные сущности должны быть:

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

Принцип подстановки Барбары Лисков

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

Принцип разделения интерфейса Роберт Мартин

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

Принцип инверсии зависимостей

— принцип, используемый для уменьшения зацепления в компьютерных программах. Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Что такое нарушение инкапсуляции?

Наружу (т.е. public) торчат какие-то данные, которые можно изменить, и объект уходит в противоречивое состояние.

Понятия ОО-программирования в 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() Определяет блок кода, выполняющегося только одной нитью в любой определенный момент времени.

Управление памятью

Garbage Collector

Это особенный режим управления памятью, основанный на периодическом запуске сборщика мусора. Мы выделяем память, но не освобождаем ее. За нас память освобождает сборщик мусора. Но он имеет ряд огромных недостатков:

  • Его должны поддерживать все без исключения используемые библиотеки. Скажем, если ARC позволяет использовать не-ARC подход, то с GС такое не пройдет, либо программа и все ее библиотеки полностью поддерживают этот метод управления памятью, либо программа просто не будет скомпилирована
  • Сборка мусора - это достаточно ресурсоемкая задача, она требует как дополнительной памяти, так и достаточно много процессорного времени. И каждый раз, когда программа будет вызывать сборщик мусора, выполнение программы в это время будет сопровождаться достаточно заметным подтормаживанием

ARC

Автоматический подсчет ссылок является компиляторной функцией, которая обеспечивает автоматическое управление памятью в Objective-C объектах. Вместо того, чтобы думать о со-хранении и освобождении объектов, ARC позволяет сосредоточиться на непосредственном коде Вашего приложения. ARC работает путем добавления кода во время компиляции, чтобы время жизни объекта было ровно столько, сколько необходимо, но не более того. Концептуально, это то же управление памятью, что и ручной подсчет ссылок (описанное в практическом управлении памятью) путем добавления соответствующего кода управления памятью, за вас. ARC поддерживается начиная с Xcode 4.2 для Mac OS X v10.6 и v10.7 (64-bit applications), а также iOS 4 и iOS 5. Слабые (weak) ссылки не поддерживаются в Mac OS X v10.6 и iOS 4 и более ранних. Существует несколько ограничений на использование механизма ARC.

  • Нельзя использовать свойство, имя которого начинается со слова new. Например, не допускается объявление
@property NSString *newString;
  • Нельзя использовать свойство только для чтения без атрибутов управления памятью. Если вы не используете механизм ARC, то можете использовать объявление
@property (readonly) NSString *title;

Но если вы используете механизм ARC, то должны указать, кто управляет памятью, так что достаточно просто вставить ключевое слово unsafe_unretained, потому что по умолчанию используется атрибут assign.

Освобождение памяти в MRC

Способ 1 - полностью ручное освобождение памяти ([str release];). Вызов данной функции уменьшает счетчик ссылок у объекта на 1. Если после вызова, количество ссылок становится 0, то объект деаллоцируется из памяти. Если указатель хотите использовать далее, то рекомендуется после этого выполнить следующую конструкцию: str = nil;

Способ 2 - [str autorelease]; - а вот этот способ требует более детального рассмотрения. Для каждого потока формируется определенный пул, куда записываются методы, память которых вы не хотите освобождать моментально. То есть после выполнения этого оператора, объект какое то время будет доступен.

А теперь как узнать, какое это время? Тут есть два варианта:

  1. пока не выполнится команда [pool drain] (в версиях XCode < 4), или пока не выйдете из блока @autorelease pool { ... };
  2. Либо на следующем витке цикла EventMessage. То есть каждый виток цикла сообщений ведет к просмотру объектов в пуле, обозначенных для удаления методом autorelease.

К сожалению, ко второму случаю, аналогию в чистом языке C++ со стандартной библиотекой я привести не могу, так как такого пула нет, но скорее всего есть какие-либо сторонние библиотеки, которые реализуют подобный механизм. А вот аналог первого варианта в C++ всем известен: delete str; Так же при создании объекта, можно сделать его изначально как autorelease. Например можно создать объект следующей строкой: NSMutableString *str1 = [[[NSMutableStirng alloc] init] autorelease]; Такой способ выделит память под новый объект, инициализирует его, а затем изначально сообщит в пул, что удаление объекта кладется на плечи autorelease пула, после чего передаст указатель на объект переменной str1. В итоге, объект, находящийся по адрессу, записанному в str1 автоматический уничтожится по условию освобождения пула. Кстати, после такой записи, если вызвать [str1 release]; - то объект сразу же будет уничтожен, но при попытке освободить пул, получим исключение, и последующий вылет из программы с ошибкой удаления несуществующего объекта. Так же есть такой стандартизированный способ создания авторелизнутых объектов классов как применение Convenience конструкторов. Еще хотелось бы порекомендовать, по возможности всегда использовать release вместо autorelease при программировании под iOS. Конечно, проще было бы создать объект изначально авторелизнутым и забыть про удаление объекта, эдакий почти сборщик мусора). Но не забывайте, что если пул очищается очень редко, то при программировании для мобильных устройств, где количество памяти ограничено, можно столкнутся с проблемой нехватки памяти еще до то того, как пул будет очищен. Еще один момент связанный с autorelease pool - если в основном потоке, каждый новый виток циклов сообщений (Event Message) очищает пул, то в потоках реализация периодической очистки пула ложиться на плечи программиста. Поэтому не забывайте иногда делать [pool drain];. P.s я так понимаю, что в версии XCode начиная с 4.2 [pool drain] делать уже не обязательно, так как там пул освобождается автоматический при выходе операторов за скобки @autorelease pool { текст программы }

Язык

Различия isKindOfClass и isMemberOfClass

isKindOfClass: возвращает логическое значение, указывающее, является ли приемник экземпляром заданного класса или экземпляром любого класса, который наследует от этого класса.
isMemberOfClass: возвращает логическое значение, указывающее, является ли приемник экземпляром заданного класса.

Когда лучше использовать категорию, а когда наследование?

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

Из чего состоит NSError?

Существует три части объекта NSError:

  1. Domain - это строка, которая идентифицирует категорию ошибок, из которых исходит эта ошибка
  2. Error code - это числовой идентификатор ошибки
  3. UserInfo - это описание ошибки

KVC

KVC (Key-Value Coding) представляет собой механизм для доступа к свойству объекта косвенно, с помощью строк для идентификации свойств, а не через вызов аксессора или доступ к ним непосредственно через переменных экземпляра. Часто используется для фильтрации в массивах (NSPredicate).

Forward Invocation

Это механизм обработки любого сообщения. То есть если мы посылаем сообщение некоторому объекту, но ни у кого из его иерархии не найдется селектора для обработки, то вызовется метод

- (void)forwardInvocation:(NSInvocation *)anInvocation;

Fast enumeration

Это итерация по обьектам любого класса, который реализует протокол NSFastEnumeration, в том числе NSArray, NSSet и NSDictionary. Реализация протокола состоит из одного метода:

 -(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len;

Чем объект Objective-C отличается от структуры С?

Структура – специальный тип данных языка C, который содержит в себе другие типы данных в одном блоке и группрует их под одним именем. Объекты в Objective-C представляют собой структуры в которых имеется ссылка на объект класса.

Что такое runLoop, когда он используется?

Циклы выполнения (run loop) - цикл обработки событий, который используется для планирования работы и координации получения входящих событий. Объект NSRunLoop также обрабатывает события NSTimer (он не будет работать в потоке, в котором нет NSRunloop)

nil, Nil, NULL, NSNull

#define nil (id)0 - это указатель на нулевой объект.
#define NULL ((void *)0) - используется для указателей (тоже самое что nil).
#define Nil (Class)0 - нулевой указатель типа Class.
NSNull — это своего рода обёртка над NULL и nil, позволяющая хранить их в объектах-коллекциях Objective-C.

Протоколы

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

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

Формальные протоколы

Объявление формального протокола гарантирует, что все методы объявленные протоколом будут реализованы классом.

Неформальные протоколы

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

First *f = [[First alloc] init];
if ([f respondsToSelector:@selector(setName:)]) {
    NSLog (@"Метод поддерживается");
}

Что такое @dynamic?

@dynamic используется для делегирования ответственности за реализацию аксессеров. @dynamic для свойств означает, что сеттеры и геттеры будут созданы вручную и/или в runtime.

Тип id

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

defer

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

override func viewDidLoad() {
    super.viewDidLoad()

    print("Шаг 1")
    myFunc()
    print("Шаг 5")
}

func myFunc() {
    print("Шаг 2")
    defer { print("Шаг 3") }
    print("Шаг 4")
}

Этот код напечатает "Шаг 1", "Шаг 2", "Шаг 4", "Шаг 3", "Шаг 5". Шаги 3 и 4 поменялись местами из-за того, что "Шаг 3" является отложенным до тех пор, пока не закончится выполнение метода myFunc(), то есть пока не выйдет за пределы видимости.

Но есть одна небольшая загвоздка - ваши вызовы defer не должны пробовать покинуть текущую зону видимости при использовании return или, выкидывая ошибку. Все остальное что делается с defer, все к лучшему!

Как работает механизм сообщений?

Компилятор переводит каждую посылку сообщения, т.е. конструкцию вида [object msg] в вызов функции objc_msgSend.

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

Каждый объект Objective-C содержит в себе атрибут isa - указатель на class object для данного объекта. class object автоматически создается компилятором и существует как один экземпляр, на который через isa ссылаются все экземпляры данного класса.

Каждый class object обязательно содержит в себе указатель на class object для родительского класса (superclass) и dispatch table. Dispatch Table представляет из себя словарь, сопоставляющий селекторам сообщений фактические адреса реализующих их методов (функций).

Функция objc_msgSend ищет метод с данным селектором в dispatch table для данного объекта. Если его там нет, то поиск продолжается в dispatch table для его родительского класса и т.д.

Приватные методы в Objective-C

Objective-С нет приватных(защищенных методов). Нужно использовать расширение. Для имитации private методов с помощью расширения, нужно в .m файле, перед @implementation добавить безымянную категорию.

Для класса NetworkManager ее определение будет выглядеть как:

@interface NetworkManager ()
...
@end

Стоит обратить особое внимание на пустые скобки — они показывают, что мы определяем именно безымянную категорию. После этого, мы можем добавлять в категорию методы, которые будут для нас считаться private. За счет того, что категория безымянная, имплементация данных методов может находиться рядом с имплементацией основных методов в разделе @implementation .. @end и нет необходимости создавать отдельные разделы для имплементации категорий. А за счет того, что она находится в .m файле, которые никто не подключает через #import, видимость методов для автодополнения ограничена текущим файлом.

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

Можно ли добавить ivar в категорию?

Директива @interface для категорий не может добавлять переменных экземпляра. Однако, она может определять, что категория поддерживает дополнительные протоколы.

Что такое указатель isa? Для чего он нужен?

Каждый объект Objective-C содержит в себе атрибут isa - указатель на class object для данного объекта. class object автоматически создается компилятором и существует как один экземпляр, на который через isa ссылаются все экземпляры данного класса.

typedef struct objc_object {
    Class isa;
} *id;

В чем отличие void* от id?

Отличие в том, что id указатель на objective - c объекты, а void* указатель на неопределенный тип, или просто область в памяти (в которой может хранится все что угодно).

KVO

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

[company_a addObserver:self forKeyPath:@"people" options:NSKeyValueObservingOptionNew context:nil];

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

observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

Надо лишь реализовать код, который будет реагировать на уведомление

Плюсы

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

Недостатки

  • Заметное падение производительности при обильном использовании KVO. Не стоит писать код, где ваши объекты общаются в основном через KVO. Рассматривайте KVO как вспомогательно средство для работы с чужим кодом, а не как основной инструмент
  • Необходимость очень аккуратно писать код при использовании KVO. Так как строковые идентификаторов не проверяются компилятором на валидность, то это может привести к ошибкам при переименовании переменных. Также, KVO очень чувствительно к порядку добавления / удаления наблюдателей. Так, если наблюдатель пытается отписаться от наблюдаемого, на который наблюдатель в данный момент не подписан, то происходит крэш. Если же, наоброт, наблюдатель не отпишется до того, как наблюдаемый будет уничтожен, то произойдет утечка памяти

Atomic vs nonatomic. Переопределение сеттера в MRC?

Cинхронизировать чтение/запись между потоками или нет. Atomic – thread safe. Тут все сложнее и неоднозначнее, есть ряд способов как сделать threadsafe аксессоры к пропертям. Самый простой способ это сделать – добавить конструкцию @synchronized:

- (NSString *)foo {
    @synchronized(self) {
       	return foo;
    }
}

- (void)setFoo:(NSString)newFoo {
    @synchronized(self) {
       	if (foo != newFoo) {
          	[foo release];
          	foo = [newFoo retain];
       	}
    }
}

Таким образом используя @synchronized мы лочим по ключу self доступ к foo, однако у такого метода есть очевидный недостаток, если в классе будет две переменные (или 100500) к которым нужен одновременный доступ с разных потоков, то они будут лочиться и друг относительно друга, т.к self для них один и тот же, в таких случаях нужно использовать другие методы лока, как NSLock, NSRecursiveLock,...

Чем отличается include от import?

import защищен от многократного включения кода

Что такое селектор (selector)? Как его вызвать?

Селектор - это имя метода закодированное специальным образом, используемым языком для быстрого поиска. Указание компилятору на селектор происходит при помощи директивы @selector(метод)

`First* f = [[First alloc] init];
if([f respondsToSelector:@selector(setName:)])
	NSLog (@"Метод поддерживается");`

В этом примере создается экземпляр класса First - f (наследник NSObject), после с помощью метода respondsToSelector проверяем может ли класс ответить на метод setName

Что такое замыкания и где их можно использовать?

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

Что такое убегающие и неубегающие замыкания (escaping/nonescaping closures)?

@nonescaping (стандартные) замыкания:

Когда замыкание передается в аргументах функции и используется до того, как выполнится тело функции и управление вернется обратно. Когда функция завершается, переданное замыкание выходит из области видимости и больше не существует в памяти.

@escaping (убегающие) замыкания:

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

Общие вопросы

Архитектура REST

REST (Representational state transfer) – это стиль архитектуры программного обеспечения для распределенных систем, таких как World Wide Web, который, как правило, используется для построения веб-служб. Термин REST был введен в 2000 году Роем Филдингом, одним из авторов HTTP-протокола. Системы, поддерживающие REST, называются RESTful-системами. Каждая единица информации однозначно определяется глобальным идентификатором, таким как URL. Каждый URL в свою очередь имеет строго заданный формат. Вот как это будет выглядеть на примере: GET /book/ — получить список всех книг GET /book/3/ — получить книгу номер 3 PUT /book/ — добавить книгу (данные в теле запроса) POST /book/3 — изменить книгу (данные в теле запроса) DELETE /book/3 — удалить книгу

Как необходимые условия для построения распределенных REST-приложений Филдинг перечислил следующие:

  • Клиент-серверная архитектура.
  • Сервер не обязан сохранять информацию о состоянии клиента.
  • В каждом запросе клиента должно явно содержаться указание о возможности кэширования ответа и получения ответа из существующего кэша.
  • Клиент может взаимодействовать не напрямую с сервером, а с произвольным количеством промежуточных узлов. При этом клиент может не знать о существовании промежуточных узлов, за исключением случаев передачи конфиденциальной информации.
  • Унифицированный программный интерфейс сервера. Филдинг приводил URI в качестве примера формата запросов к серверу, а в качестве примера ответа сервера форматы HTML, XML и JSON, различаемые с использованием идентификаторов MIME.
    Филдинг указывал, что приложения, не соответствующие приведённым условиям, не могут называться REST-приложениями.

Протоколы передачи данных

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

IP

Internet Protocol, межсетевой протокол - маршрутизируемый протокол сетевого уровня стека TCP/IP. Именно IP стал тем протоколом, который объединил отдельные компьютерные сети во всемирную сеть Интернет. Неотъемлемой частью протокола является адресация сети.

TCP

Transmission Control Protocol, протокол управления передачей — один из основных протоколов передачи данных Интернета, предназначенный для управления передачей данных в сетях и подсетях TCP/IP.

UDP

User Datagram Protocol, протокол пользовательских датаграмм. С UDP компьютерные приложения могут посылать сообщения (в данном случае называемые датаграммами) другим хостам по IP-сети без необходимости предварительного сообщения для установки специальных каналов передачи или путей данных. Природа UDP как протокола без сохранения состояния также полезна для серверов, отвечающих на небольшие запросы от огромного числа клиентов, например DNS и потоковые мультимедийные приложения вроде IPTV, Voice over IP, протоколы туннелирования IP и многие онлайн-игры.

HTTP

HyperText Transfer Protocol, протокол передачи гипертекста — протокол прикладного уровня передачи данных (изначально — в виде гипертекстовых документов). Основой HTTP является технология «клиент-сервер», то есть предполагается существование потребителей (клиентов), которые инициируют соединение и посылают запрос, и поставщиков (серверов), которые ожидают соединения для получения запроса, производят необходимые действия и возвращают обратно сообщение с результатом. Этот протокол описывает взаимодействие между двумя компьютерами (клиентом и сервером), построенное на базе сообщений, называемых запрос (Request) и ответ (Response). Каждое сообщение состоит из трех частей: стартовая строка, заголовки и тело. При этом обязательной является только стартовая строка.

FTP

File Transfer Protocol — это протокол передачи файлов со специального файлового сервера на компьютер пользователя. FTP дает возможность абоненту обмениваться двоичными и текстовыми файлами с любым компьютером сети. Установив связь с удаленным компьютером, пользователь может скопировать файл с удаленного компьютера на свой или скопировать файл со своего компьютера на удаленный.

Поверхностное и глубокое копирование

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

Глубокое копирование:

- (id)copyWithZone:(NSZone *)zone;

@implementation Person
- (id)copyWithZone:(NSZone *)zone {
	Person *copy = [[self class] allocWithZone:zone];
	copy.name = self.name;
	copy.age = self.age;
	copy.surname = self.surname;
	return copy;
}
@end

Метод объекта класса NSArray с управлением логикой копирования:

- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;

Что такое назначенный инициализатор?

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

  • Если класс имеет несколько инициализаторов, только один из них должен выполнять реальную работу. Этот метод называется основным инцициалuзатором. Все остальные инициализаторы должны вызывать основной инициализатор (прямо или косвенно).
  • Основной инициализатор вызывает основной инициализатор суперкласса перед инициализацией своих переменных экземпляров if (self = [super...])
  • Если имя основного инициализатора вашего класса отличается от имени основного инициализатора его супер класса, вы должны переопределить основной инициализатор суперкласса, чтобы он вызывал новый основной инициализатор.
  • Если класс содержит несколько инициализаторов, четко укажите в заголовочном файле, какой из них является основным. Установившейся практикой в таком случае является выделение среди всех init-методов одного, называемого designated initializer. Все остальные init-методы должны вызывать его и только он вызывает унаследованный init-метод.
- (instancetype)initWithName:(const char *)theName {  
	// call inherited method
	[super init];                       
	name = strdup(theName);
}

- (instancetype)init {
	return [self initWithName:@"name"];
}

Виды запросов

HEAD

HEAD Аналогичен методу GET, за исключением того, что в ответе сервера отсутствует тело. Запрос HEAD обычно применяется для извлечения метаданных, проверки наличия ресурса (валидация URL) и чтобы узнать, не изменился ли он с момента последнего обращения. Заголовки ответа могут кэшироваться. При несовпадении метаданных ресурса с соответствующей информацией в кэше копия ресурса помечается как устаревшая.

GET

GET Используется для запроса содержимого указанного ресурса. С помощью метода GET можно также начать какой-либо процесс. В этом случае в тело ответного сообщения следует включить информацию о ходе выполнения процесса. Клиент может передавать параметры выполнения запроса в URI целевого ресурса после символа ?: GET /path/resource?param1=value1&param2=value2 HTTP/1.1

POST

POST Применяется для передачи пользовательских данных заданному ресурсу. Например, в блогах посетители обычно могут вводить свои комментарии к записям в HTML-форму, после чего они передаются серверу методом POST и он помещает их на страницу. При этом передаваемые данные (в примере с блогами — текст комментария) включаются в тело запроса. Аналогично с помощью метода POST обычно загружаются файлы на сервер. В отличие от метода GET, метод POST не считается идемпотентным, то есть многократное повторение одних и тех же запросов POST может возвращать разные результаты (например, после каждой отправки комментария будет появляться одна копия этого комментария). Отправить POST-запрос не так тяжело как кажется. Достаточно подготовить «правильный» NSURLRequest.

NSString *params = @"param=value&number=1"; // задаем параметры POST запроса
NSURL *url = [NSURL URLWithString:@"http://server.com"]; // куда отправлять
request.HTTPMethod = @"POST";
request.HTTPBody = [params dataUsingEncoding:NSUTF8StringEncoding];
// следует обратить внимание на кодировку
// теперь можно отправить запрос синхронно или асинхронно
NSURLSession *session = [[NSURLSession alloc]initWithUrl: url];
session.

PUT

PUT Применяется для загрузки содержимого запроса на указанный в запросе URI. Если по заданному URI не существовало ресурса, то сервер создаёт его и возвращает статус 201 (Created). Если же был изменён ресурс, то сервер возвращает 200 (Ok) или 204 (No Content). Сервер не должен игнорировать некорректные заголовки Content-*, передаваемые клиентом вместе с сообщением. Если какой-то из этих заголовков не может быть распознан или не допустим при текущих условиях, то необходимо вернуть код ошибки 501 (Not Implemented). Фундаментальное различие методов POST и PUT заключается в понимании предназначений URI ресурсов. Метод POST предполагает, что по указанному URI будет производиться обработка передаваемого клиентом содержимого. Используя PUT, клиент предполагает, что загружаемое содержимое соответствует находящемуся по данному URI ресурсу.

URL and URI

Единый указатель ресурсов (англ. URL — Uniform Resource Locator) — единообразный локатор (определитель местонахождения) ресурса. URL — это стандартизированный способ записи адреса ресурса в сети Интернет.

URI (англ. Uniform Resource Identifier) — унифицированный (единообразный) идентификатор ресурса. URI — это символьная строка, позволяющая идентифицировать какой-либо ресурс: документ, изображение, файл, службу, ящик электронной почты и т. д. Прежде всего, речь идёт, конечно, о ресурсах сети Интернет и Всемирной паутины. URL это частный случай URI. Понятие URI включает в себя, помимо URL, например, ссылки на адреса электронной почты и т.п. URL указывает на Веб-ресурс, вроде сайта, страницы или конкретного файла, расположенных на интернет-серверах.

БД

CoreDate

Какие типы хранилищ поддерживает CoreData?

  1. XML
  2. SQLite
  3. In-Memory
  4. Binary

Что такое ленивая загрузка? Что ее связывает с CoreData?

Для загрузки данных из БД в память приложения удобно пользоваться загрузкой не только данных об объекте, но и о сопряжённых с ним объектах. Это делает загрузку данных проще для разработчика: он просто использует объект, который, тем не менее вынужден загружать все данные в явном виде. Но это ведёт к случаям, когда будет загружаться огромное количество сопряжённых объектов, что плохо скажется на производительности в случаях, когда эти данные реально не нужны. Паттерн Lazy Loading (Ленивая Загрузка) подразумевает отказ от загрузки дополнительных данных, когда в этом нет необходимости. Вместо этого ставится маркер о том, что данные не загружены и их надо загрузить в случае, если они понадобятся. Как известно, если Вы ленивы, то вы выигрываете в том случае, если дело, которое вы не делали на самом деле и не надо было делать.

Целесообразность использования CoreData

Core Data уменьшает количество кода, написанного для поддержки модели слоя приложения, как правило, на 50% - 70%, измеряемое в строках кода. Core Data имеет зрелый код, качество которого обеспечивается путем юнит-тестов, и используется ежедневно миллионами клиентов в широком спектре приложений. Структура была оптимизирована в течение нескольких версий. Она использует информацию, содержащуюся в модели и выполненяет функции, как правило, не работающие на уровне приложений в коде. Кроме того, в дополнение к отличной безопасности и обработке ошибок, она предлагает лучшую масштабируемость при работе с памятью, относительно любого конкурирующего решения. Другими словами: вы могли бы потратить долгое время тщательно обрабатывая Ваши собственные решения оптимизации для конкретной предметной области, вместо того, чтобы получить преимущество в производительности, которую Core Data предоставляет бесплатно для любого приложения.

Когда нецелесообразно использовать Core Data:

  • Если планируется использовать очень небольшой объем данных. В этом случае проще воспользоваться для хранения Ваших данных объектами коллекций - массивами или словарями и сохранять их в plist-файлы
  • Если используется кроссплатформерная архитектура или требуется доступ к строго определенному формату файла с данными (хранилищу), например SQLite
  • Использование баз данных клиент-сервер, например MySQL или PostgreSQL

Что такое NSManagedObjectId?

NSManagedObjectID объект является универсальным идентификатором для управляемого объекта, а также предоставляет основу для уникальности в структуре Core Data. NSManagedObjectID – универсальный потокобезопасный идентификатор. Бывает временным и постоянным. Используется в случае передачи объекта из одного контекста в другой.

NSFetchedResultsController

NSFetchedResultsController представляет собой контроллер, предоставляемый фреймворком Core Data для управления запросами к хранилищу. При изменении данных в CoreData информация в таблице будет актуализироваться.

NSFetchedResultsController предоставляет механизм для обработки данных (изменения, удаления, добавления) и отображает эти изменения в таблице.

Что такое контекст (Managed object context)?

NSManagedObjectContext - это среда в которой находится объект и которая следит за состоянием обьекта и зависимыми объектами.

Что такое Persistent store coordinator?

NSPersistentStoreCoordinator отвечает за хранение объектов данных которые передаются из NSManagedObjectContext.

Какие есть нюансы при использовании Core Data в разных потоках?

NSManagedObjectContext не thread-safe read для многопоточности основная идея - создавать для каждого потока свой NSManagedObjectContext и потом синхронизировать.

Realm

Преимущества над другими БД

  • Быстрая: Realm — невероятно быстрая библиотека для работы с базой данных. Realm быстрее, чем SQLite и CoreData (ORM-обертка над SQLite), и сравнительные тесты — лучшее доказательство для этого.
  • Кросс-платформенная: Файлы базы данных Realm кросс-платформенные и могут совместно использоваться iOS и Android. Независимо от того, Вы работаете с Java, Objective-C, или Swift, Вы будете использовать высокоуровневые модели.
  • Производительность: С позиции работоспособности было доказано что мобильная база данных Realm выполняет запросы и синхронизирует объекты значительно быстрее, чем Core Data, и осуществляет параллельный доступ к данным без проблем. Это значит, что несколько источников могут получить доступ к одному и тому же объекту без необходимости управлять блокировкой или каких-либо проблем с несогласованностью данных.
  • Шифрование: Мобильная база данных Realm предлагает службы шифрования для защиты базы на диске с помощью AES-256 + SHA2 64-разрядного шифрования
  • Хорошо документированная и есть отличная поддержка: Команда Realm предоставила читаемую, хорошо организованную документацию о Realm. Если у вас возникли проблемы, вы можете связаться с ними через Twitter, Github или Stackoverflow.
  • Бесплатная: Со всеми этими удивительными функциями Realm абсолютно бесплатна.

SQLite

Максимальный объем хранимых данных базы SQLite составляет 2 терабайта. Чтение из базы данных может производиться одним и более потоками, например несколько процессов могут одновременно выполнять SELECT. Однако запись в базу данных может осуществляться, только, если база в данный момент не занята другим процессом. SQLite не накладывает ограничения на типы данных. Любые данные могут быть занесены в любой столбец. Ограничения по типам данных действуют только на INTEGER PRIMARY KEY, который может содержать только 64-битное знаковое целое. SQLite версии 3.0 и выше позволяет хранить BLOB данные в любом поле, даже если оно объявлено как поле другого типа. Обращение к SQLite базе из двух потоков одновременно неизбежно вызовет краш. Выхода два:

Синхронизируйте обращения при помощи директивы @synchronized. Если задача закладывается на этапе проектирования, завести менеджер запросов на основе NSOperationQueue. Он страхует от ошибок автоматически, а то, что делается автоматически, часто делается без ошибок. Пример SQLite:

- (int)createTable:(NSString *)filePath {
sqlite3 *db = NULL;
int rc = 0;

rc = sqlite3_open_v2([filePath cStringUsingEncoding:NSUTF8StringEncoding], &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if (SQLITE_OK != rc) {
sqlite3_close(db);
NSLog(@"Failed to open db connection");
} else {
char *query ="CREATE TABLE IF NOT EXISTS students (id INTEGER PRIMARY KEY AUTOINCREMENT, name  TEXT, age INTEGER, marks INTEGER)";
char *errMsg;
rc = sqlite3_exec(db, query, NULL, NULL, &errMsg);
if (SQLITE_OK != rc) {
NSLog(@"Failed to create table rc:%d, msg=%s",rc,errMsg);
}
sqlite3_close(db);
}
return rc;
}

Многопоточность

Runloop

В Cocoa для каждого потока системой обычно создается свой Run Loop — цикл, который обрабатывает таймеры и события, а так же усыпляет поток, если ему нечего делать в текущий момент. Run Loop поддерживает 2 типа событий:

  1. Input sources — асинхронные события. Обычно это сообщения от других потоков, приложений или системных вызовов.
  2. Timer sources — синхронные события. Таймеры. Вызываются синхронно с известным интервалом.
    Каждый Run Loop определяет режим, в котором он работает: от режима зависит, какие события будут обработаны и кто будет об этом оповещен

Способы достижения многопоточности в iOS и macOS

Существует три способа достижения параллелизма в iOS:

  1. Потоки (threads)
  2. GCD
  3. NSOperationQueue

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

Поэтому в macOS и iOS предпочтительно использовать асинхронный подход к решению проблемы параллелизма, а не полагаться на потоки.

Одной из технологий асинхронного запуска задач является Grand Central Dispatch (GCD), которая отводит управление потоками до уровня системы. Все, что разработчик должен сделать, это определить выполняемые задачи и добавить их в соответствующую очередь отправки. GCD заботится о создании необходимых потоков и время для работы в этих потоках.

Все dispatch queues представляют собой структуры данных FIFO, поэтому задачи всегда запускаются в том же порядке, в котором они добавлены.

В отличие от dispatch queue очереди операций (NSOperation Queue) не ограничиваются выполнением задач в порядке FIFO и поддерживают создание сложных графиков выполнения заказов для ваших задач.

Что такое deadlock?

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

dispatch_queue_t queue = dispatch_queue_create("my.label", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    dispatch_sync(queue, ^{
        //  outer block is waiting for this inner block to complete,
        //  inner block won't start before outer block finishes
        //  => deadlock
    });

    // this will never be reached
})

Что такое livelock?

Livelock частая проблема в асинхронных системах. Потоки почти не блокируются на критических ресурсах. Вместо этого они выполняют свою небольшую неблокируемую задачу и отправляют её в очередь на обработку другими потоками. Может возникнуть ситуация, когда потоки друг другу начинают перекидывать какое-то событие и его обработка зацикливается. Явного бесконечного цикла, как бы, не происходит, но нагрузка на асинхронную систему резко возрастает. В результате чего эти потоки больше ничем не успевают занимаются.

Что такое DispatchGroup?

DispatchGroup уведомляют вас, когда вся группа задач завершена. Эти задачи могут быть синхронными или асинхронными и могут даже быть отслежены из разных очередей. DispatchGroup также уведомляют вас о синхронности или асинхронности, когда все события группы завершены. Так как элементы отслеживаются в различных очередях, то экземпляр dispatch_group_t отслеживает различные задачи в очередях.

Разница между синхронными и асинхронными задачами, однопоточностью и многопоточностью

Ниже описаны термины в привязке к GCD, но с небольшими изменениями они верны и для программирования в целом.

Синхронная операция начинает выполнятся сразу при вызове, блокирует поток. Выполняется в текущем потоке. Асинхронная операция ставит задачу в очередь выполнения, продолжает выполнение кода, из которого вызвана задача. Если очередь однопоточная, то задача будет выполнятся после выполнения всех задач, которые уже поставлены в очередь, если много поточная - возможно ее выполнение в другом потоке.

Нужно запомнить, что синхронность-асинхронность и однопоточность-многопоточность две пары разных характеристить, и одни не зависят от других (и синхронность и асинхронность могут работать как в однопоточной среде, так и в многопоточной)

Зачем использовать synchronized?

@synchronized гарантирует, что только один поток может выполнять этот код в блоке в любой момент времени.

Что такое мьютекс (mutex)?

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

Что такое семафор (semafor)?

Семафор позволяет выполнять какой-либо участок кода одновременно только конкретному количеству потоков. В основе семафора лежит счетчик, который и определяет, можно ли выполнять участок кода текущему потоку или нет. Если счетчик больше нуля — поток выполняет код, в противном случае — нет. В GCD выглядит так: semaphore_create – создание семафора (аналог sem_init)
semaphore_destroy – удаление, соответственно (аналог sem_destroy)
semaphore_wait – блокирующее ожидание на семафоре (аналог sem_wait)
semaphore_signal – освобождение семафора (аналог sem_post)

Тестирование

Unit-тесты

Модульное тестирование, или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.

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

TDD

Test-Driven Development («разработка через тестирование») – это специальная методика разработки ПО, которая основывается на коротких циклах работы, где сначала создаётся тест, а потом функционал.

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

Задания

Какой метод вызовется: класса A или класса B?

@interface A : NSObject
- (void)someMethod;
@end

@implementation A
- (void)someMethod {
	NSLog(@"This is class A");
}
@end

@interface B : A
@end

@implementation B
- (void)someMethod {
	NSLog(@"This is class B");
}
@end

@interface C : NSObject
@end

@implementation C
- (void)method {
	A *a = [B new];
	[a someMethod];
}
@end

Ответ: вызовется метод класса B.

Что выведется в консоль?

NSObject *object = [NSObject new];
dispatch_async(dispatch_get_main_queue(), ^ {
	NSLog(@"A %d", [object retainCount]);
	dispatch_async(dispatch_get_main_queue(), ^ {
		NSLog(@"B %d", [object retainCount]);
	});
	NSLog(@"C %d", [object retainCount]);
});
NSLog(@"D %d", [object retainCount]);

Ответ:

D 2
A 2
C 3
B 2

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

Пояснение к заданию: Прицельная точность тиков не важна, достаточно некая периодичность.

Решение: Если надо сделать таймер в фоне, то стоит выбирать поток с бегущим ранлупом. Либо воспользоваться уже готовым решением для GCD. 


dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block) {
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    if (timer) {
        dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
        dispatch_source_set_event_handler(timer, block);
        dispatch_resume(timer);
    }
    return timer;
}

Что произойдет после запуска приложения?

func application(_ application: UIApplication, didFinishLaunchingWithOptions...) -> Bool {
    DispatchQueue.global().async {
        Timer.scheduledTimer(timeInterval: 0.4, target: self,
	                         selector: #selector(self.tickTimer),
				 userInfo: nil, repeats: true)
    }
    return true
}

func tickTimer() {
    print("Tick-Tack")
}

Решение: Ничего не произойдет. Ранлуп не взведен. А еще будет небольшая утечка памяти.
Для исправления ошибки нужно выбирать бегущий ранлуп или использовать функцию sync