GitHub - синхронизируем forked репозиторий с upstream

 

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


# Добавляем новый удаленный репозиторий
$ git remote add upstream https://github.com/whoever/whatever.git

# Загружаем все ветки для отслеживания
$ git fetch upstream

# Убеждаемся, что мы на своем master'е
$ git checkout master

# Перезаписываем нашу ветку master чтобы те наши коммиты, которые не входят в удаленный мастер
# оказали на нем:
$ git rebase upstream/master

# Force push необходим для перезаписи истории ветки на вашем репозитории
$ git push -f origin master

Поваренная книга GCD

   

GCD - Grand Central Dispatch - библиотека от Apple для iOS и OS X для работы с примитивами многозадачности. На просторах интернета наткнулся на статью с некоторыми рецептами по работе с GCD. Статья на английском и очень интересная. Здесь хочу предложить вам ее вольный перевод. Далее повествование ведется от лица автора.

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

Выполнение задач в фоне

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

В этой статье для отображения чего-то, что занимает много времени для выполнения будут использоваться функции вида doSomeExpensiveWork()

И так, чтобы выполнить что-то в фоновом потоке, а затем вернуться в основной надо сделать примерно так:

let defaultPriority = DISPATCH_QUEUE_PRIORITY_DEFAULT
let backgroundQueue = dispatch_get_global_queue(defaultPriority, 0)
dispatch_async(backgroundQueue) {
  let result = doSomeExpensiveWork()
  dispatch_async(dispatch_get_main_queue()) {
    // используем как-нибудь `result`
  }
}

На практике, очень редко используются другие приоритеты, нежели DISPATCH_QUEUE_PRIORITY_DEFAULT. Функция dispatch_get_global_queue возвращает очередь, задания из которой могут выполняться на сотнях разных потоков. Если вам требуется, чтобы какие-то тяжелые задачи всегда выполнялись на какой-то определенной фоновой очереди, вы можете создать свою с помощью dispatch_queue_create. Она принимает имя очереди и флаг того, должна быть очередь последовательной (serial) или конкурентной (concurrent).

Важно, чтобы каждый вызов использовал dispatch_async, а не dispatch_sync. dispatch_async возвращает управление до того момента, как блок будет выополнен. Напротив, dispatch_sync дожидается выполнения блока перед тем как вернуть управление. Внутренний вызов в dispatch_sync может использовать dispatch_sync (так как в общем все равно когда вернется управление), но внешний вызов должен быть dispatch_async, потому что иначе главный поток будет заблокирован.

Singleton (паттерн одиночка)

С помощью dispatch_once вы можете создавать объекты паттерна “одиночка”. В swift это уже не так важно, так как есть более простые способы, однако для информации все же паттерн приводится ниже (ну и для Objective-C это по прежнему важно).

+ (instancetype) sharedInstance {  
  static dispatch_once_t onceToken;  
  static id sharedInstance;  
  dispatch_once(&onceToken, ^{  
    sharedInstance = [[self alloc] init];  
  });  
  return sharedInstance;  
}  

Сглаживаем О_О блок обратного вызова

Сглаживание конечно не совсем верный перевод для термина flatten, который еще можно перевести как выравнивание, но за неимением лучшей альтернативы пока так, можете предложить лучше, если знаете. (прим. переводчика)

А вот тут уже становится интересно. С помощью семафора, мы можем заблокировать поток на некоторое количество времени, пока не получим сигнал от другого потока. Семафоры, как и другие примитивы GCD, потокобезопасные, поэтому их можно вызывать где угодно.

Семафоры можно использовать тогда, когда вы имеете асинхронное API но хотите сделать синхронный вызов, но API менять по какой-то причине не можете.

// на фоновой очереди
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0)
doSomeExpensiveWorkAsynchronously() {
    dispatch_semaphore_signal(semaphore)
}

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
// тяжелая асинхронная обработка закончена

С помощью вызова dispatch_semaphore_wait вы блокируете поток до тех пор, пока где-либо не будет вызван сигнал dispatch_semaphore_signal. Это означает, что сигнал должен быть вызван из другого потока, так как текущий заблокирован. И более, вы никогда не должны вызывать wait из главного потока, только из фонового.

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

Может быть не совсем понятно зачем вам делать синхронный вызов из функции, которая уже содержит блок обратного вызова, но если вам это потребуется, вы знаете как это сделать. Однако, есть один случай, когда вам это может понадобиться - вам необходимо выполнить ряд асинхронных операций последовательно. Для того, чтобы облегчить использование, можно написать простой класс для абстракции - AsyncSerialWorker:

typealias DoneBlock = () -> ()
typealias WorkBlock = (DoneBlock) -> ()

class AsyncSerialWorker {
  private let serialQueue = dispatch_queue_create("com.khanlou.serial.queue", DISPATCH_QUEUE_SERIAL)

  func enqueueWork(work: WorkBlock) {
    dispatch_async(serialQueue) {
      let semaphore = dispatch_semaphore_create(0)
      work({
        dispatch_semaphore_signal(semaphore)
      })
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
    }
  }
}

Этот небольшой класс создает последовательную очередь и позволяет вам передать в нее блок на обработку. Объект WorkBlock дает вам DoneBlock для вызова, когда работа будет закончена, который в свою очередь отправит сигнал семафору и очередь отправит на обработку следующий блок.

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

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

class LimitedWorker {
  private let concurrentQueue = dispatch_queue_create("com.khanlou.concurrent.queue", DISPATCH_QUEUE_CONCURRENT)
  private let semaphore: dispatch_semaphore_t

  init(limit: Int) {
    semaphore = dispatch_semaphore_create(limit)
  }

  func enqueueWork(work: () -> ()) {
    dispatch_async(concurrentQueue) {
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
      work()
      dispatch_semaphore_signal(semaphore)
    }
  }
}

Этот пример взят из Apple Concurrency Programming Guide. Они могут объяснить что происходит лучше чем я:

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

Эффект аналогичен установке значения maxConcurrentOperationCount объекта NSOperationQueue. Если вы используете сырой GCD вместо NSOperationQueue, вы можете использовать семафоры для ограничения количества одновременно выполняемых блоков.

ВАЖНО! Здесь присутствует один важный момент. Каждый раз когда вы вызываете enqueueWork и вы достигли ограничения семафора - создается новый поток. Если у вас небольшой лимит и много задач для размещения в очередь, вы таким образом создадите огромное количество заблокированных потоков. Используйте профайлер и избегайте узких мест.

Ожидаем единого завершения нескольких асинхронных операций

Если у вас есть несколько асинхронно выполняемых блоков и вы хотите дождаться их общего завершения, вы можете использовать группу. dispatch_group_async позволяет вам добавить задачу в очередь (задача в блоке однако должна быть синхронной), и ведет учет количество добавленных задач. Важно, что одна и та же группу может добавлять задачи в несколько разных очередь и учитывать все из них. Когда все задачи в группе будут выполнены, вызывается блок, переданный в dispatch_group_notify - это своего рода блок обратного вызова на всю группу.

dispatch_group_t group = dispatch_group_create()
for item in someArray {
  dispatch_group_async(group, backgroundQueue) {
    performExpensiveWork(item: item)
  }
}

dispatch_group_notify(group, dispatch_get_main_queue()) {
  // все задачи выполнены
}

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

// этот код должен выполняться на фоновом потоке
dispatch_group_t group = dispatch_group_create()
for item in someArray {
  dispatch_group_enter(group)
  performExpensiveAsyncWork(item: item, completionBlock: {
    dispatch_group_leave(group)
  })
}

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

// все фоновые задачи в группе выполнены

Этот пример сложнее, но если пройтись по нему строка за строкой то все станет понятно. Как и семафор, группа содержит потокобезопасный внутренний счетчик, которым можно манипулировать. С помощью него вы можете гарантировать, что блок обратного вызова будет вызван после завершения всех долготекущий операций. Вызов “enter” увеличивает счетчик, вызов “leave” - уменьшает. dispatch_group_async скрывает для вас все детали, поэтому предпочтительнее.

Последнее в этом примере - это вызов wait, который блокирует поток и ожидает момента когда счетчик достигнет 0. Важно! Вы моежет использовать dispatch_group_notify даже если вы используете enter/leave. Обратное также верно - вы можете использовать dispatch_group_wait даже при использовании dispatch_group_async.

Функция dispatch_group_wait также как и dispatch_semaphore_wait принимает в качестве аргумента таймаут. Опять же, редко возникает нужда в чем-то отличном от DISPATCH_TIME_FOREVER. И так же как и в случае dispatch_semaphore_wait никогда не вызывайте dispatch_group_wait на главном потоке.

Самое главное отличие между этими двумя подходами в том, что notify можно вызывать непосредственно на главном потоке, а wait необходимо использовать на фоновом (как минимум wait, так как этот вызов блокирует текущую очередь).

Очереди изоляции

Словарь (и массив) в Swift - типы значений. Когда они изменяются, их ссылка полностью заменяется новой копией структуры. Однако, ввиду того что обновление полей экземпляра (ivar) объектов Swift не является атомарной операцией, она не является потокобезопасной. Два потока могут обновить словарь (например, добавить значение) в одно и то же время, и оба попытаются записать один и тот же блок памяти, что приведет к ее повреждению. Для того, чтобы обеспечить потокобезопасность можно использовать очереди изоляции.

Давайте сформируем коллекцию объектов, которая будет представлять собой словарь, где ключом будет ID объекта, а значением - сам объект.

class IdentityMap<T: Identifiable> {
  var dictionary = Dictionary<String, T>()

  func object(forID ID: String) -> T? {
    return dictionary[ID] as T?
  }

  func addObject(object: T) {
    dictionary[object.ID] = object
  }
}

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

К счастью, GCD дает нам замечательные инструменты для этого случая. Нам доступны следующие инструменты для решения этой задачи:

  • dispatch_sync
  • dispatch_async
  • dispatch_barrier_sync
  • dispatch_barrier_async

Идеальным случаем будет:

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

Барьеры GCD делают одну интересную вещь - они ожидают момента, когда очередь будет полностью пуста, перед тем как выполнить блок. С помощью барьеров для наших чтений мы ограничим доступ к словарю и обеспечим гарантию того, что никакая запись не будет проводиться одновременно с чтением или другой записью.

class IdentityMap<T: Identifiable> {
  var dictionary = Dictionary<String, T>()
  let accessQueue = dispatch_queue_create("com.khanlou.isolation.queue", DISPATCH_QUEUE_CONCURRENT)

  func object(withID ID: String) -> T? {
    var result: T? = nil
    dispatch_sync(accessQueue) {
      result = dictionary[ID] as T?
    }
    return result
  }

  func addObject(object: T) {
    dispatch_barrier_async(accessQueue) {
      dictionary[object.ID] = object
    }
  }
}

Функция dispatch_sync отправит блок в нашу очередь изоляции и будет дожидаться окончания перед тем как вернуть выполнение. После этого, мы будем иметь результат нашего чтения. Если не делать вызов синхронным, тогда потребуется введение блока обратного вызова. Благодаря тому, что очередь конкурентная, такие синхронные чтения могут выполняться по несколько штук параллельно.

Функция dispatch_barrier_async отправит блок в очередь изоляции. Async означает что управление будет возвращено до того, как блок фактически выполниться (т.е. выполниться запись). Это хорошо для проиводительности, но имеет и обратную сторону медали - чтение сразу после записи может вернуть старые данные.

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

Таймер на GCD

Иногда требуется периодическое выполнение какой-то функциональности и как правило для этого используют NSTimer. Однако таймер можно сделать и на GCD примерно вот так:

var timer: dispatch_source_t?

func createTimer() {
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  double secondsToFire = 1.000f;
  
  self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
  if let timer = self.timer {
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, secondsToFire * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10)
    dispatch_source_set_event_handler(timer) {
      // do something useful
    }
    dispatch_resume(timer)
  }
}

func releaseTimer() {
  guard let timer = self.timer else { return }
  dispatch_source_cancel(timer)
  timer = nil
}

Post Scriptum

Фреймворк GCD содержит много низкоуровневых примитивов. С их помощью мы смогли построить высокоуровневые конструкции. Если вам известны какие-то другие высокоуровневые конструкции не упомянутые здесь, будут рад их услышать (вы можете сделать PR тут или отправить напрямую автору. прим. переводчика)

Полезное для jekyll

 

Символификация crash-дампа iOS

 
 

Допустим вам надо понять что случилось с приложением и у Вас есть крэш-дамп, однако на телефоне стоит iOS 6.1 которую xCode 7.3.1 принципиально знать не хочет и крэш-дампы с этого устройства не забирает. А смотреть надо. Для этого включаем iTunes и синхронизируем устройство, тогда по пути

~/Library/Logs/CrashReporter/MobileDevice/

вы сможете найти крэш-дампы. После этого идем по пути

/Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources

Для xCode 7.3 путь меняется

/Applications/Xcode7.3.1.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources

где лежит symbolicatecrash. Берем его и копируем вместе с *.crash файлом и *.ipa файлом (предварительно выключив опцию Strip Debug Symbols During Copy) после этого выполняем нехитрый скрипт

$ export DEVELOPER_DIR=/Applications/xCode6.4/Xcode6.4.app/Contents/Developer
$ ./symbolicatecrash -v superapp_2016-05-11-211803_superphone.crash superapp.ipa

Боремся с The ‘Pods...’ target has transitive dependencies that include static binaries

 
 

Суть проблемы следующаяя

  1. Используем CocoaPods (про Carthage знаю, по некоторым причинам не хочу использовать)
  2. Проект на Swift
  3. Имеет swift’овые зависимости, поэтому use_frameworks!
  4. Имеет старые Objective-C’шные зависимости, в которых есть транзитивные зависимости в которых есть статические либы (something.a, можно в vendored_libraries)

При попытке сделать pod install получаем

The ‘Pods-...’ target has transitive dependencies that include static binaries

Сие подробно дискутируется тут и тут

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

pre_install do |installer|
    def installer.verify_no_static_framework_transitive_dependencies; end
end

Который просто напросто отключает проверку.

P.S. Правда сий хак не всегда помогает :( и вы можете получить unresolved symbols

P.P.S. Если же вы все-таки хотите transitive static binaries и use_frameworks! то решение существует - необходимо этот transitive static binary обернуть руками в framework а в под зашить vendored_frameworks примерно следующим образом

Нам нужен скрипт создания фреймворка (будем показывать на примере OpenSSL for iPhone)

create-openssl-framework.sh
#!/bin/sh

FWNAME=openssl

if [ ! -d lib ]; then
    echo "Please run build-libssl.sh first!"
    exit 1
fi

if [ -d $FWNAME.framework ]; then
    echo "Removing previous $FWNAME.framework copy"
    rm -rf $FWNAME.framework
fi

if [ "$1" == "dynamic" ]; then
    LIBTOOL_FLAGS="-dynamic -undefined dynamic_lookup"
else
    LIBTOOL_FLAGS="-static"
fi

echo "Creating $FWNAME.framework"
mkdir -p $FWNAME.framework/Headers
libtool -no_warning_for_no_symbols $LIBTOOL_FLAGS -o $FWNAME.framework/$FWNAME -install_name @rpath/$FWNAME.framework/$FWNAME lib/libcrypto.a lib/libssl.a
cp -r include/$FWNAME/* $FWNAME.framework/Headers/
echo "Created $FWNAME.framework"

ВАЖНО: чтобы имелся параметр -install_name … так как без него у вас все соберется, но при запуске будет Library not loaded: Image not found

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

{
  "name": "OpenSSL-for-iOS",
  ...
  "prepare_command": "./build-libssl.sh\n./create-openssl-framework.sh\n",
  "ios": {
    "vendored_frameworks": "openssl.framework"
  },
  ...
}

И далее цепляете под себе в Podfile обычным образом

pod 'OpenSSL-for-iOS'

С включенным use_frameworks!