Вы находитесь на странице: 1из 22

Память

ARC (что это? Зачем? В какой момент проставляются команды?)


Swift в основном берёт на себя управление памятью в ваших приложениях,
занимаясь выделением и освобождением памяти. Это происходит благодаря
механизму, который называется Automatic Reference Counting
(Автоматический подсчет ссылок), или сокращенно ARC. 
Область видимости:
Завернув экземпляр какого нибудь класса в метод, мы разрешим выйти ему из
области видимости, тем самым позволив ARC освободить его.

Время жизни объекта:


Существование объекта делится на пять этапов:
1. Выделение памяти (из стека или кучи)
2. Инициализация (выполняется код внутри init)
3. Использование
4. Денициализация (выполняется код внутри deinit)
5. Высвобоэжение памяти (выделенная память возвращается в стек или кучу)
Счётчик ссылок (reference counts), также известный как 'количество
использований' (usage counts), определяет, когда объект больше не нужен. Этот
счётчик показывает число тех, кто «пользуется» этим объектом. Объект
становится ненужным, когда счётчик использований равен нулю. Затем объект
деинициализируется и высвобождается.

Циклы ссылок:
Бывают ситуации, когда два объекта больше не используются, но каждый из них
ссылается на другой. Так как у каждого счетчик ссылок не равен 0, ни один из них
не будет освобождён.
Это цикл сильных ссылок (strong reference cycle). Такая ситуация сбивает с толку
ARC и не позволяет ему очистить память.

Ссылки Weak:
Чтобы разорвать цикл сильных ссылок, вы можете обозначить отношение между
объектами как слабое (weak).
По умолчанию все ссылки являются сильными и присваивание приводит к
увеличению счётчика ссылок. При использовании слабых ссылок (weak references)
счётчик ссылок не увеличивается.

Ссылки Unowned:
Существует также другой модификатор ссылки, который не приводит к
увеличению счётчика ссылок: unowned.
Ссылка weak всегда optional и автоматически становится nil, когда ссылаемый
объект высвобождается.
Вот почему мы должны объявлять weak свойства как переменную optional типа:
это свойство должно измениться. 
Ссылки Unowned, напротив, никогда не optional. Если вы попробуете получить
доступ к unowned свойству, которое ссылается на освобождённый объект, вы
получите ошибку, похожую на принудительное разворачивание содержащую nil
переменной (force unwrapping).

Циклы ссылок в замыканиях:


Циклы ссылок применительно к объектам возникают, когда у объектов есть
свойства, ссылающиеся друг на друга. Как и объекты, замыкания — это
ссылочный тип, и могут приводить к циклам ссылок. Замыкания «захватывают»
(capture) объекты, которые используют.
Например, если вы присвоите замыкание свойству класса, и это замыкание
использует свойства того же класса, то у нас появляется цикл ссылок. Другими
словами, объект держит ссылку на замыкание через свойство. Замыкание
содержит ссылку на объект через захваченное значение self.

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

x есть в списке захвата замыкания, таким образом значение x копируется в месте


определения замыкания. Оно захвачено по значению.
y нет в списке захвата, оно захвачено по ссылке. Это означает, что значение y
будет таким, какое оно в момент вызова замыкания.
Списки замыкания помогают определить отношения weak или unowned
взаимодействие по отношению к захвачиваемым внутри замыкания объектам. В
нашем случае подходящий выбор — unowned, так как замыкание не может
существовать, если экземпляр CarrierSubscription высвободится.

Захвати себя:
lazy var completePhoneNumber: () -> String = { [unowned self] in return self.countryCode + " " + self.number
}
Мы добавляем [unowned self] в список захвата замыкания. Это означает, что мы
захватили self как unowned ссылку вместо сильной.

Документация Apple рекомендует, чтобы родительский объект владел сильной


ссылкой на «детский» — не наоборот. Это означает, что мы даем Contact сильную
ссылку на Number, а Number — unowned ссылку на Contact

Autorelease pool (добавить объект, очистить пул, когда сливается сам)


В  IOS для управления памятью используется ARC – Automatic Reference Couting
автоматический учет ссылок.
MMR = MRC – Manual Retain-Release (Manial Reference Counting) ручной подсчет ссылок.
У объекта введется подсчет ссылок для него.
Изменение Retain Count (счетчик изменяется в следующих случаях):
- При создании объекта с помощью метода alloc, new, copy или mutableCopy, Retain
Count в этом случае увеличивается на единицу.
- Метод retain – увеличивает retain count (+1)
- Метод relase – уменьшает retain count (-1)
- Метод autorelease – уменьшает retain count, но не в данный момент (-1)
Когда retain count достигнет нуля,  объект уничтожится
В autorelease pool помещаются все объекты которым необходимо уменьшить счетчик
на единицу.
Использование: Например выполняется обработка большого количества изображений
в цикле, допустим загрузка такого количества изображений займет 1000Мбайт
оперативной памяти что приведет к крэшу приложения. В данном случае можно
создать autorelease pool и при завершении итерации цикла данные в этом пуле
освобождены из памяти и мы сократим единовременное потребление оперативной
памяти.
Это касается только ObjC объектов в случае с типами swift например integer, double,
string, использовать autorelease pool не нужно.
Шаблон autoreleasepool используется в Swift при возврате объектов autorelease (созданных
либо вашим кодом Objective-C, либо использованием классов Cocoa). autorelease в Swift
функционирует так же, как в Objective-C. Для примера можно рассмотреть метод создания
объектов NSImage / UIImage:
func useManyImages() {
let filename = pathForResourceInBundle

for _ in 0 ..< 5 {
autoreleasepool {
for _ in 0 ..< 1000 {
let image = NSImage(contentsOfFile: filename)
}
}
}
}

Если запустить из Инструментов Activity Monitor, то можно увидеть график


использования памяти:

Но если вы сделаете это без autoreleasepool, вы увидите, что пиковое использование


памяти выше:

Autoreleasepool позволяет вам явно управлять освобождением памяти, когда объекты


autorelease деинициализирутся в Swift, так же, как и в Objective-C.
Примечание. При работе с объектами Swift вы, как правило, не будете получать объекты
autorelease. Вот почему в начале статьи упоминается оговорка о том, что это нужно только
при «работе с Objective-C». Но если вы имеете дело с объектами Objective-C (включая
классы Cocoa), они могут быть объектами autorelease, и в этом случае это Swift-
представление шаблона Objective-C @autoreleasepool по-прежнему полезно.

Разница между strong, weak, assign (можно assign для объектов?)


Представьте, что наш объект - собака, и собака хочет убежать (быть перераспределена).

Сильные указатели похожи на поводка на собаке. Пока у вас есть привязь к собаке, собака не
убежит. Если пять человек прикрепите их привязь к одной собаке (пять сильных указателей на
один объект), то собака не убежит, пока все пять поводков не будут отсоединены.

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


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

Как только последний сильный указатель (поводок) больше не указывает на объект, объект
будет освобожден, и все слабые указатели будут обнулено.

strong(сохранить) -

1. он говорит: "Храните это в куче, пока я больше не укажу на это" 

2. Другими словами “Я владелец, вы не можете сдать это до того, как прицельтесь на

то же самое, что и удержите”


3. Вы используете сильные указатели, только если вам нужно сохранить объект.
4. По умолчанию все переменные экземпляра и локальные переменные являются
сильными указателями.
5. Обычно мы используем сильные для UIViewControllers (родители элементов
пользовательского интерфейса).
6. strong используется с ARC, и это в основном помогает вам, не заботясь о
сохранении счета объекта. ARC автоматически освобождает его для вас, когда вы
закончите с ним. Использование ключевого слова strong означает, что вы владеете
объектом.
Пример:

@property (strong, nonatomic) ViewController *viewController;

@synthesize viewController;

Начиная с Objective C 2.0 появилось понятие - свойство, под свойство выделена


директива @property, которая указывает, что данная переменная класса имеет метод для чтения
и записи в нее.
Сопутствующая директиве @property, директива говорящая компилятору, что
необходимо создать методы для доступа к переменной носит наименование @synthesize и
объявляется в файле реализации класса.
Когда мы используем слабые указатели?
Единственный раз, когда вы хотите использовать слабый, - это если вы хотите избежать
циклов сохранения (например, родительский элемент сохраняет дочерний элемент, а
дочерний элемент сохраняет родительский элемент, поэтому он не освобождается).

weak  -

1. он говорит: "держи это, пока кто-то еще указывает на это сильно" 


2. то же самое, что и назначение, не сохранять и не выпускать
3. "Слабая" ссылка - это ссылка, которую вы не сохраняете.
4. Обычно мы используем слабые для IBOutlets (дочерние элементы
UIViewController). Это работает, потому что дочерний объект должен существовать
только до тех пор, пока выполняется родительский объект.
5. Слабая ссылка - это ссылка, которая не защищает ссылочный объект от коллекции
сборщиком мусора.
6. Слабый по существу присваивает, незавершенное свойство. За исключением
случаев, когда объект освобождается, слабый указатель автоматически
устанавливается на nil
Пример:

@property (weak, nonatomic) IBOutlet UIButton *myButton;

@synthesize myButton;

Assign -

1. назначает значение по умолчанию и просто выполняет присвоение переменной


2. assign - это атрибут свойства, который сообщает компилятору, как синтезировать
реализацию средства установки свойств.
3. Я бы использовал назначение для примитивных свойств C и слабый для слабых
ссылок на объекты Objective-C.
Пример:

@property (nonatomic, assign) NSString *address;

@synthesize address;

Атрибуты мутатора (c ARC):


Assign

 Для задания нового значения используется оператор присваивания. Используется только


для POD-типов (для скалярных типов (NSInteger и CGRect)) либо для объектов, которыми мы не
владеем.
 Без ARC assign при присваивании не увеличивает счетчик ссылок, при удалении объекта
свойство будет указывать на не существующий объект. Важно: не становится nil т.е. указывает на
неопределенное место в памяти. Нужно самостоятельно занулять.
Пример:
-(void)someSetter(id)newVar
{
    var = newVar;
}
retain

 Указывает на то, что для объекта, используемого в качестве нового значения instance-
переменной, управление памятью происходит вручную (не забываем потом освободить память).
 Без ARC при присваивании увеличивает счетчик ссылок объекта, т.е. ты становишься
владельцем.
 retain, в отличие от copy не копирует объект при обращении к свойству, а увеличивает
счетчик использований и возвращает тот же самый объект (как бы ссылку на объект). Это нужно,
если объект большой или его не имеет смысла копировать. Например, объекты типа UIView,
UIViewController, UIWindow.
 retain - передача по ссылке, copy - по значению.
 Атрибут retain обеспечивает присвоение значения экземплярной переменной в порядке
retain-release.

Атрибуты мутатора (c ARC):

weak

 Аналог assign при применении режима автоматического подсчёта ссылок. (ARC должен


поддерживаться компилятором).
 В отличии от strong обозначает нестрогое соответствие и если объект был освобожден из
памяти (из другого класса или потока), то значение установится в nil.
 С ARC weak не совсем аналог assign на самом деле. Действие похожее, но становиться nil
если объект был удален. Т.е. более безопасно.
 Если мы ведем речь про weak ссылки, то это что-то похоже на следующее: "Машина едет
по дороге, у машины есть weak ссылка на дорогу. Дорога не зависит от машины, машина не
зависит от дороги. Машина может легко поменять дорогу, а старая дорога останется там же где и
была.".

strong

 Аналог retain при применении режима автоматического подсчёта ссылок. (ARC должен


поддерживаться компилятором).
 Обозначает строгое переназначение объекта, делая указатель на объект владельцем
этого объекта.
 С ARC strong == retain.
 Используем strong, если требуется, что бы один объект зависел от другого. В абстрактных
примерах можно привести такой пример: "У машины колеса должны быть strong (retain). Если
удаляется машина, то обязательно удаляются его колеса".

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

class
Krake
n {
let tentacle = Tentacle() //strong reference to
child.
}
class Tentacle {
let sucker = Sucker() //strong reference to child
}
class Sucker {}
}
Здесь мы видим линейную иерархию на примере. У Kraken есть
сильная ссылка к объекту класса Tentacle, а у него самого есть
сильная ссылка к классу Sucker. Вся иерархия идет от родителя
(Kraken) вниз до дочернего объекта (Sucker).

WEAK
Слабая ссылка (weak reference) — это просто указатель на
объект, который не защищает нас от уничтожения объекта
путем ARC. Если сильные сслыки увеличивают количество
ссылок на 1, то слабые — нет. К тому же слабые ссылки
обнуляют указатель на ваш объект, когда он был успешно
удален. Это гарантирует вам, что когда вы будете обращаться
к слабой ссылки, вы получите либо правильный объект,
либо nil.

В Swift’e все слабые ссылки — это неконстантные опционалы


(Optionals). Потому что подобная ссылка может и будет
изменяться на nil, когда на объект больше не будут
указываться сильные ссылки.
Важными местами для использовани weak переменных
являются те, у которых потенциально может
произойти retain cycle. Это случается, когда два объекта
имеют strong ссылки друг на друга. 
class
Kraken
{
var notificationObserver: ((Notification) -> Void)?
init() {
notificationObserver = NotificationCenter.default.addObserver(forName:
"humanEnteredKrakensLair", object: nil, queue: .main) { notification in
self.eatHuman()
}
}

deinit {
NotificationCenter.default.removeObserver(notificationObserver)
}
}

На данный момент у нас есть retain цикл. Как вы можете


видеть, closures (блоки замыкания) в свифте ведут себя
точно так же, как и блоки в Objective-C. Если какая-либо
переменная объявлена вне области действия замыкания,
ссылка на эту переменную внутри области действия
замыкания создает еще одну сильную ссылку на этот объект.
Единственными исключениями из этого являются
переменные, которые используют семантику значений, такую
как Ints, Strings, Arrays и Dictionaries в Swift.

Здесь, NotificationCenter сохраняет замыкание, которое


захватывает self сильно, когда вызывается функция eatHuman().
Хорошим тоном будет удалять наблюдателя (removeObserver).
Проблема здесь в том, что мы не очищаем этот блок до deinit, но
ARC никогда не вызовет deinit, потому что замыкание имеет
сильную ссылку на экземпляр Kraken!
Решение данной проблемы состоит в том, чтобы
использовать weakссылки в данных замыканиях. Это
разрушает сильную ссылку цикла. 
Изменения self на weak не будет увеличивать количество
сильных ссылок на 1, и тогда ARC уничтожит ваш объект
правильно.
NotificationCenter.default.addObserver(forName: "humanEnteredKrakensLair", object: nil,
queue: .main) { [weak self] notification in //The retain cycle is fixed by using capture
lists!
self?.eatHuman() //self is now an optional!
}
Еще одно место, где мы должны
использовать weak и unownedпеременные — это там, где мы
используем протоколы, которые выполняют роль делегатом.
В свифте струтуры и enum’ы могут быть подчиняться
протоколам, но они используют семантику значений. Если
родительский класс использует делегирование с дочерним
классом, например, так:
class Kraken:
LossOfLimbDelegate
{
let tentacle = Tentacle()
init() {
tentacle.delegate = self
}

func limbHasBeenLost() {
startCrying()
}
}

protocol LossOfLimbDelegate {
func limbHasBeenLost()
}

class Tentacle {
var delegate: LossOfLimbDelegate?

func cutOffTentacle() {
delegate?.limbHasBeenLost()
}
}

Тогда мы должны использовать weak переменные.

Вот почему:

 в данном случае хранит сильную ссылку на Kraken, под


Tentacle
протоколом LossOfLimbDelegate в свойствеdelegate.

И в тоже время:

Kraken  хранит сильную ссылку на Tentacle в свойстве tentacle.

Чтобы использовать weak перменную в данном сценарии, мы


добавляем weak оператор в начало декларации нашего
делегата:
weak var delegate: LossOfLimbDelegate?

UNOWNED
Поведение weak и unowned ссылок похоже, но не
одинаково. Unowned ссылки, как и weak, не увеличивают
количество retain ссылок на объект. Тем не менее в
Swift unowned ссылка имеет преимущество — она не
опциональна. 
«Используйте weak ссылку, если вы знаете, что в какой-
то момент времени эта ссылка станет нулевой. И
наоборот, используйте unowned ссылку, если вы знаете,
что ссылка никогда не будет равна нулю.
Здесь хороший пример того, как класс создает retain цикл,
используя closure, который не будет nil, потому что он
получает значение в init():
class
RetainCycle
{
var closure: (() -> Void)!
var string = "Hello"

init() {
closure = {
self.string = "Hello, World!"
}
}
}

// Инициализируем класс и активируем retain цикл


let retainCycleInstance = RetainCycle()
retainCycleInstance.closure()
// В данном случае мы можем гарантировать, что self внутри closure не будет nil.
// Любой последующий код после этого (особенно код, который изменяет ссылку на
self)
// должен быть оценен на предмет того, работает ли здесь еще unowned объект
В этом случае цикл сохранения происходит от того, что
замыкание захватывает self сильной ссылкой, в то время
как self имеет сильную ссылку на замыкание через свойство
замыкания. Чтобы сломать это, мы просто добавляем [unowned
self] в назначение замыкания:
closure
=
{ [unown
ed self]
in
self.string = "Hello, World!"
}
В этом случае мы можем предположить, что self никогда не
будет nil, так как мы вызываем closure сразу после
инициализации класса RetainCycle.

Вопрос 1
Что такое optional и какие проблемы они решают?

ответ
optional позволяет переменной любого типа представить ситуацию "отсутствие
значения". В Objective-C «отсутствие значения» было доступно только в
ссылочных типах с использованием специального значения nil. У типов-значений
(value types), вроде int или float, такой возможности не было.
Swift расширил концепцию «отсутствия значения» на типы-значения.
Переменная optionalможет содержать либо значение, либо nil, сигнализирующее
об отсутствии значения.

Вопрос 2
Коротко перечислите основные отличия между structure и class.

ответ
Классы поддерживают наследование, а структуры — нет.
Классы — ссылочный тип, структуры — тип-значение.

Вопрос 3
Что такое generics и для чего они нужны?

ответ
В Swift вы можете использовать generics в классах, структурах и перечислениях.

Generics устраняют проблему дублирования кода. Если у вас есть метод, который


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

Например, в этом коде вторая функция — это «клон» первой, за исключением


того, что у неё параметры string, а не integer.

func areIntEqual(_ x: Int, _ y: Int) -> Bool {


return x == y

func areStringsEqual(_ x: String, _ y: String) -> Bool {

return x == y

areStringsEqual( "ray", "ray") // true

areIntEqual( 1, 1) // true

Применяя generics, вы совмещаете две функции в одной и одновременно


обеспечиваете безопасность типов:

func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool {

return x == y

areTheyEqual ("ray", "ray")

areTheyEqual( 1, 1)

Так как вы тестируете равенство, вы ограничиваете типы теми, которые отвечают


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

Вопрос 4
В некоторых случаях не получится избежать неявного разворачивания (implicitly
unwrapped) optionals. Когда и почему?
ответ
Наиболее частые причины для использования implicitly unwrapped optionals:

 когда вы не можете инициализировать свойство, которое не nil в момент


создания. Типичный пример — outlet у Interface Builder, который всегда
инициализируется после его владельца. В этом особенном случае, если в
Interface Builder всё правильно сконфигурировано — вам гарантировано, что
outlet не-nil перед его использованием.
 чтобы разрешить проблему цикла сильных ссылок, когда два экземпляра
классов ссылаются друг на друга и требуется не-nil ссылка на другой
экземпляр. В этом случае вы помечаете ссылку на одной стороне
как unowned, а на другой стороне используете неявное разворачивание
optional.

Вопрос 5
Какими способами можно развернуть optional? Оцените их в смысле
безопасности.

var x : String? = "Test"

Подсказка: всего 7 способов.

ответ
Принудительное развёртывание (forced unwrapping) — небезопасно.

let a: String = x!

Неявное развертывание при объявлении переменной — небезопасно.

var a = x!

Optional binding — безопасно.

if let a = x {

print("x was successfully unwrapped and is = \(a)")


}

Optional chaining — безопасно.

let a = x?.count

Nil coalescing operator — безопасно.

let a = x ?? ""

Оператор Guard — безопасно.

guard let a = x else {

return

Optional pattern — безопасно.

if case let a? = x {

print(a)

Типы очередей GCD (сколько потоков для каждого типа)


Grand Central Dispatch - GCD фреймворк Apple, предназначенный для
создания приложений, использующих преимущества многоядерных
процессоров.
GCD позволяет определить задачи в приложении, которые могут
параллельно выполняться и запускает их при наличии свободных
вычислительных ресурсов.
Для работы с GCD, Apple ввела такие абстрактные понятия как задача и
очередь, использованные в данном фреймворке.
Задача в GCD представляет собой блок кода аналогичного замыканию,
инкапсулирующий код в один объект. Подтверждением Apple, создание
задачи и добавление её в очередь в GCD требует лишь 15 процессорных
инструкций, в то время как создание традиционного потока обходится в
несколько сотен инструкций, что делает технологию GCD удобной для
реализации многопоточности в приложении.
Очереди q в GCD бывают двух типов: последовательные и параллельные.
Последовательные представляют собой обычные фифу очереди, и задачи в
них выполняются по одной в порядке добавления.
В параллельных задачи выполняются параллельно. GCD по прежнему будет
брать задачи по очереди, но не будет дожидаться завершения предыдущих и
запустит следующее. 
Мы можем создавать последовательные и параллельные очереди и добавлять
к ним задачи, которые система будет выполнять в соответствии с
приоритетами очередей и свободными ресурсами. Таким образом в GCD
реализуется многопоточность.
Основные компоненты GCD:
Dispatch Queue, это объектно подобная структура, поддерживающая очереди
задач и управляющая назначением и выполнением задач. Каждая задача,
отправленное в очередь, обрабатывается в пуле потоков управляемых
системой.
Dispatch Source, является одним из основных типов данных, которые
координируют обработку конкретных низкоуровневых системных событий.
Dispatch Source позволяет регистрировать задачи для их асинхронного
выполнения при срабатывания определенного системного события.
GCD поддерживает следующие типы Dispatch Source:
 таймер отправки источника, Timer Dispatch Sources генерирует
периодически уведомления.
 сигнал отправки источников, Signal Dispatch Sources уведомит вас,
когда поступит сигнал unix.
 дискриптер источник, Descriptor Sources уведомит вас о различных
операциях на основе файлов и сокетов, таких как доступность данных
для чтения, возможность записи данных, когда файлы удалены или
перемещены.
 процесс отправки источников, Process Dispatch Sources уведомит вас о
связанных с процессом событиях, когда процесс завершится, когда
сигнал поступает на процесс и так далее.
 mach port отправки источников, Mach port Dispatch Sources уведомит
вас о связанных с мак ядром событиях. Пользовательские источники
отправки, Custom Dispatch Sources, которое вы определите и запустите
сами.
При настройке Dispatch Source вы указываете, какое системное событие вы
хотите контролировать, а также очередь для задачи и саму задачу,
используемую для обработки этих событий.
Dispatch Group, объект, позволяющий объединить задачи в группы и ожидать
их общего завершения. Группа служит полезным механизмом синхронизации
для кода, который зависит от завершения других задач.
Давайте представим, что у нас есть задача, состоящая из нескольких
подзадач, не зависящих друг от друга, например, нам необходимо загрузить
данные разных типов из сети и добавить их в таблицу. Для выполнения
задачи нужно дождаться выполнения всех под задач. В нашем случае мы не
хотим, чтобы данные добавлялись в таблицу по мере их загрузки из сети. Мы
хотим, чтобы все загруженные данные отобразились в таблице
одновременно. Здесь нам и поможет Dispatch Group. Благодаря данной
технологии мы можем отдельные задачи добавить в одну группу, а затем
использовать объект группы для ожидания завершения всех задач.
Dispatch Semaphore, обеспечивает эффективную реализацию традиционного
счётного симафора, которая может использоваться для управления доступом
к ресурсу в нескольких контекстах выполнения. Если на Dispatch Queue мы
добавляем задачи, которые могут иметь доступ к разделяемым ограниченным
ресурсам, то мы можем использовать Dispatch Semaphore для ограничения
числа задач, которые могут получить доступ к этим ресурсам одновременно.
Dispatch Semaphore работает как обычный Semaphore, с одним исключением:
когда ресурсы доступны, требуется меньше времени для получения Dispatch
Semaphore, чем для получения традиционного системного семафора. Это
связано с тем, что Grand Central Dispatch не обращается к ядру для этого
конкретного случая. Единственный раз, когда он обращается к ядру, когда
ресурс недоступен, и системе нужно оставить свой поток до тех пор, пока не
будет получен сигнал от семафора. 

Разница между sync/async


Задачи могут выполняться синхронно или асинхронно.

1. Функция Synchronous возвращает элемент управления в текущей очереди только


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

Общие проблемы. 

Самые популярные ошибки, которые программисты делают при проектировании


параллельных приложений, следующие:

1. Условие гонки - вызвано, когда работа приложения зависит от порядка выполнения


частей кода.
2. Приоритетная инверсия - когда задача с более высоким приоритетом задачи
меньшего приоритета завершена из-за блокировки некоторых ресурсов
3. Тупик - когда несколько очередей бесконечно ожидают, что источники (переменные,
данные и т.д.) уже заблокированы некоторыми из этих очередей.
НИКОГДА не вызывайте функцию синхронизации в главной очереди. 
Если вы вызовете функцию синхронизации в основной очереди, она заблокирует очередь, а
очередь будет ждать завершения задачи, но задача никогда не будет завершена, так как она
даже не сможет запускаться из-за очереди. уже заблокирован. Он называется тупиком.

Когда использовать синхронизацию?Когда нам нужно подождать, пока задача не будет


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

UI
Создать свой интерфейс можно 3-мя способами: программно, на основе маски,
которая автоматически подстраивается под изменения или использовать Auto
Layout.

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

Примером внешних изменений может быть: Изменение размера окна в macOS,


изменение ориентации экрана, различные размеры экранов.
Auto Layout без ограничений
Если вы по каким-либо причинам не хотите использовать правила(constraints) или
ваш интерфейс содержит в себе множество элементов расположение которых
можно изменять бесконечно, вам на помощь придет Stack View. 

Stack View — это ваша палочка выручалочка при создании комплексных


интерфейсов. Он может расставлять элементы внутри себя с данными
параметрами:

axis (только UIStackView) — определяет ориентацию, горизонтально или


вертикально;
orientation (только NSStackView) — тоже что и axis у UIStackView;
distribution — определяет расположение элементов в данной ориентации;
alignment — определяет расположение элементов перпендикулярно ориентации
StackView;
spacing — определяет расстояние между соседними элементами;

Для получения максимально удовлетворительных результатов вы можете


использовать constraints в самом StackView либо вкладывать несколько StackView
в StackView и затем использовать constraints например для выравнивания по
центру экрана.

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

Выглядит это приблизительно так:

Кнопка.Верх = ВысшаяТочкаИнтерфейса.Низ + 100

По данному выражению понятно что оно означает и какое правило устанавливает


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

В своих вычислениях constraints используют множители, ближайшие объекты и


константы вроде + 100из примера выше. Так же при создании правил не
обязательно, чтобы это были равенства, вы можете использовать >= или<=.

При создании layout желательно указывать два правила для каждого измерения


на элемент. Но не забывайте про StackView, который вам очень поможет при
создании интерфейса.

Самым интересным фактом является то, что при создании constraints вы можете


устанавливать приоритетность самих constraints. При вычислении, Auto Layout
старается удовлетворить все constraint'ы в порядке приоритетности. Приоритет =
1000 — обязателен. Все остальные, менее приоритетные правила вы можете
устанавливать для придания четкости обработке расположения элементов вашего
интерфейса. В случае, если один из constraint'ов будет не правильно вычислен,
Auto Layout использует ближайший constraint и начнет отталкиваться от него. Тем
не менее, настоятельно рекомендую не перегружать интерфейс различными
правилами и использовать дополнения только для достижения нужного
результата.

Создание Auto Layout и его составляющих


Вы можете создавать constraint'ы 3-мя способами:

1. CTRL + Перетаскивание, например, от label к верхней границе.


2. Используя Stack, Align, Pin и Resolve Tools.
3. Предоставить Interface Builder построить constraints вместо вас.

Среди данных пунктов самый важный именно 2-й, так как использование этой
панели является основным инструментом при создании разметки.

Stack — собственно та самая кнопка, с помощью которой вы можете поместить


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

Align — меню, которое позволит вам установить элементы четко по определенной


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

Pin — меню, позволяющее вам задать жесткие рамки относительно своего


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

Resolve Tools — самый лучший помощник в отладке constraint'ов. Основные


возможности этого меню: убрать все правила, добавить
предположительные constraints(Interface Builder построит все правила за вас),
добавить отсутствующие constraints, обновить constraints или frames(положение
объектов).
Как вы видите, тут довольно много важных пунктов и они призваны облегчить все
тяготы разработчика.

Редактировать constraint'ы можно нажав на них в Interface Builder, найти в Size


Inspector или в списке Document Outline. При редактировании параметров вы
можете задавать идентификаторы для более легкого понимания и нахождения их
в логах и консоли при выполнении различных отладок.

Немаловажным аспектом при установке правил для элементов, являются


параметры CHCR (Content-Hugging and Compression-Resistance Priorities) — эти
параметры влияют на изменение самого элемента в зависимости от
вышестоящего view. Грубо говоря Hugging — это нежелание элемента
увеличиваться, а Compression-Resistance — нежелание уменьшаться. С помощью
параметров CHCR можно к примеру изменять соотношение сжатия-расширения
элементов в StackView в зависимости от размеров находящихся в нем элементов.

Core Data
Core Data – это фреймворк, который управляет и хранит данные в приложении.
Можно рассматривать Core Data, как оболочку над физическим реляционным
хранилищем, представляющую данные в виде объектов, при этом сама Core Data
не является базой данных.
Для создания хранилища в приложении используются
классы NSPersistentStoreCoordinator или NSPersistentContainer.
NSPersistentStoreCoordinator создает хранилище указанного типа на основе
модели, можно указать размещение и дополнительные опции. 
Работает это следующим образом: если по указанному пути существует база
данных, то координатор проверяет ее версию и, при необходимости, делает
миграцию. Если база не существует, то она создается на основании модели
NSManagedObjectModel. 
Пример с NSPersistentStoreCoordinator

Пример с NSPersistentContainer
Core Data использует 4 типа хранилища:

— SQLite
— Binary
— In-Memory
— XML (только для Mac OS)

Несколько слов хочется сказать об объекте NSManagedObjectContext. Вообще,


Apple дает весьма туманную формулировку для NSManagedObjectContext —
среда для работы с объектами Core Data. Все это от желания отмежеваться от
ассоциаций с реляционными базами, и представить Core Data, как простое в
использовании средство, не требующее понимания ключей, транзакций и прочей
базданской атрибутики. Но на языке реляционных баз NSManagedObjectContext
можно, в некотором смысле, назвать менеджером транзакций. Вы, наверное,
заметили, что он имеет методы save и rollback, хотя скорее всего вы пользуетесь
только первым. 

Core Data используется как дополнительное кеширование, для программ на


телефоне. То есть мы можем посмотреть какие файлы у нас подгружены и не
загружать их с интернета и показать пользователю намного быстрее для того что
бы например при отсутствии интернета показать последние загруженные файлы
(пример лента в вк). Или же есть приложения которым не нужен интернет.
Например будильник и нам нужно хранить его данные. Так же фитнес трекер
который сохраняет обработанную информацию.
По сути Core Data представлена ввиде объектного графа не реляционной базы
данных, но внутри нее может быть один или несколько типов хранилищ.
Core Data Stack:
Persisten store. Это наши файлы на диске
Persisten store coordinator. Это тот элемент который взаимодействует между
файлами на диске и возвращает их к нам в оперативную память и может обратно
записать. Координирует данные между диском и нашим приложением.
Managed Object context. Это контекст в котором существуют те объекты (книжка,
пользователь, заметка) которые не могут в Core Data существовать сами по себе.
Они могут сузествовать только в определенном контексте.
Схема того что будет в приложении

Persistent store coordinator как правило он один на приложение


Private managed object context контекстов будет несколько
Main managed object context главный контекст который будет работать с главным
потоком. Это нужно если будет взаимодействие с пользовательским
интерфейсом.
Child managed object context констексты которые зависят от главного контекста.