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

Недавно я разработал iOS-приложение со Swift, которое обрабатывает множество фоновых HTTP-задач и обновляет не только пользовательский интерфейс, но и статические данные для текущего сеанса (множество массивов, переменных и т. д.) в соответствии с данными ответа. Меня можно считать новичком в iOS-разработке, и есть некоторые моменты, в которых я запутался:

Обновление пользовательского интерфейса из фоновой задачи выполняется через GCD API. Я всегда обрабатывал эти обновления, используя:

dispatch_async(dispatch_get_main_queue, {
    // Update UI
})

Позвольте мне привести сценарий и пояснить мою точку зрения:

У меня есть контроллер представления с подвидом UITableView. Это табличное представление будет отображать список чего-либо (скажем, имена пользователей). Я подготовил и возобновил NSURLSessionDataTask:

let request = NSMutableURLRequest(URL: someURL)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
    data, response, error in

    // Handle error case
    // Parse data, and receive a user list
    // AppData.userList = parsed list
    // Update table view
}

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

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

Любой четкий комментарий или руководство было бы очень полезно! Спасибо большое уже сейчас


person natuslaedo    schedule 23.02.2015    source источник


Ответы (2)


Перво-наперво:

let request = NSMutableURLRequest(URL: someURL)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { [weak self]
(data, response, error) in
    if let weakself = self {
        // ...
        dispatch_async(dispatch_get_main_queue()) {
            // update UI
        }
    }
}

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

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

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

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

var integers = Array<Int>()

// The following creates an IMMUTABLE shallow copy of mutable array
let ints = integers

for value in ints {
   // use value
}

// OR use locking
objc_sync_enter(integers)
for value in integers {
   // use value
}
objc_sync_exit(integers)

// in another thread - lock before mutating
objc_sync_enter(integers)
integers.append(someIntValue)
objc_sync_exit(integers)

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

Может ли выполнение вызова dispatch_async внутри замыкания (для обработчика завершения задачи) вызвать какие-либо проблемы?

Ответ - нет. Если вы убедитесь, что в этих замыканиях нет ссылок на self, а данные, доступные конкурирующим потокам, доступны/изменены поточно-безопасным способом.

person skim    schedule 11.03.2015

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

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

Вся процедура рендеринга должна выполняться в основном потоке, скорее всего, из-за ускорения графического процессора и управления событиями.
Напротив, объекты Foundation (за исключением некоторых изменяемых объектов являются потокобезопасными), поэтому ими можно управлять/манипулировать и использовать их в другом поток.
Безопасность потока означает, что вы можете легко обмениваться объектами между потоками.
Если вы используете объект Foundation в фоновом потоке, проблем вообще нет, если вы используете mutable один раз только внутри этого потока, все должно работать , проблема с изменяемыми объектами возникает, когда вы хотите добавить объекты в массив (например) из большего количества потоков.
Если вы предоставляете свои собственные классы, вы должны сами обеспечить потокобезопасность.

person Andrea    schedule 23.02.2015