UIScrollView, kaydırma bitene kadar NSTimer'ı duraklatır


84

Bir UIScrollView(veya bunun türetilmiş bir sınıfı) kaydırılırken, kaydırma NSTimersbitene kadar çalışanların tümü duraklatılmış gibi görünüyor .

Bunu aşmanın bir yolu var mı? İş Parçacığı? Öncelikli bir ayar mı? Herhangi bir şey?



Yanıtlar:


202

Uygulanması kolay ve basit bir çözüm şunları yapmaktır:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

2
UITrackingRunLoopMode, farklı zamanlayıcı davranışları oluşturmak için kullanabileceğiniz moddur ve geneldir.
Tom Andersen

Bayıldım, GLKViewController ile de bir cazibe gibi çalışıyor. UIScrollView göründüğünde controller.paused'ı YES olarak ayarlayın ve tarif edildiği gibi kendi zamanlayıcınızı başlatın. Kaydırma görünümü kapatıldığında ters çevirin ve işte bu kadar! Güncelleme / işleme döngüm çok pahalı değil, bu yüzden yardımcı olabilir.
Jeroen Bouma

3
Harika! Zamanlayıcıyı geçersiz kıldığımda kaldırmam gerekir mi? Teşekkürler
Jacopo Penzo

Bu, kaydırma için çalışır ancak uygulama arka plana girdiğinde zamanlayıcıyı durdurur. Her ikisine de ulaşmak için herhangi bir çözüm var mı?
Pulkit Sharma

23

Swift 3 kullanan herkes için

timer = Timer.scheduledTimer(timeInterval: 0.1,
                            target: self,
                            selector: aSelector,
                            userInfo: nil,
                            repeats: true)


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)

7
Bunun timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)yerine ilk komut olarak çağırmak daha iyidir Timer.scheduleTimer(), çünkü çalışma scheduleTimer()döngüsüne zamanlayıcı ekler ve sonraki çağrı aynı çalışma döngüsüne ancak farklı modda başka bir eklemedir. Aynı işi iki kez yapmayın.
Accid Bright

8

Evet, Paul haklı, bu bir çalıştırma döngüsü sorunu. Özellikle NSRunLoop yöntemini kullanmanız gerekir:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

7

Bu hızlı versiyon.

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)

6

Kaydırma sırasında zamanlayıcıların ateşlenmesini istiyorsanız, başka bir iş parçacığı ve başka bir çalışma döngüsü çalıştırmanız gerekir; Zamanlayıcılar olay döngüsünün bir parçası olarak işlendiğinden, görünümünüzü kaydırmakla meşgulseniz, zamanlayıcılara asla ulaşamazsınız. Zamanlayıcıları diğer iş parçacıklarında çalıştırmanın performans / pil cezası bu durumu ele almaya değmeyebilir.


11
Başka bir ileti dizisi gerektirmez , Kashif'in daha yeni yanıtına bakın . Denedim ve ekstra iş parçacığı gerektirmeden çalışıyor.
progrmr

3
Serçelere top atmak. İş parçacığı yerine zamanlayıcıyı doğru çalışma döngüsü modunda programlayın.
uliwitness

2
Bence Kashif'in aşağıdaki cevabı, bu sorunu çözmek için yalnızca 1 satır kod eklemenizi gerektirdiği için en iyi cevaptır.
Damien Murphy.

3

Swift 4'ü kullanan herkes için:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)

2

tl; dr runloop kaydırma yapıyor, böylece daha fazla olayı işleyemez - zamanlayıcıyı manuel olarak ayarlamadığınız sürece runloop dokunma olaylarını işlerken de gerçekleşebilir. Veya alternatif bir çözüm deneyin ve GCD'yi kullanın


Herhangi bir iOS geliştiricisi için mutlaka okunması gerekir. Sonuçta RunLoop aracılığıyla birçok şey yürütülür.

Apple'ın belgelerinden türetilmiştir .

Çalıştırma Döngüsü nedir?

Bir çalışma döngüsü, adından çok daha farklıdır. İş parçacığınızın girdiği ve gelen olaylara yanıt olarak olay işleyicilerini çalıştırmak için kullandığı bir döngüdür.

Olayların teslimi nasıl kesintiye uğrar?

Zamanlayıcılar ve diğer periyodik olaylar, çalıştırma döngüsünü çalıştırdığınızda teslim edildiğinden, bu döngünün atlatılması bu olayların teslimini bozar. Bu davranışın tipik örneği, bir döngü girerek ve uygulamadan tekrar tekrar olaylar talep ederek bir fare izleme rutini uyguladığınızda ortaya çıkar. Kodunuz, uygulamanın bu olayları normal şekilde göndermesine izin vermek yerine olayları doğrudan yakaladığından, etkin zamanlayıcılar, fare izleme rutininizden çıkıp denetimi uygulamaya geri verene kadar tetiklenemez.

Çalıştırma döngüsü yürütmenin ortasındayken zamanlayıcı çalıştırılırsa ne olur?

Bu, biz farkına bile varmadan ÇOK ZAMAN olur. Demek istediğim, zamanlayıcıyı 10: 10: 10: 00'a ayarladık, ancak döngü 10: 10: 10: 05'e kadar süren bir olayı gerçekleştiriyor, bu nedenle zamanlayıcı 10: 10: 10: 06'da çalışıyor.

Benzer şekilde, bir zamanlayıcı çalıştırma döngüsü bir işleyici rutini yürütmenin ortasındayken tetiklenirse, zamanlayıcı işleyici rutinini çağırmak için çalıştırma döngüsü boyunca bir sonraki sefere kadar bekler. Çalışma döngüsü hiç çalışmıyorsa, zamanlayıcı asla çalışmaz.

Kaydırma veya runloop'u meşgul eden herhangi bir şey, zamanlayıcımın ateşleneceği her zaman değişir mi?

Zamanlayıcıları yalnızca bir kez veya tekrar tekrar olaylar oluşturacak şekilde yapılandırabilirsiniz. Tekrarlanan bir zamanlayıcı, gerçek ateşleme zamanına göre değil, planlanan atış süresine göre otomatik olarak yeniden planlar. Örneğin, bir zamanlayıcı belirli bir zamanda ve ondan sonra her 5 saniyede bir ateşlemek üzere programlandıysa, gerçek ateşleme süresi gecikse bile, programlanan ateşleme süresi her zaman orijinal 5 saniyelik zaman aralıklarına denk gelecektir. Ateşleme süresi, programlanmış ateşleme sürelerinden bir veya daha fazlasını kaçıracak kadar geciktirilirse, zamanlayıcı, kaçırılan süre boyunca yalnızca bir kez çalıştırılır. Kaçırılan süre için ateş ettikten sonra, zamanlayıcı bir sonraki planlanmış atış zamanı için yeniden planlanır.

RunLoops'un modunu nasıl değiştirebilirim?

Yapamazsın. İşletim sistemi sizin için sadece kendini değiştirir. örneğin, kullanıcı dokunduğunda, mod olarak değişir eventTracking. Kullanıcı tıklamaları bittiğinde, mod geri döner default. Bir şeyin belirli bir modda çalıştırılmasını istiyorsanız, bunun olmasını sağlamak size bağlıdır.


Çözüm:

Kullanıcı kaydırırken Döngü Çalıştırma Modu olur tracking. RunLoop, vites değiştirmek için tasarlanmıştır. Mod bir kez ayarlandığında eventTracking, dokunma olaylarına öncelik verir (sınırlı CPU çekirdeğimiz olduğunu unutmayın). Bu, işletim sistemi tasarımcıları tarafından yapılmış bir mimari tasarımdır .

Varsayılan olarak, zamanlayıcılar trackingmodda programlanmaz . Planlanacaklar:

Bir zamanlayıcı oluşturur ve bunu varsayılan modda mevcut çalışma döngüsünde programlar .

Aşağıdakiler scheduledTimerbunu yapar:

RunLoop.main.add(timer, forMode: .default)

Zamanlayıcınızın kaydırma sırasında çalışmasını istiyorsanız, şunlardan birini yapmanız gerekir:

let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
 selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode

RunLoop.main.add(timer, forMode: .tracking) // AND Do this

Veya sadece şunu yapın:

RunLoop.main.add(timer, forMode: .common)

Sonuçta senin parçacığı yukarıdaki yollarla birini yaparak değil dokunma olayları tarafından engellendi. şuna eşdeğerdir:

RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.

Alternatif çözüm:

Zamanlayıcınız için kodunuzu çalıştırma döngüsü yönetimi sorunlarından "korumanıza" yardımcı olacak GCD'yi kullanmayı düşünebilirsiniz.

Yinelenmeyenler için sadece şunu kullanın:

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    // your code here
}

Tekrarlayan zamanlayıcılar için şunu kullanın:

DispatchSourceTimer'ı nasıl kullanacağınızı görün


Daniel Jalkut ile yaptığım bir tartışmadan daha derine inersek:

Soru: GCD (arka plan iş parçacıkları), örneğin bir arka plan iş parçacığındaki asyncAfter RunLoop'un dışında nasıl çalıştırılır? Bundan anladığım kadarıyla her şey bir RunLoop içinde yürütülecektir.

Her iş parçacığının en fazla bir çalıştırma döngüsü vardır, ancak iş parçacığının "sahipliğini" yürütmeyi koordine etmek için bir neden yoksa sıfır olabilir.

İş parçacıkları, sürecinize işlevselliğini birden çok paralel yürütme bağlamına bölme yeteneği veren işletim sistemi düzeyinde bir olanaktır. Çalıştırma döngüleri, birden çok kod yolu tarafından verimli bir şekilde paylaşılabilmesi için tek bir iş parçacığını daha da bölmenize olanak tanıyan çerçeve düzeyinde bir olanaktır.

Tipik olarak, bir iş parçacığı üzerinde çalıştırılan bir şey gönderirseniz, dolaylı olarak bir [NSRunLoop currentRunLoop]tane oluşturacak bir şey çağırmadıkça muhtemelen bir runloop'a sahip olmayacaktır.

Özetle, modlar temelde girişler ve zamanlayıcılar için bir filtre mekanizmasıdır

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.