Swift - NSTimer to loop through array with delay - ios

I have an array, and I wish to set the text of a UILabel to an element of the array, and then after a second set the text as the next element of the array. Once the end of the array has been reached, it needs to then return to the start. I have tried completing this with a for loop that runs through the array, with a delay function inside the for loop, but this does not slow down the operation of the for loop itself. I have also tried using an NSTimer,
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
func update() {
var i = Int()
UIView.animateWithDuration(0.2, delay: 0.3, options: nil, animations: { () -> Void in
if i == connectionName.count - 1 {
i = 0
println(connectionName[i])
} else {
println(connectionName[i])
}
}, completion: { (finished: Bool) -> Void in
i = i+1
})
}
But I just get an error
2015-01-08 15:06:10.511 Tinder[585:10642] -[Tinder.TinderViewController update]: unrecognized selector sent to instance 0x7fae99ead3f0
2015-01-08 15:06:10.612 Tinder[585:10642] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Tinder.TinderViewController update]: unrecognized selector sent to instance 0x7fae99ead3f0'
Is this because the function is defined within the view did load method?

You asked
Is this because the function is defined within the view did load method?
That is indeed the problem. NSTimer uses Objective-C messaging to call the timer function, and
nested functions in Swift are not exposed as Objective-C methods.
You have to define update() as a top-level function within the view controller class.
Unfortunately, the compiler cannot warn you, because it does not "know" that the
string "update" in the selector corresponds to the update() function. (Unlike the Objective-C #selector(), Swift uses simple strings as selectors, and the compiler
cannot verify its existence).
If you explicitly annotate a nested function with #objc then you will get a
compiler error.

Related

EXC_BAD_ACCESS in swift_isUniquelyReferenced_nonNull_native when accessing swift array

The Problem
While writing unit tests and mocking away the NSTimer I am seeing an
Exception: EXC_BAD_ACCESS (code=1, address=0x8)
inside
swift_isUniquelyReferenced_nonNull_native
The situation appear when accessing the array invalidateInvocations (inside func invalidate()) here.
class TimerMock: Timer {
/// Timer callback type
typealias TimerCallback = ((Timer) -> Void)
/// The latest used timer mock accessible to control
static var currentTimer: TimerMock!
/// The block to be invoked on a firing
private var block: TimerCallback!
/// Invalidation invocations (will contain the fireInvocation indices)
var invalidateInvocations: [Int] = []
/// Fire invocation count
var fireInvocations: Int = 0
/// Main function to control a timer fire
override open func fire() {
block(self)
fireInvocations += 1
}
/// Hook into invalidation
override open func invalidate() {
invalidateInvocations.append(fireInvocations)
}
/// Hook into the timer configuration
override open class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping TimerCallback) -> Timer {
// return timer mock
TimerMock.currentTimer = TimerMock()
TimerMock.currentTimer.block = block
return TimerMock.currentTimer
}
}
The interesting thing is if I change invalidateInvocations to a regular Int it can be accessed without any crashes.
Because accessing this variable leads to a EXC_BAD_ACCESS I would assume the array was already deallocated, but I would not see how this could happen.
The Demo
You can see a full running and crashing example in this repository (branch demo/crash)
https://github.com/nomad5modules/ArcProgressViewIOS/tree/demo/crash
Simply execute the unit tests and see it crashing.
The Question
What is happening here? I have observed a crash inside swift_isUniquelyReferenced_nonNull_native already in other projects too and I would love to completely understand the reason for this failure! So how is the process to find out what wrong here? And how to fix it?
Standalone reproduction project
https://drive.google.com/file/d/1fMGhgpmBRG6hzpaiTM9lO_zCZwNhwIpx/view?usp=sharing
The crash is due to not initialised member (it is NSObject not regular swift class, so explicit init() would be needed, but as this is Timer it has semi-abstract designated initializer, so override is not allowed).
The solution is to set up missed ivar explicitly, as below.
Tested & works on your Test project with Xcode 11.4.
override open class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping TimerCallback) -> Timer {
// return timer mock
TimerMock.currentTimer = TimerMock()
TimerMock.currentTimer.invalidateInvocations = [Int]() // << fix !!
TimerMock.currentTimer.block = block
return TimerMock.currentTimer
}

How to properly fire/call a "selector" in Swift?

Question Summary:
If you have a Swift class that takes a selector as an argument in its initializer, how do you manually "fire/call" that selector?
Full Question:
Consider the following attempt at making a custom timer in Swift:
let TIME_INTERVAL = 0.1
class ValueAnimator : NSObject {
private var timer = Timer()
private let maxRep: Int
private var currentRepIndex: Int = 0
private var selector: Selector
init(durationInSeconds: Int, selector: Selector) {
print("VALUEANIMATOR INIT")
self.maxRep = Int(Double(durationInSeconds) / TIME_INTERVAL)
self.selector = selector
}
func start() {
timer = Timer.scheduledTimer(timeInterval: TIME_INTERVAL, target: self, selector: (#selector(timerCallback)), userInfo: nil, repeats: true)
}
#objc func timerCallback() {
currentRepIndex += 1
perform(selector) // <-------- this line causes crash, "unrecognized selector sent to instance 0x600001740030"
print ("VA timer called!, rep: \(currentRepIndex)")
if currentRepIndex == maxRep {
timer.invalidate()
print("VA timer invalidated")
}
}
}
The usage of this "ValueAnimator" would be similar to a normal Timer/NSTimer, in that you pass a "selector" as an argument and that selector is called each time the ValueAnimator fires:
[In Parent Class]:
// { ...
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp))
valueAnimatorTest.start()
}
#objc func temp() {
print("temp VA callback works!") // this doesn't happen :(
}
I'm trying to implement the same thing and as I understand, the line:
perform(selector)
should fire the selector in the parent class, but instead I get the error: "unrecognized selector sent to instance 0x600001740030"
I'm in a bit over my head here. I have tried googling the error, but everyone seems to be talking about how to use a selector from the parent-side (how to use Timer.scheduledTimer(), etc.) but I already know how to do that successfully.
I've also tried various tweaks to the code (changing public/private, scope of variables, and different forms of the performSelector() function)... but can't figure out the proper way to make the selector fire... or the unrelated mistake I've made if there is one.
Thanks for any help.
By calling perform(selector) it's like you're calling self.perform(selector) (self is implied), and by doing so the current instance of the ValueAnimator class is the object that actually performs the selector. When that happens, it tries to call a method called temp() of the ValueAnimator class, but as it doesn't exist the app is crashing.
You can verify that if you add a temp() method in the ValueAnimator:
#objc func temp() {
print("Wrong method!!!")
}
If you run now you'll have no crash and the "Wrong Selector!!!" message will appear on the console.
The solution to your problem is to pass the object that should run the selector method along with the selector to the initialisation of the ValueAnimator object.
In the ValueAnimator class declare the following property:
private var target: AnyObject
Update the init method so it can get the target as an argument:
init(durationInSeconds: Int, selector: Selector, target: AnyObject) {
...
self.target = target
}
Also update the timerCallback():
#objc func timerCallback() {
...
_ = target.perform(selector)
...
}
Finally, when you initialise a ValueAnimator instance pass the object that the selector belongs to as well:
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp), target: self)
Run again and the proper temp() method will be executed this time.
I hope it helps.
You are calling perform on the wrong object: its an instance method of NSObject, so you are trying to call perform on ValueAnimator and ValueAnimator does not respond to "temp". You must pass in both the object and the selector you want to perform, then you call perform on that object with the selector. Notice that this is exactly what Timer does: you have to pass in self as the object and the timer call the selector you specify on self.

How to use perform(aSelector: , with: , afterDelay: , inModes: ) to pause a CABasicAnimation after a delay

I am using perform(aSelector: , with: , afterDelay: , inModes: ) to pause an animation after a specified delay. However, I keep getting an Unrecognized Selector Error. I am not sure what could possibly be causing this.
Sample Code (Updated):
class ExpandingSelectedLayer: CALayer, CAAnimationDelegate
{
let expandingAnim = CABasicAnimation(keyPath: #keyPath(CALayer.bounds))
expandingAnim.perform(#selector(expandingAnim.pauseAnim), with: nil, afterDelay: 2.0, inModes: [RunLoopMode.commonModes])
}
extension CABasicAnimation
{
#objc func pauseAnim()
{
print("called pause Anim")
self.speed = 0
}
}
First: the anArgument argument in perform(_:with:afterDelay:) is the argument to pass to the method. The selector in your question doesn't take any arguments but your perform call is passing it an argument. Since pauseAnim doesn't take any arguments you would just pass nil for the anArgument argument.
Second: It's not clear from your question where the pauseAnim method is defined. Unless it's a method on CABasicAnimation (or somewhere in its class hierarchy) you won't be able to call that method on an instance of CABasicAnimation. If this method is defined on a view controller or other object you would use that as the receiver instead (possibly self).

Function with Completion in Timer Selector

I am declaring a function as such:
#objc func fetchDatabase(completion: ((Bool) -> Void)? = nil)
I'm allowing the completion to be nil so I can either call it as fetchDatabase() or as
fetchDatabase(completion: { (result) in
// Stuff in here
})
However, I am also trying to use this function in a #selector for a Timer. I am creating this timer using the following line:
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fetchDatabase), userInfo: nil, repeats: true)
Without the completion, this runs fine. However, with the completion added, I get an EXC_BAD_ACCESS error whenever the timer is run. Some help with correctly constructing this selector would be greatly appreciated, if this is in fact the error.
The selector passed to the timer only allows one of two possible signatures
someFunction()
someFunction(someLabel someParameter: Timer)
You can't pass your fetchDatabase(completion:) function because it doesn't match either of the two legal selector signatures.
You need to pass a valid selector which in turn calls your fetchDatabase(completion:) function. For example:
#objc timerHandler() {
fetchDatabase(completion: { (result) in
// Stuff in here
})
}
use #selector(timerHandler) with your timer.
Default arguments get applied at the calling site, so you'll need to generate two separate methods (one of which calls the other):
func fetchDatabase() { fetchDatabase(callback:nil) }
func fetchDatabase(callback:()->()) {
...
}
Now your scheduledTimer call should work fine.

Does the target of #selector() depend on the context of `addObserver` (class vs object)?

The full test case below is supposed to demonstrate: a selector, even though it is specified identically in two places, is performed differently: either it is performed on the class, or on the object. (I understand that a static method and an object method can share the same name, but there is only one below.) Whether the receiver is class or object seems to depend on where the “same” selector is made known to NSNotificationCenter, either in class context or in method context:
a static method has the call to addObserver, or
an object method has the call addObserver
while the calls are otherwise identical.
If the identical call occurs in a static method, then when the notification is processed later, the system tries to invoke the selector on the class, not the object. The class does not have it. The code compiles fine with the new (in 2.2) syntax. Is this result to be expected?
import XCTest
import class Foundation.NSNotificationCenter // for emphasis
class SelectorTests: XCTestCase {
static let NotificationName = "OneTwoThreeNotification"
override func setUp() {
super.setUp()
}
override func tearDown() {
NSNotificationCenter.defaultCenter().removeObserver(self)
super.tearDown()
}
func addObserverForTestNormal() { // <- HERE
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(SelectorTests.myMethod(_:)), // <- HERE
name: SelectorTests.NotificationName,
object: nil)
}
func testNormal() {
self.addObserverForTestNormal()
NSNotificationCenter.defaultCenter().postNotificationName(
SelectorTests.NotificationName,
object: self)
}
static func addObserverForTestStatic() { // <- HERE
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(SelectorTests.myMethod(_:)), // <- HERE
name: SelectorTests.NotificationName,
object: nil)
}
func testStatic() {
SelectorTests.addObserverForTestStatic()
NSNotificationCenter.defaultCenter().postNotificationName(
SelectorTests.NotificationName,
object: self)
}
func myMethod(x : Int) {
XCTAssert(true)
}
}
One test succeeds, the other fails. The gist of the stack trace and message is
"+[KuckuckTests.SelectorTests myMethod:]: unrecognized selector sent to class
Is this schism, i.e. class or object “inferred” from addObserver-context, so obvious to old Objective-C hands that it isn't worth mentioning with #selector? In this case, could you point out some documentation?
Edit: just noticed that self in the static function's invocation
of addObserver is perhaps referring to the class, not to some object. That makes the effect somewhat plausible, and suggests that programmers should know what overloaded names stand for…
Nothing about a #selector expression has any connection to the use site of that selector. A selector names a message, and says nothing about the receiver of that message. You can use a #selector expression to create a Selector value for a method on one object, then pass that Selector value to an API (like NSNotificationCenter.addObserver or UIControl.sendAction or NSTimer.init) that'll result in sending a message with that selector to some completely different object.
This loose binding is an intentional part of the dynamic nature of the Objective-C runtime Cocoa uses for passing these messages (regardless of whether the functions referenced by your selectors are build in ObjC or Swift). The #selector expression, and the Swift function-reference syntax it depends on, give you a way to "sorta" strongly type your use of selectors, but only on one end — they let you verify that the Selector value you're constructing refers to a specific method. (But once you have a Selector value, how it gets used is out of Swift's control.)
Your error message (emphasis added):
unrecognized selector sent to class
...indicates that the failure is because the message is being sent to the SelectorTests class object (aka the metaclass object). That is, by scheduling a notification to be sent to self in a static method, you're asking for a call to class func myMethod, not to func myMethod.
The self keyword always refers to the instance responsible for the code that's executing: inside an instance method, self refers to the current instance. Inside a class method, self refers to the (only instance of) the class object.

Resources