iOS. Приемы программирования Нахавандипур Вандад
toTarget: self
withObject: fileToDownload];
}
Обсуждение
Любое приложение iOS состоит из одного или нескольких потоков. В операционной системе iOS 5 у обычного приложения с одним контроллером вида изначально может быть от одного до пяти потоков, создаваемых системными библиотеками, с которыми связано приложение. Для вашего приложения будет создаваться как минимум один поток независимо от того, собираетесь вы пользоваться несколькими потоками или нет. Этот поток называется основным потоком пользовательского интерфейса и прикрепляется к главному рабочему циклу.
Чтобы оценить, насколько полезны потоки, проведем эксперимент. Предположим, у нас есть три цикла:
— (void) firstCounter{
NSUInteger counter = 0;
for (counter = 0;
counter < 1000;
counter++){
NSLog(@"First Counter = %lu", (unsigned long)counter);
}
}
— (void) secondCounter{
NSUInteger counter = 0;
for (counter = 0;
counter < 1000;
counter++){
NSLog(@"Second Counter = %lu", (unsigned long)counter);
}
}
— (void) thirdCounter{
NSUInteger counter = 0;
for (counter = 0;
counter < 1000;
counter++){
NSLog(@"Third Counter = %lu", (unsigned long)counter);
}
}
Очень просто, правда? Все циклы проходят от 0 до 1000, выводя на консоль номера счетчиков. Теперь предположим, что вы хотите, как обычно, запустить эти счетчики:
— (void) viewDidLoad{
[super viewDidLoad];
[self firstCounter];
[self secondCounter];
[self thirdCounter];
}
Этот код не обязательно должен находиться в методе viewDidLoad контроллера вида.
Теперь откройте окно консоли и запустите это приложение. Вы увидите, как сначала целиком выполнится первый счетчик, потом второй и, наконец, третий. Это означает, что данные циклы выполняются в одном и том же потоке. Каждый счетчик блокирует исполнение остального кода, относящегося к потоку, пока этот счетчик не завершит свой цикл.
А что, если бы мы захотели запустить все счетчики одновременно? Разумеется, для каждого из них нам пришлось бы создать отдельный поток. Но подождите! Мы ведь уже знаем, что прямо при загрузке приложение само создает для нас потоки. Кроме того, весь код, который мы уже успели создать для приложения, когда бы он ни был написан, исполняется в полученном потоке. Итак, мы уже создали по потоку для первого и второго счетчиков, а третий счетчик будет работать в главном потоке:
— (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{
NSUInteger counter = 0;
for (counter = 0;
counter < 1000;
counter++){
NSLog(@"Third Counter = %lu", (unsigned long)counter);
}
}
— (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(firstCounter)
toTarget: self
withObject: nil];
[NSThread detachNewThreadSelector:@selector(secondCounter)
toTarget: self
withObject: nil];
/* Этот код запускаем в главном потоке. */
[self thirdCounter];
}
У метода thirdCounter нет автоматически высвобождаемого пула, поскольку он не работает в новом открепленном потоке. Этот метод будет выполняться в главном потоке приложения, а главный поток располагает автоматически высвобождаемым пулом. Данный пул создается автоматически при написании любого приложения Cocoa Touch.
Ближе к концу кода мы видим вызовы селектора detachNewThreadSelector, предназначенные для запуска первого и второго счетчиков в отдельных потоках. Теперь, запустив приложение, вы увидите в окне консоли примерно следующий вывод:
Second Counter = 921
Third Counter = 301
Second Counter = 922
Second Counter = 923
Second Counter = 924
First Counter = 956
Second Counter = 925
First Counter = 957
Second Counter = 926
First Counter = 958
Third Counter = 302
Second Counter = 927
Third Counter = 303
Second Counter = 928
Иными словами, все три счетчика работают одновременно и их вывод перемежается случайным образом.
Каждый поток должен создавать автоматически высвобождаемый пул. Внутри такого пула содержатся ссылки на объекты, автоматически высвобождаемые до того, как будет высвобожден весь пул. Это очень важный механизм, действующий при управлении памятью с подсчетом ссылок в таких окружениях, как Cocoa Touch, то есть в средах, где объекты могут автоматически высвобождаться. Всякий раз при выделении экземпляра объекта количество ссылок на объект становится равным 1. Если пометить объекты как автоматически высвобождаемые, то количество ссылок на объект остается равным 1, но только до того момента, как высвободится тот пул, в котором создан объект. При высвобождении всего пула объект также получает сообщение release. Если на данный момент количество ссылок на объект так и осталось равным 1, объект высвобождается.
В каждом потоке необходимо создавать автоматически высвобождаемый пул, причем это должен быть самый первый объект, создаваемый в конкретном потоке. Если этого не сделать, то любой объект, создаваемый в потоке на протяжении его существования, будет вызывать утечку памяти. Чтобы лучше понять эту проблему, рассмотрим следующий код:
— (void) autoreleaseThread:(id)paramSender{
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *filePath = [mainBundle pathForResource:@"AnImage"
ofType:@"png"];
UIImage *i = [UIImage iWithContentsOfFile: filePath];
/* Делаем что-нибудь с изображением. */
NSLog(@"Image = %@", i);
}
— (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(autoreleaseThread:)
toTarget: self
withObject: self];
}
Если запустить этот код и одновременно следить за окном консоли, то можно увидеть примерно следующее сообщение:
*** __NSAutoreleaseNoPool(): Object 0x5b2c990 of
class NSCFString autoreleased with no pool in place — just leaking
*** __NSAutoreleaseNoPool(): Object 0x5b2ca30 of
class NSPathStore2 autoreleased with no pool in place — just leaking
*** __NSAutoreleaseNoPool(): Object 0x5b205c0 of
class NSPathStore2 autoreleased with no pool in place — just leaking
*** __NSAutoreleaseNoPool(): Object 0x5b2d650 of
class UIImage autoreleased with no pool in place — just leaking
Эти данные свидетельствуют о том, что созданный нами автоматически высвобождаемый экземпляр UIImage приводит к утечке памяти. Более того, утечку вызывают и экземпляр класса NSString под названием FilePath, а также другие объекты, которые в обычной ситуации спокойно высвободились бы. Дело в том, что при создании потока мы забыли первым делом выделить и инициализировать автоматически высвобождаемый пул — именно первым делом. Далее приведен правильный код. Можете сами его протестировать и убедиться, что никаких утечек не возникает:
— (void) autoreleaseThread:(id)paramSender{
@autoreleasepool {
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *filePath = [mainBundle pathForResource:@"AnImage"
ofType:@"png"];
UIImage *i = [UIImage iWithContentsOfFile: filePath];
/* Делаем что-то с изображением. */
NSLog(@"Image = %@", i);
}
}
— (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(autoreleaseThread:)
toTarget: self
withObject: self];
}
7.16. Активизация фоновых методов
Постановка задачи
Необходимо найти простой способ создания потоков так, чтобы с потоками не приходилось работать напрямую.
Решение
Воспользуйтесь методом экземпляра 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];
Обсуждение