iOS. Приемы программирования — страница 59 из 124

Постановка задачи

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

Решение

Воспользуйтесь методом экземпляра performSelectorInBackground: withObject:, относящимся к классу NSObject:


— (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


[self performSelectorInBackground:@selector(firstCounter)

withObject: nil];


[self performSelectorInBackground:@selector(secondCounter)

withObject: nil];


[self performSelectorInBackground:@selector(thirdCounter)

withObject: nil];


self.window = [[UIWindow alloc] initWithFrame:

[[UIScreen mainScreen] bounds]];

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}


Методы счетчиков реализуются следующим образом:


— (void) firstCounter{


@autoreleasepool {

NSUInteger counter = 0;

for (counter = 0;

counter < 1000;

counter++){

NSLog(@"First Counter = %lu", (unsigned long)counter);

}

}


}


— (void) secondCounter{


@autoreleasepool {

NSUInteger counter = 0;

for (counter = 0;

counter < 1000;

counter++){

NSLog(@"Second Counter = %lu", (unsigned long)counter);

}

}


}


— (void) thirdCounter{


@autoreleasepool {

NSUInteger counter = 0;

for (counter = 0;

counter < 1000;

counter++){

NSLog(@"Third Counter = %lu", (unsigned long)counter);

}

}


}

Обсуждение

Метод performSelectorInBackground: withObject: создает в фоновом режиме новый поток. Ситуация эквивалентна созданию нового потока для селекторов. Самое важное, что в данном случае нужно учитывать: поскольку этот метод создает поток для конкретного селектора, у селектора должен быть автоматически высвобождаемый пул, как и у любого другого потока, который действует в среде, управляемой с применением подсчета ссылок.

7.17. Выход из потоков и таймеров

Постановка задачи

Требуется остановить поток или таймер либо не допустить его повторного запуска.

Решение

При работе с таймерами пользуйтесь методом экземпляра invalidate, относящимся к классу NSTimer. При работе с потоками используйте метод cancel. Старайтесь не применять метод exit при работе с потоками, так как он не позволяет потоку произвести после себя очистку, что в итоге приводит к утечке ресурсов из вашего приложения.


NSThread *thread = /* Здесь получаем ссылку на ваш поток. */;

[thread cancel];


NSTimer *timer = /* Здесь получаем ссылку на ваш таймер. */;

[timer invalidate];

Обсуждение

Выйти из таймера не составляет труда — можно просто вызвать метод экземпляра invalidate, относящийся к таймеру. После вызова этого метода таймер больше не будет инициировать никаких событий в своем целевом объекте.

А вот выходить из потоков немного сложнее. Когда поток находится в спящем режиме и вызывается его метод cancel, рабочий цикл этого потока выполнит свою задачу, а только потом осуществит выход. Рассмотрим это:


— (void) threadEntryPoint{


@autoreleasepool {

NSLog(@"Thread Entry Point");

while ([[NSThread currentThread] isCancelled] == NO){

[NSThread sleepForTimeInterval:4];

NSLog(@"Thread Loop");

}

NSLog(@"Thread Finished");

}


}


— (void) stopThread{


NSLog(@"Cancelling the Thread");

[self.myThread cancel];

NSLog(@"Releasing the thread");

self.myThread = nil;


}


— (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


self.myThread = [[NSThread alloc]

initWithTarget: self

selector:@selector(threadEntryPoint)

object: nil];


[self performSelector:@selector(stopThread)

withObject: nil

afterDelay:3.0f];


[self.myThread start];


self.window = [[UIWindow alloc] initWithFrame:

[[UIScreen mainScreen] bounds]];

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}


Данный код создает экземпляр класса NSThread и немедленно запускает поток. Поток в каждом цикле проводит 4 секунды в спящем режиме, а потом переходит к выполнению своей задачи. Тем не менее, прежде чем поток будет запущен, мы вызываем метод stopThread, относящийся к (написанному нами) контроллеру вида; это делается с трехсекундной задержкой. Данный метод вызывает метод cancel, относящийся к потоку, пытаясь заставить поток выйти из своего цикла. Теперь запустим приложение и посмотрим, что выводится в окне консоли:


Thread Entry Point

Cancelling the Thread

Releasing the thread

Thread Loop

Thread Finished


Итак, ясно видно, что перед выходом поток завершил текущий цикл, хотя запрос о выходе и был дан в середине цикла. Это очень распространенная ловушка. Чтобы избежать ее, нужно сначала проверять, не отменен ли поток, и лишь потом переходить к выполнению какой-либо задачи, для которой свойственны внешние побочные эффекты в цикле потока. Мы можем переписать код следующим образом. При этом операция с внешним эффектом (записыванием в регистрационный журнал) сначала проверяет, не отменен ли поток:


— (void) threadEntryPoint{


@autoreleasepool {

NSLog(@"Thread Entry Point");

while ([[NSThread currentThread] isCancelled] == NO){

[NSThread sleepForTimeInterval:4];

if ([[NSThread currentThread] isCancelled] == NO){

NSLog(@"Thread Loop");

}

}

NSLog(@"Thread Finished");

}


}


— (void) stopThread{

NSLog(@"Cancelling the Thread");

[self.myThread cancel];

NSLog(@"Releasing the thread");

self.myThread = nil;


}


— (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


self.myThread = [[NSThread alloc]

initWithTarget: self

selector:@selector(threadEntryPoint)

object: nil];


[self performSelector:@selector(stopThread)

withObject: nil

afterDelay:3.0f];


[self.myThread start];


self.window = [[UIWindow alloc] initWithFrame:

[[UIScreen mainScreen] bounds]];

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}

Глава 8. Безопасность