SlideShare ist ein Scribd-Unternehmen logo
1 von 112
Downloaden Sie, um offline zu lesen
Егор Толстой
Ведущий iOS разработчик @ Rambler&Co
Задачи синхронизации
Классические и прикладные решения
Синхронизация
• Что такое синхронизация
• Решение проблем синхронизации
• Классические задачи
синхронизации
Синхронизация
• Синхронизация - 

обеспечение правильной
совместной работы нескольких
потоков.
Синхронизация
Алиса
Боб
Поток 1 Поток 2
Синхронизация
Алиса Боб
Поток 1 Поток 2
Синхронизация
Алиса Боб
Поток 1 Поток 2
Синхронизация
Алиса
Боб
Поток 1 Поток 2
Синхронизация
• Состояние гонки - 

работа приложения зависит от
последовательности вызова
секции его кода разными
потоками.
Синхронизация
func incrementSharedVariable {
// counter == 0
counter += 1
}
Синхронизация
func incrementSharedVariable {
// counter == 0
counter += 1
}
Поток 1 Поток 2 Поток 3
Синхронизация
func incrementSharedVariable {
// counter == 0
counter += 1
}
Поток 1 Поток 2 Поток 3
counter ∈ [1,3]
Синхронизация
• Критическая секция - 

участок кода, в котором
одновременно может находиться
только один поток.
Синхронизация
func incrementSharedVariable {
lockSection()
counter += 1
unlockSection()
}
Синхронизация
Поток 1 Поток 2 Поток 3
func incrementSharedVariable {
lockSection()
counter += 1
unlockSection()
}
Синхронизация
Поток 1 Поток 2 Поток 3
func incrementSharedVariable {
lockSection()
counter += 1
unlockSection()
}
Синхронизация
Поток 1 Поток 2 Поток 3
func incrementSharedVariable {
lockSection()
counter += 1
unlockSection()
}
Синхронизация
Поток 1 Поток 2 Поток 3
func incrementSharedVariable {
lockSection()
counter += 1
unlockSection()
}
counter == 3
Синхронизация
• Deadlock - 

несколько потоков блокируют
выполнение друг друга.
Синхронизация
• У каждой задачи синхронизации
есть определенные условия.
Синхронизация
Алиса Боб
Поток 1 Поток 2
Синхронизация
Алиса
Боб
Поток 1 Поток 2
Синхронизация
Алиса
Боб
Поток 1 Поток 2
Синхронизация
Алиса Боб
Поток 1 Поток 2
Синхронизация
• Передача сообщений между
потоками - простое решение
большинства проблем.
Синхронизация
Алиса Боб
а1: Съесть завтрак
а2: Поработать
а3: Съесть обед
а4: Позвонить Бобу
б1: Съесть завтрак
б2: Дождаться звонка Алисы
б3: Съесть обед
а1 < a2 < a3 < a4 б1 < б2 < б3
Синхронизация
Алиса Боб
а1: Съесть завтрак
а2: Поработать
а3: Съесть обед
а4: Позвонить Бобу
б1: Съесть завтрак
б2: Дождаться звонка Алисы
б3: Съесть обед
а1 < a2 < a3 < a4 б1 < б2 < б3
Синхронизация
Алиса Боб
а1: Съесть завтрак
а2: Поработать
а3: Съесть обед
а4: Позвонить Бобу
б1: Съесть завтрак
б2: Дождаться звонка Алисы
б3: Съесть обед
а1 < a2 < a3 < a4 б1 < б2 < б3
б3 > б2 > a4 > a3
Синхронизация
• События считаются
параллельными, если, изучив код
программы, мы не можем
утверждать, какое из них
произойдет первым.
Синхронизация
0
Алиса
Семафор
Боб
Синхронизация
0
Алиса
Семафор
Боб
Синхронизация
wait0
Алиса
Семафор
Боб
Синхронизация
wait-1
Алиса
Семафор
Боб
Синхронизация
wait-1Алиса
Семафор
Боб
Синхронизация
signal wait-1Алиса
Семафор
Боб
Синхронизация
signal wait0Алиса
Семафор
Боб
Синхронизация
signal wait0
Алиса
Семафор
Боб
Синхронизация
• Семафор создается с начальным
значением,
• wait - уменьшает счетчик,
• signal - увеличивает счетчик.
Синхронизация
• Задача "Читатели-Писатели" 

Readers-Writers problem
Синхронизация
• Любое количество читателей в
критической секции
• Единственный писатель в
критической секции
Синхронизация
1.
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
Синхронизация
1.
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
Синхронизация
1.
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
Синхронизация
1.
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
Синхронизация
1.
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
Синхронизация
0.
Семафор
"комната пуста"
on
Выключатель
"читатели"
Читатели Писатели
1
Синхронизация
Семафор
"комната пуста"
on
Выключатель
"читатели"
Читатели Писатели
2
0.
Синхронизация
Семафор
"комната пуста"
on
Выключатель
"читатели"
Читатели Писатели
2
-1.
Синхронизация
Семафор
"комната пуста"
on
Выключатель
"читатели"
Читатели Писатели
2
-1.
Синхронизация
Семафор
"комната пуста"
on
Выключатель
"читатели"
Читатели Писатели
2
-1.
Синхронизация
Семафор
"комната пуста"
on
Выключатель
"читатели"
Читатели Писатели
2
-1.
Синхронизация
Семафор
"комната пуста"
on
Выключатель
"читатели"
Читатели Писатели
1
-1.
Синхронизация
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
0.
Синхронизация
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
0.
Синхронизация
Семафор
"комната пуста"
off
Выключатель
"читатели"
Читатели Писатели
0
1.
Синхронизация
• Голод - состояние, в котором
поток может бесконечно ждать
своей очереди. 

А может и дождаться.
Синхронизация
• Решение с использованием
NSOperation
Синхронизация
class OperationScheduler {
var readerQueue = NSOperationQueue()
var writerQueue = NSOperationQueue()
init () {
// Читатели должны выполняться параллельно
readerQueue.maxConcurrentOperationCount = MAX
// Писатели должны выполняться последовательно
writerQueue.maxConcurrentOperationCount = 1
}
...
}
Синхронизация
func addReaderOperation(operation: NSBlockOperation) {
// Зависим от выполнения всех писателей
for writerOperation in writerQueue.operations {
operation.addDependency(writerOperation)
}
readerQueue.addOperation(operation)
}
Синхронизация
func addWriterOperation(operation: NSBlockOperation) {
// Все читатели зависят от нового писателя
for readerOperation in readerQueue.operations {
readerOperation.addDependency(operation)
}
writerQueue.addOperation(operation)
}
Синхронизация
func readersWriters() {
let scheduler = OperationScheduler()
scheduler.addWriter()
for i in 1...5 {
scheduler.addReader(i)
}
scheduler.addWriter()
}
> writer fired
> writer fired
> reader 2 fired
> reader 3 fired
> reader 1 fired
> reader 5 fired
> reader 4 fired
Синхронизация
• Задача "Обедающие философы" 

Dining philosophers problem
Синхронизация
while(true) {
think()
getForks()
eat()
putForks()
}
Синхронизация
• Не больше одного философа на
вилку
• Не допустить deadlock
• Не допустить голод
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
вилка
1
вилка
1
вилка 1 вилка
1
вилка1
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка0
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка0
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка0
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка0
deadlock :(
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
Семафор
"официант"
4
вилка
1
вилка
1
вилка 1 вилка
1
вилка1
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
Семафор
"официант"
3
вилка
1
вилка
1
вилка 1 вилка
1
вилка1
Синхронизация
Ф.5
Ф.1
Ф.2
Ф.3
Ф.4
Семафор
"официант"
3
вилка
0
вилка
1
вилка 0 вилка
1
вилка1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
вилка 0 вилка
1
вилка1
Ф.2
Семафор
"официант"
2
Ф.1
1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
1
вилка1
Ф.2
Семафор
"официант"
2
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
1
вилка1
Ф.2
Семафор
"официант"
1
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка1
Ф.2
Семафор
"официант"
1
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка1
Ф.2
Семафор
"официант"
0
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка0
Ф.2
Семафор
"официант"
0
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 1 вилка
0
вилка0
Ф.2
Семафор
"официант"
1
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка0
Ф.2
Семафор
"официант"
0
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
1
вилка
0
вилка 0 вилка
0
вилка0
Ф.2
Семафор
"официант"
1
Ф.1
Синхронизация
Ф.5
Ф.3
Ф.4
вилка
0
вилка
0
вилка 0 вилка
0
вилка0
Ф.2
Семафор
"официант"
0
Ф.1
Синхронизация
• Решение с использованием
NSOperation
Синхронизация
class DiningPhilosophersOperationScheduler {
var philosopherQueues = [NSOperationQueue]()
init() {
for _ in 0...4 {
let philosopherQueue = NSOperationQueue()
philosopherQueue.maxConcurrentOperationCount = 1
philosopherQueues.append(philosopherQueue)
}
}
...
}
Синхронизация
class PhilosopherBlockOperation: NSBlockOperation {
init(index: Int) {
super.init()
addExecutionBlock({
print("philosopher (index) is eating")
sleep(2)
print("philosopher (index) puts forks")
})
}
}
Синхронизация
func startPhilosopher(index: Int) {
let operation = PhilosopherBlockOperation(index: index)
let leftQueue = getLeftPhilosopher(index)
let leftPhilosopherEats = leftQueue.operationCount > 0
if (leftPhilosopherEats) {
for leftOperation in leftQueue.operations {
operation.addDependency(leftOperation)
}
}
// То же и для правого соседа
let currentPhilosopherQueue = philosopherQueues[index]
currentPhilosopherQueue.addOperation(operation)
}
Синхронизация
func diningPhilosophers() {
let scheduler =
DiningPhilosophersOperationScheduler()
scheduler.startPhilosopher(0)
scheduler.startPhilosopher(3)
scheduler.startPhilosopher(2)
scheduler.startPhilosopher(1)
scheduler.startPhilosopher(4)
}
> philosopher 0 is thinking
> philosopher 0 is eating
> philosopher 3 is thinking
> philosopher 3 is eating
> philosopher 2 is thinking
> philosopher 1 is thinking
> philosopher 4 is thinking
> philosopher 3 puts forks
> philosopher 0 puts forks
> philosopher 2 is eating
> philosopher 4 is eating
> philosopher 2 puts forks
> philosopher 4 puts forks
> philosopher 1 is eating
> philosopher 1 puts forks
Синхронизация
> philosopher 0 is thinking
> philosopher 0 is eating
> philosopher 3 is thinking
> philosopher 3 is eating
> philosopher 2 is thinking
> philosopher 1 is thinking
> philosopher 4 is thinking
> philosopher 3 puts forks
> philosopher 0 puts forks
> philosopher 2 is eating
> philosopher 4 is eating
> philosopher 2 puts forks
> philosopher 4 puts forks
> philosopher 1 is eating
> philosopher 1 puts forks
Ф.0
Ф.1
Ф.2
Ф.3
Ф.4
Синхронизация
• Задача "Собираем H2O" 

Building H2O problem
Синхронизация
• Кислород ждет двух потоков
водорода
• Водород ждет одного потока
водорода и одного - кислорода
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
О
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
О
Синхронизация
-1
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
О
H
Синхронизация
-1
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
О H
Синхронизация
-1
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
-1
3
Барьер
О H
Синхронизация
-1
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
-1
3
Барьер
О H
H
Синхронизация
-1
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
-1
3
Барьер
О H H
Синхронизация
-1
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
-2
3
Барьер
О H H
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
О H H
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
О H H
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
0
Барьер
О H H
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
0
Барьер
О H H
Синхронизация
0
Семафор
"Кислород"
Кислород Водород
Семафор
"Водород"
0
3
Барьер
Синхронизация
• Решение с использованием
NSOperation
Синхронизация
func triggerHydrogenEvent(location: CGPoint) {
let sprite = AtomNode.atom(AtomSprite.Hydrogen, location: location)
self.addChild(sprite)
self.scheduler.addHydrogen(sprite, block: atomBlock(sprite))
}
Синхронизация
class H2OOperationScheduler {
let moleculeQueue = NSOperationQueue()
// Этот блок вызывается при успешном сборе молекулы H2O
var moleculeBlock: (Array<SKSpriteNode>) -> Void
init(H2OBlock: (Array<SKSpriteNode>) -> Void) {
moleculeQueue.maxConcurrentOperationCount = 1
moleculeBlock = H2OBlock
}
...
}
Синхронизация
func addHydrogen(node: SKSpriteNode, block: () -> Void) {
let currentMolecule = obtainMolecule { (molecule) -> Bool in
return molecule.hydrogen < 2
}
let operation = AtomOperation(node: node, block: block)
currentMolecule.addHydrogen(operation)
}
Синхронизация
class H2OOperation: NSOperation {
let atomQueue = NSOperationQueue()
let moleculeBlock: (Array<SKSpriteNode>) -> Void
var nodes = Array<SKSpriteNode>()
...
override var ready: Bool {
return hydrogen == 2 && oxygen == 1
}
func addHydrogen(operation: AtomOperation) {
nodes.append(operation.node)
atomQueue.addOperation(operation)
hydrogen++
}
}
Синхронизация
class H2OOperation: NSOperation {
...
override func main() {
atomQueue.suspended = false
atomQueue.waitUntilAllOperationsAreFinished()
self.moleculeBlock(nodes)
}
...
}
Синхронизация
• Синхронизация - это непросто
• Синхронизация - это интересно
• Синхронизация - это важно
Синхронизация
• Код примеров:

https://github.com/rambler-
digital-solutions/synchronization-
problems
Синхронизация
• The Little Book of Semaphores

Allen B. Downey



http://www.greenteapress.com/
semaphores/

Weitere ähnliche Inhalte

Mehr von RAMBLER&Co

RDSDataSource: App Thinning
RDSDataSource: App ThinningRDSDataSource: App Thinning
RDSDataSource: App ThinningRAMBLER&Co
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRAMBLER&Co
 
RDSDataSource: YapDatabase
RDSDataSource: YapDatabaseRDSDataSource: YapDatabase
RDSDataSource: YapDatabaseRAMBLER&Co
 
Rambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRAMBLER&Co
 
Rambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектураRambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектураRAMBLER&Co
 
Rambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCoreRambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCoreRAMBLER&Co
 
Rambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеровRambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеровRAMBLER&Co
 
RDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperiencedRDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperiencedRAMBLER&Co
 
RDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDKRDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDKRAMBLER&Co
 
RDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOSRDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOSRAMBLER&Co
 
RDSDataSource: Promises
RDSDataSource: PromisesRDSDataSource: Promises
RDSDataSource: PromisesRAMBLER&Co
 
RDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwiftRDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwiftRAMBLER&Co
 
Rambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейсаRambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейсаRAMBLER&Co
 
Rambler.iOS #7: Прием платежей по банковским картам в iOS приложении
Rambler.iOS #7: Прием платежей по банковским картам в iOS приложенииRambler.iOS #7: Прием платежей по банковским картам в iOS приложении
Rambler.iOS #7: Прием платежей по банковским картам в iOS приложенииRAMBLER&Co
 
Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101RAMBLER&Co
 
Rambler.iOS #6: App delegate - разделяй и властвуй
Rambler.iOS #6: App delegate - разделяй и властвуйRambler.iOS #6: App delegate - разделяй и властвуй
Rambler.iOS #6: App delegate - разделяй и властвуйRAMBLER&Co
 
Rambler.iOS #6: Pagination Demystified
Rambler.iOS #6: Pagination DemystifiedRambler.iOS #6: Pagination Demystified
Rambler.iOS #6: Pagination DemystifiedRAMBLER&Co
 
Rambler.iOS #6: Не рычите на pbxproj
Rambler.iOS #6: Не рычите на pbxprojRambler.iOS #6: Не рычите на pbxproj
Rambler.iOS #6: Не рычите на pbxprojRAMBLER&Co
 
Rambler.iOS #5: VIPER и Swift
Rambler.iOS #5: VIPER и SwiftRambler.iOS #5: VIPER и Swift
Rambler.iOS #5: VIPER и SwiftRAMBLER&Co
 
Rambler.iOS #5: TDD и VIPER
Rambler.iOS #5: TDD и VIPERRambler.iOS #5: TDD и VIPER
Rambler.iOS #5: TDD и VIPERRAMBLER&Co
 

Mehr von RAMBLER&Co (20)

RDSDataSource: App Thinning
RDSDataSource: App ThinningRDSDataSource: App Thinning
RDSDataSource: App Thinning
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
 
RDSDataSource: YapDatabase
RDSDataSource: YapDatabaseRDSDataSource: YapDatabase
RDSDataSource: YapDatabase
 
Rambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тесты
 
Rambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектураRambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектура
 
Rambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCoreRambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCore
 
Rambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеровRambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеров
 
RDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperiencedRDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperienced
 
RDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDKRDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDK
 
RDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOSRDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOS
 
RDSDataSource: Promises
RDSDataSource: PromisesRDSDataSource: Promises
RDSDataSource: Promises
 
RDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwiftRDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwift
 
Rambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейсаRambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейса
 
Rambler.iOS #7: Прием платежей по банковским картам в iOS приложении
Rambler.iOS #7: Прием платежей по банковским картам в iOS приложенииRambler.iOS #7: Прием платежей по банковским картам в iOS приложении
Rambler.iOS #7: Прием платежей по банковским картам в iOS приложении
 
Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101
 
Rambler.iOS #6: App delegate - разделяй и властвуй
Rambler.iOS #6: App delegate - разделяй и властвуйRambler.iOS #6: App delegate - разделяй и властвуй
Rambler.iOS #6: App delegate - разделяй и властвуй
 
Rambler.iOS #6: Pagination Demystified
Rambler.iOS #6: Pagination DemystifiedRambler.iOS #6: Pagination Demystified
Rambler.iOS #6: Pagination Demystified
 
Rambler.iOS #6: Не рычите на pbxproj
Rambler.iOS #6: Не рычите на pbxprojRambler.iOS #6: Не рычите на pbxproj
Rambler.iOS #6: Не рычите на pbxproj
 
Rambler.iOS #5: VIPER и Swift
Rambler.iOS #5: VIPER и SwiftRambler.iOS #5: VIPER и Swift
Rambler.iOS #5: VIPER и Swift
 
Rambler.iOS #5: TDD и VIPER
Rambler.iOS #5: TDD и VIPERRambler.iOS #5: TDD и VIPER
Rambler.iOS #5: TDD и VIPER
 

Rambler.iOS #4: Задачи синхронизации. Классические и прикладные решения.