Когды Вы начинаете работать с блоками, то сразу возникает вопрос: “Есть блок кода, могу ли я просто выполнить его на определенном потоке?”. Слава богу - ответ “Да”, но Apple почему-то не дала простого способа сделать этого, что на Mac OS X, что на iOS. Однако ситуацию можно легко исправить. Как? Прошу под кат…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@implementation NSThread(BlockOnThread)
+(void)BOT_performBlockOnMainThread:(void (^)())block
{
[[NSThread mainThread] BOT_performBlock: block];
}
+(void)BOT_performBlockInBackground:(void (^)())block
{
[NSThread performSelectorInBackground: @selector(BOT_runBlock:)
withObject: [[block copy] autorelease]];
}
+(void)BOT_runBlock:(void (^)())block
{
block();
}
-(void)BOT_performBlock:(void (^)())block
{
if ([[NSThread currentThread] isEqual: self])
block( );
else
[self BOT_performBlock: block waitUntilDone: NO];
}
-(void)BOT_performBlock:(void (^)())block waitUntilDone: (BOOL)wait
{
[NSThread performSelector: @selector(BOT_runBlock:)
onThread: self
withObject: [[block copy] autorelease]
waitUntilDone: wait];
}
-(void)BOT_performBlock:(void (^)())block afterDelay: (NSTimeInterval)delay
{
[self performSelector: @selector(BOT_performBlock:)
withObject: [[block copy] autorelease]
afterDelay: delay];
}
@end
Эта категория просто добавляет несколько методов к интерфейсу класса NSThread, которые позволят вам выполнить любой блок кода на любом потоке, к которому вы имеете доступ. Увы, к названиям методов пришлось добавить префиксы, так как Objective-C не поддерживает пространства имен (а-ля, namespace в C++).
Для более глубокого понимания того, как работают блоки кода, можете обратится к документации Apple “Apple Blocks Programming Topics”.
Кроме блоков, в Mac OS 10.6 Apple ввела также технологию Grand Central Dispatch (GCD). Предполагается, что в том случае, если необходимо выполнить ресурсоемкие вычисления на главном потоке, то необходимо использовать технологию GCD. Однако, существуют ситуации (например, при использовании устаревших API и библиотек), когда до сих пор требуется выполнение кода на выделенном потоке.
Возьмем следующий пример. Пусть у вас есть сетевой поток (networkThread) на котором открыт сокет для поддержания постоянного соединения с сервером, кроме того есть парсер для данных, поступающих по этому сокету. Тут налицо частая ошибка, когда сетевые программисты выносят операции ввода-вывода в другой поток, однако парсят данные на главном, тем самым интерфейс “заикается” в момент приема данных.
Вообще, данные, которые вы хотите послать через сеть, появляются когда что-то происходит в интерфейсе приложения (например, кто-то нажал кнопку), следовательно в главном потоке. Затем необходимо эти данные передать в сетевой поток, чтобы они могли быть отправлены по назначению.
Например, необходимо послать имя (firstName) и фамилию (lastName) человека и название компании (companyName) через сетевое соединение.
В идеале, неплохо бы иметь метод, вида:
1
2
3
-(void)sendFirstName:(NSString *)firstName
lastName:(NSString *)lastName
companyName:(NSString *)companyName;
Этот метод производил бы преобразование данных в формат, в котором они будут отсылаться через сеть (например, XML, JSON, …) и ставил бы в очередь сокета для отправки.
Проблема состоит в том, что если два потока попытаются использовать один и тот же сокет в один и тот же момент времени - приложение упадет. Поэтому этот метод может быть вызван только из сетевого потока (networkThread). Это, кстати, важно и для других API, которые не обеспечивают потокобезопасность.
Поэтому создадим еще один метод вида:
1
2
3
-(void)onNetworkThreadSendFirstName:(NSString *)firstName
lastName:(NSString *)lastName
companyName:(NSString *)companyName;
и сохраним исходный для использования на главном потоке. Итак, как же вызвать метод на другом потоке? Это нельзя сделать напрямую, вместо этого нам надлежит использовать:
1
2
3
4
-(void)performSelector:(SEL)selector
onThread:(NSThread *)thread
withObject:(id)object
waitUntilDone:(BOOL)wait;
Единственная проблема в том, что мы в данном методе может передать только один параметр (withObject), хотя в примере нам необходимо передать три.
Все что нам необходимо сделать, это поместить все три аргумента в один объект. Это можно попытаться сделать с помощью массива, с помощью специального объекта (из пушки по воробьям, так как этот объект будет использоваться только для передачи аргументов в метод) или с помощью словаря, чем мы и воспользуемся.
1
-(void)onNetworkThreadSendArguments:(NSDictionary *)arguments;
Если следовать всему вышесказанному, то код будет выглядеть примерно следующим образом:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-(void)sendFirstName:(NSString *)firstName
lastName:(NSString *)lastName
companyName:(NSString *)companyName
{
NSDictionary* arguments = [NSDictionary dictionaryWithObjectsAndKeys:
firstName, @"firstName",
lastName, @"lastName",
companyName, @"companyName",
nil
];
[self performSelector: @selector(onNetworkThreadSendArguments:)
onThread: networkThread
withObject: arguments
waitUntilDone: NO];
}
-(void)onNetworkThreadSendArguments:(NSDictionary *)arguments
{
NSString* firstName = [arguments objectForKey: @"firstName"];
NSString* lastName = [arguments objectForKey: @"lastName"];
NSString* companyName = [arguments objectForKey: @"companyName"];
[self onNetworkThreadSendFirstName: firstName
lastName: lastName
companyName: companyName];
}
-(void) onNetworkThreadSendFirstName:(NSString *)firstName
lastName:(NSString *)lastName
companyName:(NSString *)companyName
{
//format and send data
}
До Mac OS X 10.6 это было обычной практикой, но она требует много дополнительных методов, упаковку и распаковку аргументов для кросс-поточных вызовов. Но благодаря нашей категории, мы может решить задачу гораздо проще:
1
2
3
4
5
6
7
8
-(void)sendFirstName:(NSString *)firstName
lastName:(NSString *)lastName
companyName:(NSString *)companyName
{
[networkThread BOT_performBlock: ^{
// format and send data
}];
}
Так как блок кода всегда будет выполнен только на сетевом потоке, метод можно вызывать из любого потока. Видно, что блоки представляют собой крайне удобное средство, даже без использования Grand Central Dispatch.