Принудительное выполнение всех действий UIKit в основном потоке

Я делаю сложное приложение, которое должно делать что-то во многих потоках и часто обновлять интерфейс.

Поэтому я должен добавить много

  dispatch_async(dispatch_get_main_queue(), ^{

  });

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

Итак, у меня есть идея создать подклассы элементов, таких как UILabel, UITextField и т. д., переопределяя их методы основного потока, например:

- (void)setAttributedText:(NSAttributedString *)attributedText {
  dispatch_async(dispatch_get_main_queue(), ^{
    [super setAttributedText:attributedText];
  });
}

- (void)setText:(NSString *)text {
  dispatch_async(dispatch_get_main_queue(), ^{
    [super setText:text];
  });
}

- (void)scrollRangeToVisible:(NSRange)range {
  dispatch_async(dispatch_get_main_queue(), ^{
    [super scrollRangeToVisible:range];
  });
}

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

Есть ли способ лучше?


person Duck    schedule 26.11.2017    source источник
comment
Это будет относительно дорого, если у вас есть несколько последовательных вызовов пользовательского интерфейса, поскольку каждый из них будет создавать новый блок и отправлять его в основную очередь. Поскольку рассылки также являются асинхронными, один блок может быть оценен немедленно, в то время как другие ждут чего-то еще, что приводит к непоследовательному обновлению вашего пользовательского интерфейса. Вместо этого я бы предложил найти способ сделать отправку как можно менее уродливой и ненавязчивой — вы можете сделать код более приятным с помощью макроса или вспомогательной функции или найти абстракцию более высокого уровня.   -  person Itai Ferber    schedule 26.11.2017
comment
Поскольку вы, кажется, используете Obj-C, могут ли макросы CPP помочь нам?   -  person meaning-matters    schedule 26.11.2017
comment
@meaning-matters - можете ли вы привести пример того, как это не может быть загрязнением кода, а не тем беспорядком, который у меня уже есть?   -  person Duck    schedule 26.11.2017
comment
Вероятно, было бы проще использовать сеттеры для свойств данных, которые могут асинхронно отправлять обновления в соответствующие элементы пользовательского интерфейса, а затем вы можете просто обновлять свойства модели данных, когда захотите, и знать, что пользовательский интерфейс в конечном итоге будет обновлен. Вы также можете найти связующую структуру, чтобы упростить эту задачу.   -  person Paulw11    schedule 26.11.2017
comment
@Paulw11Paulw11 - извините, но ваше объяснение кажется мне клингонским... Мне нужен код, чтобы увидеть это... спасибо   -  person Duck    schedule 26.11.2017
comment
Вы можете создать свою собственную функцию установки для свойства, скажем, currentFoo. В функции установки вы можете иметь dispatch_async, который устанавливает self.currentFooLabel.text = currentFoo. Затем в коде вашего фонового потока в любое время вы можете просто сказать self.currentFoo = @“newFoo” или что-то еще, и функция установки позаботится об асинхронной отправке за вас.   -  person Paulw11    schedule 26.11.2017
comment
Ваш пример реализации будет вызывать себя рекурсивно, или я что-то упустил? Я думаю, что каждая функция должна проверять основной поток и либо вызывать исходную реализацию super, либо планировать блок.   -  person mschmidt    schedule 26.11.2017
comment
Нет, есть разница между свойством currentFoo и текстовым полем, в которое вы хотите поместить это значение в currentFooLabel. См. здесь информацию о установщиках и геттерах target c.   -  person Paulw11    schedule 26.11.2017
comment
@ Paulw11 - извините, моя ошибка. Я исправил это, заменив self на super, но этот мой метод не будет работать для геттера -(NSString *)text, который должен возвращать текст внутри текстового представления... черт!   -  person Duck    schedule 26.11.2017
comment
Это не должно быть проблемой. Вы можете просто написать геттер, который возвращает текст из соответствующего текстового представления.   -  person Paulw11    schedule 26.11.2017
comment
выполнять действия во многих потоках Ваш компонент, выполняющий действия в фоновом режиме, должен возвращать результаты в основном потоке; его клиенты не должны нести ответственность за очистку ответа. Если вы используете чужой компонент, который плохо себя ведет в этом отношении, напишите обертку/адаптер, который делает то, что вам нужно. Другими словами, централизуйте возврат к основному потоку любыми необходимыми средствами.   -  person jscs    schedule 26.11.2017
comment
Как сказал @JoshCaswell, ваш фоновый код/класс отвечает за возврат к основному потоку в случае необходимости. Я не думаю, что это хорошая практика - использовать сеттеры подклассов (как вы хотите), чтобы всегда принудительно защищать выполнение вашего кода в основном потоке.   -  person crom87    schedule 26.11.2017
comment
@JoshCaswell Я должен не согласиться. Если клиентский код вызывает асинхронный API, обратный вызов также должен выполняться в фоновой очереди. Клиент должен нести ответственность за принятие решения о том, нужно ли ему выполнять определенные действия в определенной очереди (например, в основной очереди обновлений пользовательского интерфейса). Подумайте о случае, когда я вызываю асинхронный API из фоновой очереди для начала. Почему обратный вызов завершения должен быть отправлен в основную очередь с помощью асинхронного API? Фреймворки Apple полны примеров, когда блоки завершения не вызываются автоматически в основной очереди.   -  person rmaddy    schedule 27.11.2017
comment
Не могли бы вы рассказать о структуре вашего кода и о том, как переплетаются фоновая и основная работа? Сделав шаг назад и получив более широкое представление о вашей задаче, я/мы сможем предложить лучший метод.   -  person meaning-matters    schedule 27.11.2017


Ответы (1)


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

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

Например, объявите пару свойств, одно для текстового поля и одно для текста.

@property (weak, nonatomic)  UITextField *someTextField;
@property (strong, nonatomic) NSString *someText;

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

-(NSString *)someText {
   return self.someTextField.text;
} 

-(Void *)setSomeText: (NSString *)newValue {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.someTextField.text = newValue
  });
}

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

person Paulw11    schedule 26.11.2017
comment
хорошо, я так и думал, но в моем случае я должен получить значение элементов в основном потоке, иначе все выйдет из строя... в любом случае, поскольку это лучший ответ на данный момент, я принимаю это. - person Duck; 27.11.2017
comment
Вы не должны захватывать данные элемента пользовательского интерфейса в случайное время из фонового потока. Вы должны получить все необходимые данные и передать их вашему методу до того, как код начнет работать в фоновом потоке. - person Paulw11; 27.11.2017
comment
нет, у меня наоборот. Все выполняется в фоновом режиме, затем мне нужно обновить интерфейс. Фоновый поток — это тот, который вычисляет вещи, которые перейдут к интерфейсу. - person Duck; 27.11.2017
comment
Правильно, тогда код, который у меня есть, обрабатывает это. Вы можете просто установить self.someText из фонового потока и знать, что текстовое поле будет обновлено со временем. - person Paulw11; 27.11.2017
comment
приложение вылетает, когда я это делаю, и xcode показывает ошибку, сообщающую мне, что self.sometext следует читать из основного потока. - person Duck; 27.11.2017
comment
Итак, вы читаете элементы пользовательского интерфейса из фонового потока; вы говорите что-то вроде NSString *localVar = self.someText. Это то, что я говорю, что вы не должны делать. Вы должны получить localVar перед началом фоновой операции, а затем передать его фоновому коду; [myObject someBackgroundOperationWithText: localVar] - person Paulw11; 27.11.2017
comment
вы не понимаете сути. Значение для обновления интерфейса поступает из фонового потока. Невозможно обновить его из основного потока. Поверьте мне. - person Duck; 27.11.2017
comment
Давайте продолжим это обсуждение в чате. - person Paulw11; 27.11.2017