Электронная библиотека » Вандад Нахавандипур » » онлайн чтение - страница 29


  • Текст добавлен: 14 июля 2014, 12:45


Автор книги: Вандад Нахавандипур


Жанр: Зарубежная компьютерная литература, Зарубежная литература


сообщить о неприемлемом содержимом

Текущая страница: 29 (всего у книги 59 страниц)

Шрифт:
- 100% +
См. также

Разделы 7.11 и 7.15.

7.13. Создание зависимости между операциями
Постановка задачи

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

Решение

Если операция B может начать выполнение содержащейся в ней задачи только после того, как операция A выполнит свою задачу, то операция B должна добавить к себе операцию A в качестве зависимой. Это делается с помощью метода экземпляра addDependency:, относящегося к классу NSOperation:


[self.firstOperation addDependency: self.secondOperation];


Свойства firstOperation и secondOperation относятся к типу NSInvocationOperation, подробнее об этом мы поговорим в подразделе «Обсуждение» данного раздела. В приведенном примере кода первая операция, находящаяся в операционной очереди, не будет выполняться до тех пор, пока не будет выполнена задача второй операции.

Обсуждение

Выполнение операции не начинается до тех пор, пока не будут успешно завершены все операции, от которых она зависит. По умолчанию после инициализации операция не связана зависимостями с какими-либо другими операциями.

Если мы хотим внедрить зависимости в пример с кодом, описанный в разделе 7.12, то можем немного изменить реализацию делегата приложения и воспользоваться методом экземпляра addDependency:, чтобы первая операция дождалась окончания выполнения второй операции:


#import «AppDelegate.h»


@interface AppDelegate ()

@property (nonatomic, strong) NSInvocationOperation *firstOperation;

@property (nonatomic, strong) NSInvocationOperation *secondOperation;

@property (nonatomic, strong) NSOperationQueue *operationQueue;

@end


@implementation AppDelegate

– (void) firstOperationEntry:(id)paramObject{


NSLog(@"First Operation – Parameter Object = %@",

paramObject);


NSLog(@"First Operation – Main Thread = %@",

[NSThread mainThread]);


NSLog(@"First Operation – Current Thread = %@",

[NSThread currentThread]);


}


– (void) secondOperationEntry:(id)paramObject{


NSLog(@"Second Operation – Parameter Object = %@",

paramObject);


NSLog(@"Second Operation – Main Thread = %@",

[NSThread mainThread]);


NSLog(@"Second Operation – Current Thread = %@",

[NSThread currentThread]);


}


– (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


NSNumber *firstNumber = @111;

NSNumber *secondNumber = @222;


self.firstOperation = [[NSInvocationOperation alloc]

initWithTarget: self

selector:@selector(firstOperationEntry:)

object: firstNumber];


self.secondOperation = [[NSInvocationOperation alloc]

initWithTarget: self

selector:@selector(secondOperationEntry:)

object: secondNumber];


[self.firstOperation addDependency: self.secondOperation];


self.operationQueue = [[NSOperationQueue alloc] init];


/* Добавляем операции в очередь. */

[self.operationQueue addOperation: self.firstOperation];

[self.operationQueue addOperation: self.secondOperation];


NSLog(@"Main thread is here");


self.window = [[UIWindow alloc] initWithFrame:

[[UIScreen mainScreen] bounds]];

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}

Теперь после запуска программ вы увидите в окне консоли примерно следующее:

Second Operation – Parameter Object = 222

Main thread is here

Second Operation – Main Thread = <NSThread: 0x68 10250>{name = (null),

num = 1}

Second Operation – Current Thread = <NSThread: 0x6836ab0>{name = (null),

num = 3}

First Operation – Parameter Object = 111

First Operation – Main Thread = <NSThread: 0x68 10250>{name = (null),

num = 1}

First Operation – Current Thread = <NSThread: 0x6836ab0>{name = (null),

num = 3}


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

Если вы в любой момент пожелаете разорвать зависимость между двумя операциями, воспользуйтесь методом экземпляра removeDependency:, относящимся к объекту операции.

См. также

Раздел 7.12.

7.14. Создание таймеров
Постановка задачи

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

Решение

Воспользуйтесь таймером:


– (void) paint:(NSTimer *)paramTimer{

/* Делаем здесь что-либо. */

NSLog(@"Painting");

}


– (void) startPainting{


self.paintingTimer = [NSTimer

scheduledTimerWithTimeInterval:1.0

target: self

selector:@selector(paint:)

userInfo: nil

repeats: YES];


}


– (void) stopPainting{

if (self.paintingTimer!= nil){

[self.paintingTimer invalidate];

}

}


– (void)applicationWillResignActive:(UIApplication *)application{

[self stopPainting];

}


– (void)applicationDidBecomeActive:(UIApplication *)application{

[self startPainting];

}


Кроме того, метод invalidate будет высвобождать таймер сам и нам не придется делать это вручную. Как видите, мы определили свойство paintingTimer, которое следующим образом определяется в заголовочном файле (.h-файле):


#import «AppDelegate.h»


@interface AppDelegate ()

@property (nonatomic, strong) NSTimer *paintingTimer;

@end


@implementation AppDelegate

Обсуждение

Таймер – это объект, инициирующий определенное событие через заданные временные интервалы. Таймер должен быть запланирован в рабочем цикле. При определении объекта NSTimer создается незапланированный таймер, который ничего не делает, но остается в распоряжении программы на случай, если этот таймер понадобится запланировать. Как только будет сделан вызов вида scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:, начинается работа запланированного таймера и будет инициировано затребованное вами событие. Запланированным называется такой таймер, который добавлен к рабочему циклу. Чтобы получить любой таймер и инициировать связанное с ним событие, таймер нужно запланировать в рабочем цикле. Это будет продемонстрировано в следующем примере, где мы создадим незапланированный таймер, а затем вручную запланируем его в главном рабочем цикле приложения.

После того как таймер запланирован и добавлен к рабочему циклу – явно или неявно, – он начинает вызывать метод в своем целевом объекте (указываемом программистом) каждые n секунд (n также указывает программист). Поскольку n – это число с плавающей точкой, в данном параметре можно задать долю секунды.

Существуют различные способы создания, инициализации и планирования таймеров. Один из наиболее простых способов связан с использованием метода класса scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:, относящегося к классу NSTimer. Далее перечислены параметры данного метода:

• scheduledTimerWithTimeInterval – количество секунд, в течение которого таймер должен ожидать, прежде чем запустит то или иное событие. Например, если вы хотите, чтобы таймер вызывал метод в своем целевом объекте дважды в секунду, то для этого параметра нужно установить значение 0.5 (1 секунда, деленная на 2). Если вы желаете, чтобы целевой метод вызывался четыре раза в секунду, то этот параметр должен иметь значение 0.25 (1 секунда, деленная на 4);

• target – объект, который будет получать событие;

• selector – сигнатура метода в том целевом объекте, который будет получать событие;

• userInfo – объект, который будет содержаться в таймере для дальнейшего пользования (в целевом методе целевого объекта);

• repeats – параметр указывает, как таймер должен вызывать целевой метод многократно (в таком случае данный параметр получает значение YES) или однократно (тогда этот параметр получит значение NO).

Как только таймер создан и добавлен к рабочему циклу, можно остановиться и высвободить этот таймер, воспользовавшись методом экземпляра invalidate, относящимся к классу NSTimer. Таким образом будет высвобожден не только таймер, но и объект (если имеется объект, который передан таймеру и который требуется сохранять на протяжении всего жизненного цикла таймера; например, объект может быть сообщен параметру userInfo метода класса scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:, относящемуся к классу NSTimer). Если передать параметру repeats значение NO, то таймер самоуничтожится после первого прохода цикла и высвободит любой удерживаемый объект (при его наличии).

Есть и другие методы, с помощью которых можно создать запланированный таймер. Один из них – метод класса scheduledTimerWithTimeInterval: invocation: repeats:, относящийся к классу NSTimer:


– (void) paint:(NSTimer *)paramTimer{

/* Делаем здесь что-либо. */

NSLog(@"Painting");

}


– (void) startPainting{


/* Здесь находится селектор, который мы хотим вызвать. */

SEL selectorToCall = @selector(paint:);


/* Здесь на основе селектора составляется сигнатура метода.

Нам известно, что селектор относится к текущему классу,

поэтому составить сигнатуру метода совсем не сложно. */

NSMethodSignature *methodSignature =

[[self class] instanceMethodSignatureForSelector: selectorToCall];


/* Теперь основываем активизацию на сигнатуре метода. Данная активизация

требуется нам для того, чтобы запланировать таймер. */

NSInvocation *invocation =

[NSInvocation invocationWithMethodSignature: methodSignature];

[invocation setTarget: self];

[invocation setSelector: selectorToCall];


/* Теперь запускаем запланированный таймер. */

self.paintingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0

invocation: invocation

repeats: YES];


}


– (void) stopPainting{

if (self.paintingTimer!= nil){

[self.paintingTimer invalidate];

}

}


– (void)applicationWillResignActive:(UIApplication *)application{

[self stopPainting];

}


– (void)applicationDidBecomeActive:(UIApplication *)application{

[self startPainting];

}


Планирование таймера можно сравнить с запуском автомобильного двигателя. Запланированный таймер – это работающий мотор. Незапланированный таймер – это мотор, который уже готов завестись, но пока не работает. Мы можем планировать и отменять (распланировать) таймер в любой момент работы приложения, точно так же как можем заводить и глушить двигатель, не выходя из машины. Если вы хотите вручную запланировать таймер на определенный момент жизненного цикла приложения, можно воспользоваться методом класса timerWithTimeInterval: target: selector: userInfo: repeats:, относящимся к классу NSTimer. Когда придет нужный момент, можно добавить таймер к интересующему вас рабочему циклу:


– (void) startPainting{


self.paintingTimer = [NSTimer timerWithTimeInterval:1.0

target: self

selector:@selector(paint:)

userInfo: nil

repeats: YES];


/* Здесь выполняется обработка, и когда наступает нужный момент,

задействуется метод экземпляра addTimer: forMode, относящийся к классу

NSRunLoop, чтобы запланировать данный таймер в этом рабочем цикле. */


[[NSRunLoop currentRunLoop] addTimer: self.paintingTimer

forMode: NSDefaultRunLoopMode];


}

Методы класса currentRunLoop и mainRunLoop, относящиеся к классу NSRunLoop, возвращают соответственно актуальный и главный рабочие циклы конкретного приложения, что понятно из их названий[7]7
  Main (англ.) – «главный», run (англ.) – «рабочий», loop (англ.) – «цикл». – Примеч. пер.


[Закрыть]
.

Можно создавать запланированные таймеры с применением активизации, воспользовавшись вариантом с методом scheduledTimerWithTimeInterval: invocation: repeats:. С тем же успехом можно пользоваться методом класса timerWithTimeInterval: invocation: repeats:, относящимся к классу NSTimer, чтобы создать незапланированный таймер – также с применением активизации:


– (void) paint:(NSTimer *)paramTimer{

/* Делаем здесь что-нибудь. */

NSLog(@"Painting");

}


– (void) startPainting{


/* Здесь находится селектор, который мы хотим вызвать. */

SEL selectorToCall = @selector(paint:);


/* Здесь на основе селектора составляется сигнатура метода.

Нам известно, что селектор относится к текущему классу,

поэтому составить сигнатуру метода совсем не сложно. */

NSMethodSignature *methodSignature =

[[self class] instanceMethodSignatureForSelector: selectorToCall];


/* Теперь основываем активизацию на сигнатуре метода. Данная активизация

требуется нам для того, чтобы запланировать таймер. */

NSInvocation *invocation =

[NSInvocation invocationWithMethodSignature: methodSignature];


[invocation setTarget: self];

[invocation setSelector: selectorToCall];


self.paintingTimer = [NSTimer timerWithTimeInterval:1.0

invocation: invocation

repeats: YES];;


/* Здесь выполняется обработка, и когда наступает нужный момент,

задействуется метод экземпляра addTimer: forMode, относящийся к классу

NSRunLoop, чтобы запланировать данный таймер в данном рабочем цикле. */


[[NSRunLoop currentRunLoop] addTimer: self.paintingTimer

forMode: NSDefaultRunLoopMode];


}


– (void) stopPainting{

if (self.paintingTimer!= nil){

[self.paintingTimer invalidate];

}

}


– (void)applicationWillResignActive:(UIApplication *)application{

[self stopPainting];

}


– (void)applicationDidBecomeActive:(UIApplication *)application{

[self startPainting];

}


Целевой метод таймера получает экземпляр таймера, вызывающий его в качестве параметра. Например, метод paint:, показанный в начале данного раздела, демонстрирует, как таймер передается своему целевому методу – по умолчанию он (таймер) выступает в качестве единственного параметра целевого метода:


– (void) paint:(NSTimer *)paramTimer{

/* Что-то здесь делаем. */

NSLog(@"Painting");

}


Данный параметр дает нам ссылку на таймер, запускающий этот метод. Вы можете, например, при необходимости не допустить повторного запуска таймера – для этого используется метод invalidate. Кроме того, можно активизировать метод userInfo экземпляра класса NSTimer, чтобы получить объект, удерживаемый таймером (если такой объект имеется). Здесь мы имеем дело с обычным объектом, передаваемым методам инициализации NSTimer, затем этот объект передается непосредственно таймеру для дальнейшего пользования.

7.15. Параллельное программирование с использованием потоков
Постановка задачи

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

Решение

В приложении воспользуйтесь потоками. Это делается примерно так:


– (void) downloadNewFile:(id)paramObject{


@autoreleasepool {

NSString *fileURL = (NSString *)paramObject;


NSURL *url = [NSURL URLWithString: fileURL];


NSURLRequest *request = [NSURLRequest requestWithURL: url];


NSURLResponse *response = nil;

NSError *error = nil;


NSData *downloadedData =

[NSURLConnection sendSynchronousRequest: request

returningResponse:&response

error:&error];


if ([downloadedData length] > 0){

/* Загрузка завершена. */

} else {

/* Ничего загружено не было. Проверьте значение Error. */

}

}


}


– (void)viewDidLoad {

[super viewDidLoad];


NSString *fileToDownload = @"http://www.OReilly.com";


[NSThread detachNewThreadSelector:@selector(downloadNewFile:)

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 *image = [UIImage imageWithContentsOfFile: filePath];


/* Делаем что-нибудь с изображением. */

NSLog(@"Image = %@", image);


}


– (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 *image = [UIImage imageWithContentsOfFile: filePath];


/* Делаем что-то с изображением. */

NSLog(@"Image = %@", image);

}


}

– (void)viewDidLoad {


[super viewDidLoad];

[NSThread detachNewThreadSelector:@selector(autoreleaseThread:)

toTarget: self

withObject: self];

}


Страницы книги >> Предыдущая | 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | Следующая
  • 0 Оценок: 0

Правообладателям!

Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.


Популярные книги за неделю


Рекомендации