попытаться использовать это для чего-то другого.

нальный вопрос ...............................................

Если вы продвинутый пользователь drawRect, вы будете знать, что drawRect, конечно же, на самом деле не будет работать до тех пор, пока «вся обработка не будет завершена».

setNeedsDisplay помечает представление как недействительное и ОС и в основном ждет, пока вся обработка не будет завершена. Это может приводить в бешенство в обычной ситуации, когда вы хотите иметь:

контроллер вида 1запускает некоторую функцию 2который постепенно 3создает все более сложные произведения искусства и 4на каждом шаге вы устанавливаете NeedsDisplay (неправильно!) 5пока вся работа не сделана 6

Конечно, когда вы делаете выше 1-6, все, что происходит, это то, что drawRect запускаетсятолько однажды после шага 6.

Ваша цель - обновить представление в пункте 5. Что делать?

Решение исходного вопроса ............................................. ,

Одним словом, вы можете(Задний фон большая картина и вызов на передний план для обновления пользовательского интерфейса или(В), возможно, спорно предлагается четыре «немедленных» метода, которые не используют фоновый процесс. Для результата того, что работает, запустите демонстрационную программу. У него есть #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 представили поразительные решения в другом конкретном вопросе, надеюсь, это поможет.

 Jonathan Grynspan23 янв. 2011 г., 17:32
Он не заставляет UIView перерисовывать - он заставляет цикл цикла зацикливаться. Другой этап игры, а не то, что вы обычно должны делать в производственном коде (так как вы будете вытеснять свой код для запуска всего остального в цикле, включая таймеры, сокеты и тому подобное.)
 hotpaw223 янв. 2011 г., 19:01
Замедление: если вы вкладываете циклы выполнения пользовательского интерфейса или drawRects без специальной обработки для условий гонки, вы можете закончить перерисовку одних и тех же пикселей несколько раз, возможно, даже если старые события пользовательского интерфейса или обновления пикселей перезаписывают более новые вещи.
 Eiko23 янв. 2011 г., 20:06
Извините, но весь дизайн принудительного обновления не работает. Просто сделайте хруст данных в фоновом режиме и обновите (setNeedsDisplayInRect :) необходимые разделы экранакогда-то - т.е. каждую 1/10 секунды или когда хруст достиг определенного реального прогресса. Не борись за рамки.

Ответы на вопрос(9)

и он будет работать нормально, без потоков, без возни с циклом выполнения и т. Д.

[CATransaction begin];
// modify view or views
[view setNeedsDisplay];
[CATransaction commit];

Если до цикла уже существует неявная транзакция, вам нужно зафиксировать ее с помощью [CATransaction commit], прежде чем это сработает.

 hotpaw223 янв. 2011 г., 20:38
@Joe Blow: Вы пытаетесь обновить с частотой кадров выше, чем позволяет время выполнения drawRect? Это может быть причиной замедления. Попробуйте уменьшить частоту кадров в секунду, лучше подходя к тому, сколько времени занимает ваш drawRect для завершения. например не сбрасывайте, если с момента последнего drawRect прошло так много миллисекунд (несколько кратных 16,7).
 Christopher Lloyd24 янв. 2011 г., 17:32
Если вы поместите [CATransaction commit] в строку 17 SimpleDrawingView, он закроет текущую неявную транзакцию, тогда мой ответ будет работать.
 hotpaw223 янв. 2011 г., 18:51
Это работает для меня (вызывает drawRect представления), если я делаю сброс CATransaction после setNeedsDisplay, а не begin + commit.
 hotpaw223 янв. 2011 г., 19:24
Обратите внимание, что мое тестирование [CATransaction flush] не пытается повторно войти в тот же обработчик drawRect или UI, а сбрасывает drawRect из и, следовательно, анимирует отдельное представление хода выполнения. Это работает для меня, даже внутри длинных вычислений, блокирующих потоки пользовательского интерфейса.
 hotpaw223 янв. 2011 г., 21:01
@Joe Blow: также обратите внимание, что (текущие!) IPhone и iPad имеют единый процессор приложений. Таким образом, время, затрачиваемое на прорисовку Core Graphics, обновления представлений или обработку цикла выполнения пользовательского интерфейса, отнимает время и, следовательно, замедляет всю остальную обработку.

самый полный ответ приходит из сообщения в блоге Джеффри Сэмбелла«Асинхронные операции в 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);
            });
        }
    }
});

Первая часть этого образца может быть найденаВот в ответ Дамиана.

 Fattie29 сент. 2014 г., 06:42
фантастическая информация ...

о сразу, поскольку ОС может все еще ждать, например, следующего обновления аппаратного дисплея и т. Д.), Приложение должно как можно скорее отключить цикл выполнения пользовательского интерфейса, выход из любого и всех методов в потоке пользовательского интерфейса и ненулевое количество времени.

Вы можете сделать это в основном потоке, выделив любую обработку, которая занимает больше времени кадра анимации, в более короткие порции и запланировав продолжение работы только после небольшой задержки (так что drawRect может выполняться в промежутках), или выполнив обработку в фоновый поток с периодическим вызовом executeSelectorOnMainThread для выполнения setNeedsDisplay с некоторой разумной частотой кадров анимации.

Не-OpenGL-метод для немедленного обновления дисплея (что означает при ближайшем обновлении аппаратного дисплея или трех) - это замена видимого содержимого CALayer на изображение или CGBitmap, в которое вы нарисовали. Приложение может рисовать Quartz в растровое изображение Core Graphics практически в любое время.

Новый добавленный ответ:

Пожалуйста, смотрите комментарии Брэда Ларсона ниже и комментарий Кристофера Ллойда о другом ответе здесь, как подсказку, ведущую к этому решению.

[ CATransaction flush ];

вызовет drawRect для представлений, для которых был выполнен запрос setNeedsDisplay, даже если сброс выполняется изнутри метода, который блокирует цикл выполнения пользовательского интерфейса.

Обратите внимание, что при блокировке потока пользовательского интерфейса необходима очистка Core Animation для обновления изменяющегося содержимого CALayer. Таким образом, для анимации графического контента, чтобы показать прогресс, оба они могут в конечном итоге быть формами одного и того же.

Новое добавленное примечание к новому добавленному ответу выше:

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

 Mike Abdullah19 янв. 2011 г., 22:10
Вы должны иметь возможность периодически запускать цикл выполнения для обновления пользовательского интерфейса. Тем не менее, это (по моему опыту) не примет события пользовательского ввода, поэтому вам гораздо лучше вернуть контроль над циклом выполнения в систему как можно скорее.
 hotpaw220 янв. 2011 г., 00:47
@ Джо Блоу: Да. У меня есть приложение, которое вызывает setNeedsDisplay и делает один drawRect только при инициализации представления. С этого момента вся анимация выполняется путем перемещения UIViews и замены содержимого CALayer с помощью CGImageRefs. Нет drawRects. Кажется, обеспечивает прилично плавную частоту кадров для индикаторов выполнения и т. Д. Даже без каких-либо сбросов CATransaction (однако приложение быстро возвращается в цикл выполнения пользовательского интерфейса).
 Brad Larson♦19 янв. 2011 г., 22:40
По моему опыту, обмен контента в CALayer обновляется только немедленно, если вы выполняете[CATransaction flush] после того, как вы измените этот контент. Однако это может привести к появлению артефактов в других местах вашего интерфейса.
 hotpaw219 янв. 2011 г., 21:36
@Joe - Выйти из процедуры обработки сейчас, прежде чем это будет сделано. Метод, который я использую, состоит в том, чтобы разбить мои длинные циклы обработки на состояния конечного автомата (из алгоритмов 101) и просто выйти из процедуры ASAP с последующими состояниями, если не концом цикла, вызываемыми таймером с задержкой в ​​один кадр , Если вы не можете сделать это из-за большого количества вложений или рекурсии, вам лучше всего изучить всю обработку в фоновом потоке.
 hotpaw220 янв. 2011 г., 22:37
@Brad Larson: я написал и запустил тестовое приложение на своем iPhone, которое периодически обновляет содержимое CALayer в методе, который блокирует поток пользовательского интерфейса на многие секунды. Да, для получения, казалось бы, немедленного обновления требуется очистка CATransaction (я использовал 20 кадров в секунду). Но, возможно, из-за того, что все мои CALayers и подпредставления не перекрывались и никакие другие анимации не были запланированы, я увидел нулевые артефакты. YMMV.

ивать основному потоку для планирования обновлений представления?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 setSel,ector:@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];

}
 Jonathan Grynspan19 янв. 2011 г., 20:55
Да, вы обнаружили, что не можете обновить экран со скоростью 500 кадров в секунду.
 Fattie19 янв. 2011 г., 21:22
Привет Джонатон - нет, это совершенно не связано с тем, о чем я говорю. Просто поместите NSLogs в drawRect и поэкспериментируйте, чтобы обнаружить «пробел» после текущей процедуры, когда ОС попытается нарисовать.
Решение Вопроса

охода цикла выполнения. Эти обновления выполняются в основном потоке, поэтому все, что выполняется в основном потоке в течение длительного времени (длительные вычисления и т. Д.), Будет препятствовать запуску обновлений интерфейса. Кроме того, все, что выполняется некоторое время в главном потоке, также приведет к тому, что ваша сенсорная обработка не будет отвечать.

Это означает, что не существует способа «заставить» обновление пользовательского интерфейса происходить из какой-либо другой точки процесса, выполняющегося в основном потоке. Предыдущее утверждение не совсем правильно, как показывает ответ Тома. Вы можете позволить циклу выполнения завершиться в середине операций, выполняемых в основном потоке. Однако это все еще может снизить скорость отклика вашего приложения.

В общем случае рекомендуется переместить все, что требует времени для выполнения, в фоновый поток, чтобы пользовательский интерфейс оставался отзывчивым. Однако любые обновления, которые вы хотите выполнить для пользовательского интерфейса, должны быть выполнены обратно в главном потоке.

Возможно, самый простой способ сделать это под 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 и очереди для выполнения фоновой работы, а затем обновить пользовательский интерфейс, когда закончите. Также мойМолекулы Приложение использует фоновые потоки для обработки новых структур, со строкой состояния, которая обновляется по мере этого. Вы можете скачать исходный код, чтобы увидеть, как я этого добился.

 Brad Larson♦20 янв. 2011 г., 21:40
@Joe - я считаю, что проблема сwaitUntilDone: значение YES означает, что ваш фоновый поток будет блокироваться при выполнении до тех пор, пока основной поток не получит возможность завершить цикл выполнения. Это может вызвать остановку вашего потока обработки и предотвратило регулярные обновления пользовательского интерфейса по крайней мере для меня. Ваш поток обработки не должен заботиться о том, что пользовательский интерфейс завершил свое обновление, поэтому имеет смысл заставить его запускать селектор и продолжать делать то, что он делает.
 hotpaw220 янв. 2011 г., 04:15
@Brad Larson: Какие артефакты CA вы видите?
 Brad Larson♦19 янв. 2011 г., 22:42
@Joe - Как я прокомментировал в ответе hotpaw2, я мог только быстро обновлять CALayers, используя операцию сброса в Core Animation, но это приводит к нежелательным эффектам в других местах.
 Brad Larson♦19 янв. 2011 г., 21:44
@Joe - я также помню, что это где-то было задокументировано, но я не могу найти его в стандартных местах. Может быть, это было упомянуто в одном из переговоров WWDC. UIKit и AppKit, безусловно, имеют разные способы решения проблем, учитывая новый старт, предоставленный UIKit, и то, насколько сильно он основан на Core Animation.

чтобы ваша длительная обработка происходила внутри 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
 Mike Abdullah19 янв. 2011 г., 22:07
ПОЖАЛУЙСТА, НЕ НАДО.-drawRect: для рисованиятолько, ДелатьНЕ попытаться использовать это для чего-то другого.

есть простое решение для этого. Во время вашей длительной подпрограммы вы должны указать текущему циклу выполнения обрабатывать одну итерацию (или более) цикла выполнения в определенных точках вашей собственной обработки. например, когда вы хотите обновить дисплей. Любые представления с грязными областями обновления будут иметь свои методы 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 за пределы цикла.

Однако с архитектурной точки зрения я очень рекомендую рассмотреть некоторые из других подходов, упомянутых здесь. Я не вижу причин, по которым вы не можете структурировать свой рисунок, чтобы он происходил в фоновом потоке (алгоритм не изменился), и используете таймер или другой механизм, чтобы сигнализировать основному потоку об обновлении его пользовательского интерфейса на некоторой частоте до тех пор, пока рисунок не будет завершен. Я думаю, что большинство людей, которые участвовали в обсуждении, согласились бы, что это был бы "правильный" подход.

 hotpaw223 янв. 2011 г., 02:58
-1: я ненавижу голосовать за это, так как я обычно пытался это сделать. Но я просто дважды проверил, чтобы убедиться, и Apple DTS НЕ рекомендует вложенные циклы выполнения пользовательского интерфейса в iOS, особенно в NSDefaultRunLoopMode. Говорит, что могут возникнуть "дурацкие проблемы".
 Fattie22 янв. 2011 г., 10:35
Привет, Том - это ASTOUNDING. Никто другой не упомянул об этом трюке. Как ты узнал об этом? Я собираюсь провести тщательное расследование. На самом деле это именно то, что нужно делать в определенных обстоятельствах ... заставить drawRect работать «прямо сейчас», то есть вернуться в цикл выполнения, когда вы застряли в цикле выполнения. [NSDate date], удивительно ... Я должен проверить это.
 TomSwift23 янв. 2011 г., 03:20
@ hotpaw2 Я не рекомендую использовать это. Я отвечаю на вопрос. Я не думаю, что это заслуживает отрицательного ответа. Согласитесь, есть лучшие способы разработки приложения, руки вниз. Вопрос был о «возможно ли», и да, это так.
 Christopher Lloyd23 янв. 2011 г., 07:13
Запуск цикла выполнения один раз просто фиксирует текущую неявную CATransaction, инициированную путем изменения представления и нижележащего уровня, вы можете просто использовать явную CATransaction напрямую, чтобы выполнить то же самое, не задействуя цикл выполнения.
 Brad Larson♦22 янв. 2011 г., 23:28
@Joe - Том прав, это не трюк, это простое манипулирование циклом бега. Он должен работать во всех версиях ОС на Mac и iOS, учитывая фундаментальность NSRunLoop. Ни я, ни многие другие, которых я знаю, не проводили много времени с NSRunLoop, поэтому неудивительно, что есть элементы, о которых я не знаю. Apple рассказывает о циклах выполнения в сеансе 208 WWDC 2010 - «Сетевые приложения для iPhone OS», часть 2, если вам интересны другие способы их использования.

вы можете сделать:

[myView setNeedsDisplay];
[[myView layer] displayIfNeeded];

-setNeedsDisplay пометит представление как нуждающееся в перерисовке.-displayIfNeeded заставит фоновый слой представления перерисовать, но только если он был помечен как необходимый для отображения.

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

 Dave DeLong23 янв. 2011 г., 20:39
@ Джо, конечно, подход Брэда имеет больше смысла. Мой пост должен был строго ответить «как сделать вид нарисованным прямо сейчас».
 Dave DeLong24 янв. 2011 г., 04:11
@ hotpaw2 правильно. UIKit разработан, чтобы рисовать представления один раз за вращение цикла выполнения, не опускаясь в подпоследовательность (вращая ее самостоятельно). Однако это может измениться в будущем по усмотрению Apple, поэтому кодирование приложения с учетом деталей реализации никогда не является хорошей идеей. Это гораздо лучшая идея, чтобы делать вещи правильно. Дорогие вещи идут в фоновом потоке. Пользовательский интерфейс идет в основном потоке.
 hotpaw223 янв. 2011 г., 20:25
Delong: я попробовал это из долго выполняющейся вычислительной подпрограммы, которая блокировала основной поток пользовательского интерфейса, и это не вызывало вызов drawRect до тех пор, пока не закончилось длинное вычисление.
 hotpaw223 янв. 2011 г., 21:19
@Joe Blow: DDL мог бы сказать, что, поскольку UIKit не был разработан с учетом этого, этот метод может плохо работать при некоторых будущих обновлениях ОС, которые остаются полностью совместимыми с текущими документами API. Приложение, не аварийно завершающее работу на текущем тестовом устройстве, недостаточно.

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

Я согласен с другими авторами, что в идеальной ситуации всю тяжелую работу следует выполнять в фоновом потоке, однако бывают случаи, когда это просто невозможно, поскольку трудоемкая часть требует большого доступа к не потоко-безопасным методам, таким как те, которые предлагает 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 = ...: 
        });

        ...
    });
}

Конечно, это, по сути, то же самое, что и метод Брэда, но его ответ не совсем решает существующую проблему - запуск большого количества не поточно-безопасного кода при периодическом обновлении пользовательского интерфейса.

 Fattie20 сент. 2014 г., 17:40
захватывающий ....

Ваш ответ на вопрос