2012-04-12 3 views
1

Ich arbeite durch die 4. ed. von dem Hillegass/Preble Cocoa Buch und habe einen Haken gefunden, den ich in Core Animation nicht verstehen kann.CATransaction Begin/Commit in Schleife nicht geehrt wird, geschieht alles auf einmal

Insbesondere habe ich die folgende Schleife, die eine Liste von Bildern, angeblich einzeln, in eine Schicht-Hosting-Ansicht präsentiert. Das erwartete Ergebnis ist, dass ich jedes Bild einzeln fliegen sehen sollte, wie presentImage aufgerufen wird. Das tatsächliche Ergebnis ist, dass die Ansicht leer bleibt, bis die Schleife abgeschlossen ist und die Bilder dann alle gleichzeitig ausgeflogen werden. Während der Verzögerung sehe ich die mehreren Protokollnachrichten, die die mehreren Aufrufe an presentImage anzeigen. Wenn diese Protokollmeldungen aufhören zu zeigen, dass die Schleife beendet ist, werden alle Bilder auf einmal ausgelöst.

// loop to present each image in list of URLs 
// called at end of applicationDidFinishLaunching 
NSTimeInterval t0 = [NSDate timeIntervalSinceReferenceDate]; 
for(id url in urls) { 
    NSImage *img = [[NSImage alloc] initWithContentsOfURL:url]; 
    if(!img) 
     continue; 

    NSImage *thumbImg = [self thumbImageFromImage:img]; 

    [self presentImage:thumbImg]; 

    NSString *dt = [NSString stringWithFormat:@"%0.1fs", 
        [NSDate timeIntervalSinceReferenceDate]-t0]; 

    NSLog(@"Presented %@ at %@ sec",url,dt); 
} 

Die thumbImageFromImage Methode nur das tut, was man denkt (erzeugt ein kleineres Bild). Der Code für presentImage ist wie folgt:

- (void)presentImage:(NSImage *)image 
{ 
    CGRect superlayerBounds = view.layer.bounds; 
    NSPoint center = NSMakePoint(CGRectGetMidX(superlayerBounds), CGRectGetMidY(superlayerBounds)); 

    NSRect imageBounds = NSMakeRect(0, 0, image.size.width, image.size.height); 

    CGPoint randomPoint = CGPointMake(CGRectGetMaxX(superlayerBounds)*(double)random()/(double)RAND_MAX, CGRectGetMaxY(superlayerBounds)*(double)random()/(double)RAND_MAX); 

    CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 

    CABasicAnimation *posAnim = [CABasicAnimation animation]; 
    posAnim.fromValue = [NSValue valueWithPoint:center]; 
    posAnim.duration = 1.5; 
    posAnim.timingFunction = tf; 

    CABasicAnimation *bdsAnim = [CABasicAnimation animation]; 
    bdsAnim.fromValue = [NSValue valueWithRect:NSZeroRect]; 
    bdsAnim.duration = 1.5; 
    bdsAnim.timingFunction = tf; 

    CALayer *layer = [CALayer layer]; 
    layer.contents = image; 
    layer.actions = [NSDictionary dictionaryWithObjectsAndKeys:posAnim,@"position", bdsAnim, @"bounds", nil]; 

    [CATransaction begin]; 
    [view.layer addSublayer:layer]; 
    layer.position = randomPoint; 
    layer.bounds = NSRectToCGRect(imageBounds); 
    [CATransaction commit]; 
} 

Das beobachtete Ergebnis ist, als ob die [CATransaction begin] und [CATransaction commit] Anrufe die for Schleife anstatt den Anruf zu addSublayer innerhalb presentImage sind Belichtungsreihen. Tatsächlich beobachte ich das gleiche All-at-Once-Verhalten, wenn ich die [CATransaction begin] und [CATransaction commit] von innen presentImage entferne und stattdessen die for Schleife einreihe. Tatsächlich beobachte ich das gleiche All-at-Once-Verhalten, wenn ich Anrufe insgesamt zu [CATransaction begin] und [CATransaction commit] entferne.

Antwort

3

Gute Frage. Was Sie sehen, ist angesichts der Art, wie die Übung entworfen wird, zu erwarten. Der Grund dafür ist, dass die Animation (und das Zeichnen) von Layern auf dem Hauptthread erfolgt, der gerade damit beschäftigt ist, Bilder zu laden und Thumbnails zu erstellen. Erst nachdem alle Bilder geladen und die Thumbnails erstellt wurden, kann der Hauptthread tatsächlich die gewünschte Animation ausführen. CATransaction hat in diesem single-threaded Kontext keinen großen Einfluss.

Wir verbessern dies im nächsten Kapitel (34, Nebenläufigkeit), indem wir die Bilder in einem Hintergrundthread vorbereiten (mit NSOperationQueue). Dies gibt den Haupt-Thread frei, um die neuen Bilder anzuzeigen und die Animation auszuführen, wenn jedes Bild geladen und im Hintergrund verändert wird.

+0

Danke, Adam. Ich schätze es. Ich bin froh, dass das Verhalten zumindest von jemandem zu erwarten ist :-). Ich finde es ziemlich seltsam. Es scheint mir, dass jede Iteration der 'for (id url in urls)' - Schleife ein Bild laden und dann die Präsentation dieses Bildes animieren sollte, unabhängig davon, welcher Thread die Arbeit macht. Es ist mir weder aus dem Code noch aus der "CATransaction" -Dokumentation klar, dass die "CATransaction" -Aufrufe möglicherweise ignoriert werden. Ich werde diese Dokumentation jedoch bald sorgfältiger lesen. – JefferyRPrice

+0

Ich würde nicht sagen, dass sie ignoriert werden, aber mehr, dass sie keine Chance bekommen, wirksam zu werden, da der Hauptthread keine Chance hat, mit anderen Ereignissen (wie der Animation) umzugehen, bis danach - addImagesFromFolderURL: gibt zurück. In Cocoa (und anderen Frameworks) sollten Sie Folgendes beachten: Ermöglichen, dass der Hauptthread fortgesetzt wird, sodass die Benutzeroberfläche aktualisiert und auf Benutzereingaben reagiert werden kann. –

+0

Nochmals vielen Dank! Jetzt, wo ich das Concurrency-Kapitel beendet habe, muss ich sagen, dass ich immer noch verwirrt bin. Alle Bilder erscheinen immer noch auf einmal, wenn auch schneller (2.0s vs. 3.0s). Die Benutzeroberfläche wird alle gleichzeitig aktualisiert, als ob ich nur eine Animation festgeschrieben hätte. Mir scheint, dass ich mehrere Animationen gemacht habe, eine in jedem Durchgang durch die Schleife. Wie würde ich anfangen, die Bilder einzeln erscheinen zu lassen? (Ich habe versucht, 'addImagesFromFolderURL' aus' applicationDidFinishLaunch' zu entfernen und stattdessen das Ziel einer Schaltfläche zu machen. Immer noch gleiche Ergebnisse.) – JefferyRPrice

0

Nun, nachdem ich diesen Fehler für ein paar Wochen aus dem Häuschen gelassen habe, scheint hier etwas Sinnvolles zu sein und scheint (etwas) offensichtlich für mich zu sein, also hoffe ich bin nicht zu weit weg.

Aus dem Code in meiner ursprünglichen Frage oben ist der Hauptthread verantwortlich für alle UI-Updates, einschließlich der Animationen, aber der Hauptthread ist effektiv von der Aktualisierung der Benutzeroberfläche blockiert, bis presentImage getan wird. Dies gilt unabhängig davon, wie wir presentImage in Threads aufteilen (wie in Ch 34).

Zum Beispiel, wenn ich presentImage genannt als das Ziel einer Schaltfläche anstelle von Looping auf es wie im obigen Code, beginnt jede Animation, sobald ich auf die Schaltfläche klicken und verläuft wie ich erwarten würde, unabhängig davon, wie schnell (oder nicht) Ich klicke auf den Knopf. Dies führt zu mehreren simultanen Animationen, wie ich es erwarten würde (und wie ich fälschlicherweise vom Original erwartet hatte).

Ich bin mir nicht sicher, dass ich die offiziellen technischen Details genau erfasst habe, aber das genügt mir zumindest als Antwort.

Verwandte Themen