2013-02-19 7 views
6

ich CADisplayLink in meinem iPhone app bin mit. HierCADisplayLink laufen niedrigere Framerate auf iOS5.1

ist der relevante Code:

SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onTick)]; 
SMPTELink.frameInterval = 2;//30fps 60/n = fps 
[SMPTELink addToRunLoop:[NSRunLoop mainRunLoop] 
        forMode:NSDefaultRunLoopMode]; 

onTick somit jeden Rahmen von 30FPS (1/30tel einer Sekunde) genannt wird. Das funktioniert super auf iOS6 + - macht genau das, was ich brauche. Wenn ich jedoch meine App auf einem iPhone 4s mit iOS5.1 ausgeführt habe, lief die onTick-Methode etwas langsamer als mit dem iOS6-Gegenstück. Fast wie es war, es zu laufen 29FPS. Nach ein wenig, es war nicht synchron mit dem iOS6 iPhone 5.

Der Code in der onTick-Methode ist nicht zeitaufwendig (das war einer meiner Gedanken ...), und es ist nicht das iPhone, weil die App läuft gut auf einem iPhone 4s mit iOS6.

Hat CADisplayLink Funktion unterschiedlich in iOS5.1? Irgendwelche möglichen Workarounds/Lösungen?

Antwort

16

Ich kann nicht mit den iOS 5.xv 6.x Unterschieden sprechen, aber wenn ich CADisplayLink verwende, programmiere ich niemals Code wie "move x pixels/points" bei jeder Iteration, sondern schaue auf die timestamp (oder genauer gesagt, das Delta zwischen meinem ersten timestamp und dem aktuellen timestamp) und berechnen die Position auf der Grundlage, wie viel Zeit vergangen ist, nicht, wie viele Frames bestanden haben. Auf diese Weise beeinflussen Bildraten nicht die Geschwindigkeit der Bewegung, sondern nur die Glätte. (Und der Unterschied zwischen 30 und 29 wahrscheinlich nicht zu unterscheiden sein.)

Zitat aus den CADisplayLink Class Reference:

Sobald der Display-Link mit einer Laufschleife zugeordnet ist, wird der Wähler auf dem Ziel aufgerufen, wenn Der Inhalt des Bildschirms muss aktualisiert werden. Das Ziel kann lesen Sie die timestamp Eigenschaft Display Link, um die Zeit abzurufen, die der vorherige Rahmen angezeigt wurde. Beispielsweise kann eine Anwendung, die Filme anzeigt, den Zeitstempel verwenden, um zu berechnen, welcher Videoframe als nächstes angezeigt wird. Eine Anwendung, die eigene Animationen ausführt, verwendet möglicherweise den Zeitstempel, um zu bestimmen, wo und wie angezeigte Objekte im nächsten Frame angezeigt werden. Die Eigenschaft duration bietet die Zeit zwischen Frames. Sie können diesen Wert in Ihrer Anwendung verwenden, um die Bildrate der Anzeige, die ungefähren Zeit, dass der nächste Frame angezeigt wird, zu berechnen, und die Zeichnungsverhalten einzustellen, so dass der nächste Rahmen in der Zeit vorbereitet wird, angezeigt werden.


Als Zufalls Beispiel here Ich bin ein UIBezierPath mit der Anzahl der Sekunden Animieren, der als Parameter vergangen sind.

Oder alternativ, wenn Sie mit einer Folge von UIImage Frames zu tun haben, könnten Sie die Rahmennummer wie folgt berechnen:

@property (nonatomic) CFTimeInterval firstTimestamp; 

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    if (!self.firstTimestamp) 
     self.firstTimestamp = displayLink.timestamp; 

    CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); 

    NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; 

    // now do whatever you want with this frame number 
} 

Oder, noch besser, einen Rahmen zu vermeiden, riskieren zu verlieren, gehen Sie voran und lassen Sie dies bei 60 fps laufen und bestimmen Sie nur, ob der Rahmen aktualisiert werden muss, und auf diese Weise reduzieren Sie das Risiko, einen Rahmen fallen zu lassen.

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    if (!self.firstTimestamp) 
     self.firstTimestamp = displayLink.timestamp; 

    CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); 

    NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; 

    if (frameNumber != self.lastFrame) 
    { 
     // do whatever you want with this frame number 

     ... 

     // now update the "lastFrame" number property 

     self.lastFrame = frameNumber; 
    } 
} 

Aber häufig sind Rahmennummern nicht benötigt.Um zum Beispiel eine UIView in einem Kreis zu bewegen, könnten Sie so etwas wie:

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    if (!self.firstTimestamp) 
     self.firstTimestamp = displayLink.timestamp; 

    CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); 

    self.animatedView.center = [self centerAtElapsed:elapsed]; 
} 

- (CGPoint)centerAtElapsed:(CFTimeInterval)elapsed 
{ 
    CGFloat radius = self.view.bounds.size.width/2.0; 

    return CGPointMake(radius + sin(elapsed) * radius, 
         radius + cos(elapsed) * radius); 
} 

By the way, wenn Sie Instrumente verwenden Framerate zu messen, kann es langsamer erscheinen, als es auf wirklich sein wird, Ein Gerät. Um die genauen Frameraten zu ermitteln, sollten Sie Matt's Kommentar programmatisch auf einem tatsächlichen Gerät mit einem Release-Build messen.

+0

Tolle Idee! Also, auf der onTick-Methode, ich Timestamp, dann auf der nächsten, ich Timestamp wieder - dann vergleichen, um zu sehen, ob ich 0,03333 Sekunden Unterschied? Was wenn ich es nicht tue? Können Sie Ihr Konzept näher erläutern? – objectiveccoder001

+0

@ objectcoder001 Wenn es einen Grund gibt, warum du unbedingt 30 fps brauchst, dann könntest du so etwas machen. Sehen Sie sich mein Codebeispiel an, das die Bildnummer basierend auf der verstrichenen Zeit bestimmen kann. Bei vielen Animationen müssen Sie das nicht tun, sondern Sie können einfach die neue Position basierend auf der verstrichenen Zeit berechnen, und es gibt keinen Grund, warum sie auf 30 fps beschränkt ist (vs 29 vs 59 vs etc.). – Rob

+0

FANTASTISCH! Ich danke dir sehr. Was sind kMaxNumberOfFrames? – objectiveccoder001

3

Robs Antwort ist genau richtig. Sie müssen sich keine Sorgen über die Bildrate von CADisplayLink machen. Tatsächlich darf man nicht erwarten, dass der Timer mit Regelmäßigkeit abfeuert. Ihre Aufgabe besteht darin, die gewünschte Animation entsprechend der gewünschten Zeitskala aufzuteilen und den Rahmen, den Sie tatsächlich erhalten, bei jedem Zünden des Timers zu zeichnen, indem Sie die akkumulierten Zeitstempel addieren. Hier

ist Beispielcode aus meinem Buch:

if (self->_timestamp < 0.01) { // pick up and store first timestamp 
    self->_timestamp = sender.timestamp; 
    self->_frame = 0.0; 
} else { // calculate frame 
    self->_frame = sender.timestamp - self->_timestamp; 
} 
sender.paused = YES; // defend against frame loss 

[_tran setValue:@(self->_frame) forKey:@"inputTime"]; 
CGImageRef moi3 = [self->_con createCGImage:_tran.outputImage 
            fromRect:_moiextent]; 
self->_iv.image = [UIImage imageWithCGImage:moi3]; 
CGImageRelease(moi3); 

if (self->_frame > 1.0) { 
    [sender invalidate]; 
    self->_frame = 0.0; 
    self->_timestamp = 0.0; 
} 
sender.paused = NO; 

In diesem Code, der _frame Wert verläuft zwischen 0 (wir beginnen gerade die Animation) und 1 (wir haben die Animation beendet), und in der Mitte Ich mache einfach das, was diese spezielle Situation erfordert, um diesen Rahmen zu zeichnen. Um die Animation länger oder kürzer zu machen, multiplizieren Sie einfach einen Skalierungsfaktor, wenn Sie die _frame -Variante einstellen.

Beachten Sie auch, dass Sie nie im Simulator testen müssen, da die Ergebnisse völlig bedeutungslos sind. Nur das Gerät führt CADisplayLink ordnungsgemäß aus.

(Beispiel kommt von hier: http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions)

Verwandte Themen