Есть ли способ заставить drawRect работать прямо сейчас?
Оригинальный вопрос...............................................
Если вы продвинутый пользователь drawRect, вы будете знать, что drawRect, конечно же, не будет работать до тех пор, пока "вся обработка не будет завершена".
setNeedsDisplay помечает представление как недействительное и ОС и в основном ждет, пока вся обработка не будет завершена. Это может приводить в бешенство в обычной ситуации, когда вы хотите иметь:
- контроллер вида 1
- запускает некоторую функцию 2
- который постепенно 3
- создает все более сложные произведения искусства и 4
- на каждом шаге вы устанавливаете NeedsDisplay (неправильно!) 5
- пока вся работа не сделана 6
Конечно, когда вы делаете выше 1-6, все, что происходит, это то, что drawRect запускается один раз только после шага 6.
Ваша цель - обновить представление в пункте 5. Что делать?
Решение исходного вопроса.............................................,
Одним словом, вы можете (A) фон большая картина, и называют на первый план для обновления пользовательского интерфейса или (В), возможно, спорное есть четыре "непосредственные" методы предложил не использовать фоновый процесс. Для результата того, что работает, запустите демонстрационную программу. У него есть #defines для всех пяти методов.
Поистине поразительное альтернативное решение, представленное Томом Свифтом..................
Том Свифт объяснил удивительную идею простой манипуляции с циклом выполнения. Вот как вы запускаете цикл выполнения:
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [дата NSDate]];
Это действительно удивительный образец техники. Конечно, нужно быть предельно осторожным при манипулировании циклом выполнения, и, как многие отмечали, этот подход предназначен исключительно для экспертов.
Странная проблема, которая возникает.............................................,
Несмотря на то, что некоторые методы работают, они на самом деле не "работают", потому что есть странный прогрессирующий замедляющий артефакт, который вы четко увидите в демоверсии.
Прокрутите до "ответа", который я вставил ниже, показывая вывод консоли - вы можете видеть, как он постепенно замедляется.
Вот новый вопрос SO:
Загадочная проблема "прогрессивного замедления" в цикле выполнения / drawRect
Вот V2 демо-приложения...
http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html
Вы увидите, что тестирует все пять методов,
#ifdef TOMSWIFTMETHOD
[self setNeedsDisplay];
[[NSRunLoop currentRunLoop]
runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
#endif
#ifdef HOTPAW
[self setNeedsDisplay];
[CATransaction flush];
#endif
#ifdef LLOYDMETHOD
[CATransaction begin];
[self setNeedsDisplay];
[CATransaction commit];
#endif
#ifdef DDLONG
[self setNeedsDisplay];
[[self layer] displayIfNeeded];
#endif
#ifdef BACKGROUNDMETHOD
// here, the painting is being done in the bg, we have been
// called here in the foreground to inval
[self setNeedsDisplay];
#endif
Вы можете сами увидеть, какие методы работают, а какие нет.
Вы можете увидеть странное "прогрессивное замедление". почему это происходит?
вы можете увидеть спорный метод TOMSWIFT, нет фактически никаких проблем с отзывчивостью. нажмите для ответа в любое время. (но все еще причудливая проблема "прогрессирующего замедления")
Таким образом, подавляющая вещь - это странное "прогрессивное замедление": на каждой итерации, по неизвестным причинам, время, затрачиваемое на цикл, уменьшается. Обратите внимание, что это относится как к тому, чтобы сделать это "правильно" (фоновый вид), так и к использованию одного из "непосредственных" методов.
Практические решения........................
Для тех, кто читает в будущем, если вы на самом деле не можете заставить его работать в рабочем коде из-за "загадочного прогрессирующего замедления"... Felz и Void представили поразительные решения в другом конкретном вопросе, надеюсь, это поможет.
10 ответов
Обновления пользовательского интерфейса происходят в конце текущего прохода цикла выполнения. Эти обновления выполняются в основном потоке, поэтому все, что выполняется в основном потоке в течение длительного времени (длительные вычисления и т. Д.), Будет препятствовать запуску обновлений интерфейса. Кроме того, все, что выполняется некоторое время в главном потоке, также приведет к тому, что ваша сенсорная обработка не отвечает.
Это означает, что нет никакого способа "заставить" обновление пользовательского интерфейса происходить из какой-либо другой точки процесса, выполняющегося в основном потоке. Предыдущее утверждение не совсем правильно, как показывает ответ Тома. Вы можете позволить циклу выполнения завершиться в середине операций, выполняемых в основном потоке. Однако это все еще может снизить скорость отклика вашего приложения.
В общем случае рекомендуется переместить все, что требует времени для выполнения, в фоновый поток, чтобы пользовательский интерфейс оставался отзывчивым. Однако любые обновления, которые вы хотите выполнить для пользовательского интерфейса, должны быть сделаны обратно в главном потоке.
Возможно, самый простой способ сделать это в Snow Leopard и iOS 4.0+ - это использовать блоки, как в следующем элементарном примере:
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// Do some work
dispatch_async(main_queue, ^{
// Update the UI
});
});
Do some work
часть вышеупомянутого может быть длительным вычислением или операцией, которая зацикливается на нескольких значениях. В этом примере пользовательский интерфейс обновляется только в конце операции, но если вы хотите непрерывного отслеживания прогресса в своем пользовательском интерфейсе, вы можете поместить диспетчеризацию в основную очередь там, где вам когда-либо требовалось выполнить обновление пользовательского интерфейса.
Для более старых версий ОС вы можете разорвать фоновый поток вручную или с помощью NSOperation. Для ручной фоновой нарезки вы можете использовать
[NSThread detachNewThreadSelector:@selector(doWork) toTarget:self withObject:nil];
или же
[self performSelectorInBackground:@selector(doWork) withObject:nil];
а затем обновить интерфейс, который вы можете использовать
[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];
Обратите внимание, что в предыдущем методе я нашел аргумент NO, необходимый для получения постоянных обновлений пользовательского интерфейса при работе с непрерывным индикатором выполнения.
В этом примере приложения, которое я создал для своего класса, показано, как использовать NSOperations и очереди для выполнения фоновой работы, а затем обновлять пользовательский интерфейс, когда закончите. Кроме того, мое приложение Molecules использует фоновые потоки для обработки новых структур со строкой состояния, которая обновляется по мере продвижения. Вы можете скачать исходный код, чтобы увидеть, как я этого добился.
Если я правильно понимаю ваш вопрос, есть простое решение для этого. Во время вашей длительной подпрограммы вы должны указать текущему циклу выполнения обрабатывать одну итерацию (или более) цикла выполнения в определенных точках вашей собственной обработки. например, когда вы хотите обновить дисплей. Любые представления с грязными областями обновления будут иметь свои методы drawRect:, вызываемые при запуске runloop.
Чтобы указать текущему циклу выполнения обработать одну итерацию (а затем вернуться к вам...):
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
Вот пример (неэффективной) длительной подпрограммы с соответствующим drawRect - каждый в контексте пользовательского UIView:
- (void) longRunningRoutine:(id)sender
{
srand( time( NULL ) );
CGFloat x = 0;
CGFloat y = 0;
[_path moveToPoint: CGPointMake(0, 0)];
for ( int j = 0 ; j < 1000 ; j++ )
{
x = 0;
y = (CGFloat)(rand() % (int)self.bounds.size.height);
[_path addLineToPoint: CGPointMake( x, y)];
y = 0;
x = (CGFloat)(rand() % (int)self.bounds.size.width);
[_path addLineToPoint: CGPointMake( x, y)];
x = self.bounds.size.width;
y = (CGFloat)(rand() % (int)self.bounds.size.height);
[_path addLineToPoint: CGPointMake( x, y)];
y = self.bounds.size.height;
x = (CGFloat)(rand() % (int)self.bounds.size.width);
[_path addLineToPoint: CGPointMake( x, y)];
[self setNeedsDisplay];
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
}
[_path removeAllPoints];
}
- (void) drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor( ctx, [UIColor blueColor].CGColor );
CGContextFillRect( ctx, rect);
CGContextSetStrokeColorWithColor( ctx, [UIColor whiteColor].CGColor );
[_path stroke];
}
А вот и полностью рабочий образец, демонстрирующий эту технику.
С некоторыми изменениями вы, вероятно, сможете настроить это, чтобы сделать остальную часть пользовательского интерфейса (то есть пользовательский ввод) отзывчивой.
Обновление (предостережение для использования этой техники)
Я просто хочу сказать, что согласен со многими отзывами других, которые говорят, что это решение (вызов runMode: принудительно вызвать drawRect:) не обязательно является хорошей идеей. Я ответил на этот вопрос тем, что, по моему мнению, является фактическим ответом "вот как" на поставленный вопрос, и я не собираюсь рекламировать его как "правильную" архитектуру. Кроме того, я не говорю, что не может быть других (лучших?) Способов достижения того же эффекта - конечно, могут быть и другие подходы, о которых я не знал.
Обновление (ответ на пример кода Джо и вопрос о производительности)
Замедление производительности, которое вы наблюдаете, - это издержки запуска runloop на каждой итерации вашего кода рисования, который включает в себя рендеринг слоя на экран, а также все другие обработки, выполняемые runloop, такие как сбор и обработка ввода.
Один из вариантов может состоять в том, чтобы вызывать runloop реже.
Другим вариантом может быть оптимизация кода рисования. В его нынешнем виде (и я не знаю, является ли это вашим настоящим приложением или просто вашим образцом...), есть несколько вещей, которые вы могли бы сделать, чтобы сделать его быстрее. Первое, что я хотел бы сделать, это переместить весь код UIGraphicsGet/Save/Restore за пределы цикла.
Однако с архитектурной точки зрения я бы настоятельно рекомендовал рассмотреть некоторые другие подходы, упомянутые здесь. Я не вижу причин, по которым вы не можете структурировать свой рисунок, чтобы он происходил в фоновом потоке (алгоритм не изменился), и используете таймер или другой механизм, чтобы сигнализировать основному потоку, чтобы он обновлял свой пользовательский интерфейс с определенной частотой, пока рисунок не будет завершен. Я думаю, что большинство людей, которые участвовали в обсуждении, согласились бы, что это был бы "правильный" подход.
Вы можете сделать это несколько раз в цикле, и он будет работать нормально, без потоков, без возни с циклом выполнения и т. Д.
[CATransaction begin];
// modify view or views
[view setNeedsDisplay];
[CATransaction commit];
Если до цикла уже существует неявная транзакция, вам нужно зафиксировать ее с помощью [CATransaction commit], прежде чем это сработает.
Для того, чтобы drawRect вызывался как можно быстрее (что не обязательно сразу, поскольку ОС все еще может ждать, например, следующего обновления дисплея оборудования и т. Д.), Приложение должно как можно скорее отключить цикл выполнения пользовательского интерфейса, выход из любого и всех методов в потоке пользовательского интерфейса и на ненулевое количество времени.
Вы можете сделать это в главном потоке, выделив любую обработку, которая занимает больше времени кадра анимации, в более короткие порции и запланировав продолжение работы только после небольшой задержки (так что drawRect может выполняться в промежутках), или выполнив обработку в фоновый поток с периодическим вызовом executeSelectorOnMainThread для выполнения setNeedsDisplay с некоторой разумной частотой кадров анимации.
Не-OpenGL-метод для немедленного обновления дисплея (что означает при ближайшем обновлении аппаратного дисплея или трех) - это замена видимого содержимого CALayer на изображение или CGBitmap, в которое вы нарисовали. Приложение может рисовать Quartz в растровое изображение Core Graphics практически в любое время.
Новый добавленный ответ:
Пожалуйста, смотрите комментарии Брэда Ларсона ниже и комментарий Кристофера Ллойда к другому ответу здесь, как подсказку, ведущую к этому решению.
[ CATransaction flush ];
вызовет drawRect для представлений, для которых был выполнен запрос setNeedsDisplay, даже если сброс выполняется изнутри метода, который блокирует цикл выполнения пользовательского интерфейса.
Обратите внимание, что при блокировке потока пользовательского интерфейса необходима очистка Core Animation для обновления изменяющегося содержимого CALayer. Таким образом, для анимации графического контента, чтобы показать прогресс, оба они могут оказаться формами одного и того же.
Новое добавленное примечание к новому добавленному ответу выше:
Не выполняйте сбрасывание быстрее, чем ваш drawRect или анимация может завершить рисование, так как это может поставить в очередь сбросы, вызывая странные эффекты анимации.
Не подвергая сомнению мудрость этого (что вы должны сделать), вы можете сделать:
[myView setNeedsDisplay];
[[myView layer] displayIfNeeded];
-setNeedsDisplay
пометит вид как нуждающийся в перерисовке.-displayIfNeeded
заставит фоновый слой представления перерисовать, но только если он был помечен как необходимый для отображения.
Я подчеркну, однако, что ваш вопрос свидетельствует об архитектуре, которая может использовать некоторые переделки. Во всех, кроме исключительно редких случаях, вам никогда не нужно или нужно принудительно перерисовывать представление. UIKit не создан с учетом этого варианта использования, и если он работает, считайте себя счастливчиком.
Я понимаю, что это старая тема, но я хотел бы предложить чистое решение данной проблемы.
Я согласен с другими авторами, что в идеальной ситуации всю тяжелую работу следует выполнять в фоновом потоке, однако бывают случаи, когда это просто невозможно, поскольку трудоемкая часть требует большого доступа к не потоко-безопасным методам, таким как те, которые предлагает UIKit. В моем случае инициализация моего пользовательского интерфейса занимает много времени, и я ничего не могу запустить в фоновом режиме, поэтому мой лучший вариант - обновить индикатор выполнения во время инициализации.
Однако, как только мы думаем с точки зрения идеального подхода GCD, решение на самом деле является простым. Мы выполняем всю работу в фоновом потоке, разделяя его на чаки, которые вызываются синхронно в основном потоке. Цикл выполнения будет выполняться для каждого патрона, обновляя пользовательский интерфейс и любые индикаторы выполнения и т. Д.
- (void)myInit
{
// Start the work in a background thread.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// Back to the main thread for a chunk of code
dispatch_sync(dispatch_get_main_queue(), ^{
...
// Update progress bar
self.progressIndicator.progress = ...:
});
// Next chunk
dispatch_sync(dispatch_get_main_queue(), ^{
...
// Update progress bar
self.progressIndicator.progress = ...:
});
...
});
}
Конечно, это, в сущности, то же самое, что и метод Брэда, но его ответ не совсем решает проблему - проблему запуска большого количества не поточно-безопасного кода при периодическом обновлении пользовательского интерфейса.
Я думаю, самый полный ответ приходит из поста в блоге Джеффри Сэмбелла " Асинхронные операции в iOS с Grand Central Dispatch", и он сработал для меня! Это в основном то же решение, которое было предложено Брэдом выше, но полностью объясненное с точки зрения модели параллелизма OSX/IOS.
dispatch_get_current_queue
функция вернет текущую очередь, из которой отправлен блок, иdispatch_get_main_queue
Функция вернет основную очередь, в которой работает ваш пользовательский интерфейс.
dispatch_get_main_queue
функция очень полезна для обновления пользовательского интерфейса приложения iOS, так какUIKit
методы не являются поточно-ориентированными (за некоторыми исключениями), поэтому любые вызовы, которые вы делаете для обновления элементов пользовательского интерфейса, всегда должны выполняться из главной очереди.Типичный вызов GCD будет выглядеть примерно так:
// Doing something on the main thread dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL); dispatch_async(myQueue, ^{ // Perform long running process dispatch_async(dispatch_get_main_queue(), ^{ // Update the UI }); }); // Continue doing other stuff on the // main thread while process is running.
И вот мой рабочий пример (iOS 6+). Отображает кадры сохраненного видео, используя AVAssetReader
учебный класс:
//...prepare the AVAssetReader* asset_reader earlier and start reading frames now:
[asset_reader startReading];
dispatch_queue_t readerQueue = dispatch_queue_create("Reader Queue", NULL);
dispatch_async(readerQueue, ^{
CMSampleBufferRef buffer;
while ( [asset_reader status]==AVAssetReaderStatusReading )
{
buffer = [asset_reader_output copyNextSampleBuffer];
if (buffer!=nil)
{
//The point is here: to use the main queue for actual UI operations
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI using the AVCaptureVideoDataOutputSampleBufferDelegate style function
[self captureOutput:nil didOutputSampleBuffer:buffer fromConnection:nil];
CFRelease (buffer);
});
}
}
});
Первая часть этого примера может быть найдена здесь в ответе Дамиана.
Вы пытались выполнить тяжелую обработку во вторичном потоке и перезванивать основному потоку для планирования обновлений представления? NSOperationQueue
делает такие вещи довольно легкими.
Пример кода, который принимает массив NSURL в качестве входных данных и асинхронно загружает их все, уведомляя основной поток о завершении и сохранении каждого из них.
- (void)fetchImageWithURLs:(NSArray *)urlArray {
[self.retriveAvatarQueue cancelAllOperations];
self.retriveAvatarQueue = nil;
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
for (NSUInteger i=0; i<[urlArray count]; i++) {
NSURL *url = [urlArray objectAtIndex:i];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(cacheImageWithIndex:andURL:)]];
[inv setTarget:self];
[inv setSelector:@selector(cacheImageWithIndex:andURL:)];
[inv setArgument:&i atIndex:2];
[inv setArgument:&url atIndex:3];
NSInvocationOperation *invOp = [[NSInvocationOperation alloc] initWithInvocation:inv];
[opQueue addOperation:invOp];
[invOp release];
}
self.retriveAvatarQueue = opQueue;
[opQueue release];
}
- (void)cacheImageWithIndex:(NSUInteger)index andURL:(NSURL *)url {
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = PATH_FOR_IMG_AT_INDEX(index);
NSError *error = nil;
// Save the file
if (![fileManager createFileAtPath:filePath contents:imageData attributes:nil]) {
DLog(@"Error saving file at %@", filePath);
}
// Notifiy the main thread that our file is saved.
[self performSelectorOnMainThread:@selector(imageLoadedAtPath:) withObject:filePath waitUntilDone:NO];
}
Относительно исходной проблемы:
Одним словом, вы можете (A) фонировать большую картину и вызывать передний план для обновлений пользовательского интерфейса или (B) возможно спорно , есть четыре предложенных «немедленных» метода, которые не используют фоновый процесс. Чтобы получить результат, запустите демо-программу. Он имеет #define для всех пяти методов.
Поочередно за Тома Свифта
Том Свифт объяснил удивительную идею простого управления циклом выполнения . Вот как вы запускаете цикл выполнения:
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [дата NSDate]];
Это действительно удивительный образец инженерной мысли. Конечно, нужно быть предельно осторожным при манипулировании циклом выполнения, и, как многие указывали, этот подход предназначен исключительно для экспертов.
Однако возникает странная проблема...
Несмотря на то, что некоторые методы работают, на самом деле они не «работают», потому что есть странный артефакт прогрессивного замедления, который вы ясно увидите в демо.
Прокрутите до «ответа», который я вставил ниже, показывающего вывод консоли — вы можете видеть, как он постепенно замедляется.
Вот новый вопрос SO:
загадочная проблема «прогрессивного замедления» в цикле выполнения/drawRect
Вот вторая версия демонстрационного приложения...
http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html
Вы увидите, что он тестирует все пять методов,
#ifdef TOMSWIFTMETHOD
[self setNeedsDisplay];
[[NSRunLoop currentRunLoop]
runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
#endif
#ifdef HOTPAW
[self setNeedsDisplay];
[CATransaction flush];
#endif
#ifdef LLOYDMETHOD
[CATransaction begin];
[self setNeedsDisplay];
[CATransaction commit];
#endif
#ifdef DDLONG
[self setNeedsDisplay];
[[self layer] displayIfNeeded];
#endif
#ifdef BACKGROUNDMETHOD
// here, the painting is being done in the bg, we have been
// called here in the foreground to inval
[self setNeedsDisplay];
#endif
Вы можете сами убедиться, какие методы работают, а какие нет.
вы можете увидеть причудливое «прогрессивное замедление». почему это происходит?
вы можете видеть с помощью спорного метода TOMSWIFT, на самом деле нет никаких проблем с отзывчивостью. нажмите для ответа в любое время. (но все еще странная проблема «прогрессивного замедления»)
Таким образом, подавляющим является это странное «прогрессивное замедление»: на каждой итерации по неизвестным причинам время, затрачиваемое на цикл, уменьшается. Обратите внимание, что это относится как к тому, чтобы сделать это «правильно» (фоновый просмотр), так и к использованию одного из «немедленных» методов.
Практические решения?
Для тех, кто будет читать в будущем, если вы на самом деле не можете заставить это работать в производственном коде из-за «загадочного прогрессирующего замедления» ... Felz и Void представили поразительные решения в другом конкретном вопросе, надеюсь, это поможет.
Джо - если вы хотите настроить его так, чтобы ваша длительная обработка происходила внутри drawRect, вы можете заставить его работать. Я только что написал тестовый проект. Оно работает. Смотрите код ниже.
LengthyComputationTestAppDelegate.h:
#import <UIKit/UIKit.h>
@interface LengthyComputationTestAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@end
LengthComputationTestAppDelegate.m:
#import "LengthyComputationTestAppDelegate.h"
#import "Incrementer.h"
#import "IncrementerProgressView.h"
@implementation LengthyComputationTestAppDelegate
@synthesize window;
#pragma mark -
#pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
IncrementerProgressView *ipv = [[IncrementerProgressView alloc]initWithFrame:self.window.bounds];
[self.window addSubview:ipv];
[ipv release];
[self.window makeKeyAndVisible];
return YES;
}
Incrementer.h:
#import <Foundation/Foundation.h>
//singleton object
@interface Incrementer : NSObject {
NSUInteger theInteger_;
}
@property (nonatomic) NSUInteger theInteger;
+(Incrementer *) sharedIncrementer;
-(NSUInteger) incrementForTimeInterval: (NSTimeInterval) timeInterval;
-(BOOL) finishedIncrementing;
incrementer.m:
#import "Incrementer.h"
@implementation Incrementer
@synthesize theInteger = theInteger_;
static Incrementer *inc = nil;
-(void) increment {
theInteger_++;
}
-(BOOL) finishedIncrementing {
return (theInteger_>=100000000);
}
-(NSUInteger) incrementForTimeInterval: (NSTimeInterval) timeInterval {
NSTimeInterval negativeTimeInterval = -1*timeInterval;
NSDate *startDate = [NSDate date];
while (!([self finishedIncrementing]) && [startDate timeIntervalSinceNow] > negativeTimeInterval)
[self increment];
return self.theInteger;
}
-(id) init {
if (self = [super init]) {
self.theInteger = 0;
}
return self;
}
#pragma mark --
#pragma mark singleton object methods
+ (Incrementer *) sharedIncrementer {
@synchronized(self) {
if (inc == nil) {
inc = [[Incrementer alloc]init];
}
}
return inc;
}
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (inc == nil) {
inc = [super allocWithZone:zone];
return inc; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
@end
IncrementerProgressView.m:
#import "IncrementerProgressView.h"
@implementation IncrementerProgressView
@synthesize progressLabel = progressLabel_;
@synthesize nextUpdateTimer = nextUpdateTimer_;
-(id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame: frame]) {
progressLabel_ = [[UILabel alloc]initWithFrame:CGRectMake(20, 40, 300, 30)];
progressLabel_.font = [UIFont systemFontOfSize:26];
progressLabel_.adjustsFontSizeToFitWidth = YES;
progressLabel_.textColor = [UIColor blackColor];
[self addSubview:progressLabel_];
}
return self;
}
-(void) drawRect:(CGRect)rect {
[self.nextUpdateTimer invalidate];
Incrementer *shared = [Incrementer sharedIncrementer];
NSUInteger progress = [shared incrementForTimeInterval: 0.1];
self.progressLabel.text = [NSString stringWithFormat:@"Increments performed: %d", progress];
if (![shared finishedIncrementing])
self.nextUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0. target:self selector:(@selector(setNeedsDisplay)) userInfo:nil repeats:NO];
}
- (void)dealloc {
[super dealloc];
}
@end