How to pass a function as parameter avoiding retain cycles? - swift

I have a view controller where I am trying to call Timer.scheduledTimer(withTimeInterval:repeats:block) by passing a function as block parameter, instead of creating a block on the fly. I have this view controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Timer.scheduledTimer(withTimeInterval: 5,
repeats: true,
block: onTimer)
}
deinit {
print("deinit \(self)")
}
func onTimer(_ timer: Timer) {
print("Timer did fire")
}
}
The call is retaining the view controller, so the controller is never deallocated.
I know I can make it work as I want by replacing the call with:
Timer.scheduledTimer(withTimeInterval: 5,
repeats: true) { [weak self] timer in
self?.onTimer(timer)
}
But I'd like to know if there is a way to send the onTimer method directly and avoid the retain cycle.
Thanks.

You should call the invalidate() method:
This method is the only way to remove a timer from an RunLoop object.
The RunLoop object removes its strong reference to the timer, either
just before the invalidate() method returns or at some later point.
If it was configured with target and user info objects, the receiver
removes its strong references to those objects as well.
Somewhere in your code, you should implement:
timer.invalidate()
Hope this helped.

Related

Swift calling a ViewController function from the AppDelegate [duplicate]

I am building an iOS app using the new language Swift. Now it is an HTML5 app, that displays HTML content using the UIWebView. The app has local notifications, and what i want to do is trigger a specific javascript method in the UIWebView when the app enters foreground by clicking (touching) the local notification.
I have had a look at this question, but it does not seem to solve my problem. I have also come across this question which tells me about using UIApplicationState, which is good as that would help me know the the app enters foreground from a notification. But when the app resumes and how do i invoke a method in the viewController of the view that gets displayed when the app resumes?
What i would like to do is get an instance of my ViewController and set a property in it to true. Something as follows
class FirstViewController: UIViewController,UIWebViewDelegate {
var execute:Bool = false;
#IBOutlet var tasksView: UIWebView!
}
And in my AppDelegate i have the method
func applicationWillEnterForeground(application: UIApplication!) {
let viewController = self.window!.rootViewController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("FirstView") as FirstViewController
setViewController.execute = true;
}
so what i would like to do is when the app enters foreground again, i want to look at the execute variable and run the method as follows,
if execute{
tasksView.stringByEvaluatingJavaScriptFromString("document.getElementById('sample').click()");
}
Where should i put the code for the logic to trigger the javascript from the webview? would it be on viewDidLoad method, or one of the webView delegate methods? i have tried to put that code in the viewDidLoad method but the value of the boolean execute is set to its initial value and not the value set in the delegate when the app enters foreground.
If I want a view controller to be notified when the app is brought back to the foreground, I might just register for the UIApplication.willEnterForegroundNotification notification (bypassing the app delegate method entirely):
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Note, in the completion closure, I include [unowned self] to avoid strong reference cycle that prevents the view controller from being deallocated if you happen to reference self inside the block (which you presumably will need to do if you're going to be updating a class variable or do practically anything interesting).
Also note that I remove the observer even though a casual reading of the removeObserver documentation might lead one to conclude is unnecessary:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
But, when using this block-based rendition, you really do need to remove the notification center observer. As the documentation for addObserver(forName:object:queue:using:) says:
To unregister observations, you pass the object returned by this method to removeObserver(_:). You must invoke removeObserver(_:) or removeObserver(_:name:object:) before any object specified by addObserver(forName:object:queue:using:) is deallocated.
I like to use the Publisher initializer of NotificationCenter. Using that you can subscribe to any NSNotification using Combine.
import UIKit
import Combine
class MyFunkyViewController: UIViewController {
/// The cancel bag containing all the subscriptions.
private var cancelBag: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
addSubscribers()
}
/// Adds all the subscribers.
private func addSubscribers() {
NotificationCenter
.Publisher(center: .default,
name: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.doSomething()
}
.store(in: &cancelBag)
}
/// Called when entering foreground.
private func doSomething() {
print("Hello foreground!")
}
}
Add Below Code in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector:#selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToForeground() {
print("App moved to foreground!")
}
In Swift 3, it replaces and generates the following.
override func viewDidLoad() {
super.viewDidLoad()
foregroundNotification = NotificationCenter.default.addObserver(forName:
NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: OperationQueue.main) {
[unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}

Swift Runtime exception: unrecognized selector

In my ViewController class, I have a function:
func updateTimes() {
// (code)
}
I create a timer:
class ViewController: NSViewController {
var timer = Timer.scheduledTimer(timeInterval: 5,
target: self,
selector:
#selector(ViewController.updateTimes),
userInfo: nil,
repeats: true)
The compiler is happy with this. At runtime, when the timer fires, I get an exception:
unrecognized selector sent to instance 0x6000000428b0
Am I doing something obviously wrong?
As I wrote as a comment on NaGib ToroNgo's answer, he has given us a nice suggestion.
The selector may not be sent to the instance of ViewController.
I guess the ViewController would be taking this form:
class ViewController: UIViewController {
var timer = Timer.scheduledTimer(timeInterval: 5,
target: self,
selector: #selector(ViewController.updateTimes),
userInfo: nil,
repeats: true)
//...(Other property declarations or method definitions)...
func updateTimes() {
// (code)
}
}
The variable timer is declared as an instance property, and self is used in an initial value of timer. (In some old versions of Swift, this sort of usage caused error, so I was thinking that this line exists in any of the methods.)
In the current version of Swift (tested with Swift 3.1/Xcode 8.3.3), the code above does not cause error, but self is interpreted as a method reference of self() method declared in NSObjectProtocol. So, Selector("updateTimes") is sent to the closure representing the method reference (curried function), not to the instance of the ViewController.
The closure does not have a method named updateTimes, which caused the exception:
unrecognized selector sent to instance
Move the initial value code into some instance context, and then self represents the instance of the ViewController:
class ViewController: UIViewController {
var timer: Timer? //<- Keep `timer` as an instance property, but move the initial value code into `viewDidLoad()`.
//...(Other property declarations or method definitions)...
override func viewDidLoad() {
super.viewDidLoad()
//Do initialize the timer in the instance context.
timer = Timer.scheduledTimer(timeInterval: 5,
target: self,
selector: #selector(self.updateTimes),
userInfo: nil,
repeats: true)
//...
}
//In Swift 3, `#objc` is not needed, just for a preparation for Swift 4
#objc func updateTimes() {
// (code)
}
}
I believe this does not cause unrecognized selector exception.
The code you have provided seems perfect. I think the problem is, somehow your view controller is getting released or having dangling pointer.
Its time to say good bye to selectors!!! use the below code
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { (timer) in
// check self for nil before using
}

Cancel Swift Timer From Another Function

I am using a Swift Timer library found here. It allows you to run a timer and then stop it using the following syntax:
Timer.every(5.seconds) { (timer: Timer) in
// do something
if finished {
timer.invalidate()
}
}
I am trying to have the timer start and then have the option of the timer being cancelled from another function but I can't figure out how to reference the timer that is counting down from another function. I have tried doing something like this but it throws the following error:
Static member every cannot be used on instance of type Timer
var countTimer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
initNotificationSetupCheck()
countTimer.every(5.seconds) { //error here
}
func stopTimer() {
countTimer.invalidate()
}
What if you were to use a mixture of both, but instead of creating an instance of Timer, assign the timer from inside the closure to your member variable like:
var countTimer: Timer
Timer.each(5.seconds) { timer in
countTimer = timer
//rest of code
}
Then you can invalidate the timer outside of the closure if needed.
You can go like this
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self,
selector: #selector(self.eventForTimer), userInfo: nil, repeats: true)
for cancelling timer
func cancelTimer() {
self.timer.invalidate()
}

Do I need capture self using thread class?

I have this code:
myThreadTemp = Thread(target: self, selector: #selector(threadMain), object: nil)
#objc func threadMain(data: AnyObject) {
let runloop = RunLoop.current
runloop.add(NSMachPort(), forMode: RunLoopMode.defaultRunLoopMode)
while !Thread.current.isCancelled{
//foreground
DispatchQueue.main.async {[weak self] in
self?.somemethod()
self?.somevar = 1
print("tick")
}
if Thread.current.isCancelled {
}
Thread.sleep(forTimeInterval: 1.0)
}
runloop.run(mode: RunLoopMode.defaultRunLoopMode, before: NSDate.distantFuture)
}
or I can just do this:
DispatchQueue.main.async {
self.somemethod()
self.somevar = 1
print("tick")
}
I saw this:
Shall we always use [unowned self] inside closure in Swift
But was if #objc func is used?
The 1st example looks to spin the runloop indefinitely, waiting 1s between ticks, whereas the 2nd example will execute once, on the very next run loop iteration. There is no memory management issue in terms of capturing self in the 2nd case, indeed because it is only executed once and the block is released after it (breaking the momentary retain loop that does exist between self and the block).
Assuming you are trying to tick every 1s (as I am guessing based on your questions), there is a better way to do what you are trying to do, using a timer:
// Must be executed on main thread, or other NSRunLoop enabled thread,
// or the below code will silently do nothing.
self.timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
self?.someMethod()
self?.someVar = 1
print("tick")
}
// Somewhere in the future, to stop the timer:
// self.timer.invalidate()
As you can see in the above example, with the timer case you might indeed want to refer to self with either an unowned or weak reference (as the timer block will otherwise make a strong reference to self, and self to the timer). The block should be released on invalidating the timer too, so even in this case the weak reference is not 100% necessary I guess.

Confusion Regarding How to Use Capture Lists to Avoid a Reference Cycle

My custom UIViewController subclass has a stored closure property. The closure signature is defined as to take a single argument of the same type of the class:
class MyViewController {
var completionHandler : ((MyViewController)->(Void))?
// ...
}
...the idea being, the object passing itself back as an argument of the handler, a bit like the UIAlertAction initializer.
In addition, and for convenience, I have a factory(-ish) class method:
class func presentInstance(withCompletionHandler handler:((MyViewController)->(Void)))
{
// ...
}
...that performs the following actions:
Creates an instance of the view controller,
Assigns the completion handler to the property,
Presents it modally from whatever happens to be the top/root view controller at the time of the call.
My view controller is definitely leaking: I set up a breakpoint on deinit() but execution never hits it, even way after I'm done with my view controller and it is dismissed.
I am not sure of how or where I should specify a capture list in order to avoid the cycle. Every example I have come across seems to place it where the closure body is defined, but I can't get my code to compile.
Where I declare the closure property? (how?)
var completionHandler : ((MyViewController)->(Void))?
// If so, where does it go?
Where I declare the closure parameter?
class func presentInstance(withCompletionHandler handler:((MyViewController)->(Void)))
{
// Again, where could it go?
Where I call the above function and pass the closure body?
MyViewController.presentInstance(withCompletionHandler:{
[unowned viewController] viewController in
// ...
})
// ^ Use of unresolved identifier viewController
// ^ Definition conflicts with previous value
Where I actually call the closure, towards self?
None of these will compile:
self.completionHandler?(unowned self)
self.completionHandler?([unowned self] self)
self.completionHandler?([unowned self], self)
Well, it turns out my view controller was being retained by a block, but not the one I was thinking:
class MyViewController
{
deinit(){
print("Deinit...")
}
// ...
#IBAction func cancel(sender:UIButton)
{
completionHandler(self)
// View controller is dismissed, AND
// deinit() gets called.
}
#IBAction func punchIt(sender: UIButton)
{
MyWebService.sharedInstance.sendRequest(
completion:{ error:NSError? in
self.completionHandler(self)
// View controller is dismissed, BUT
// deinit() does NOT get called.
}
)
}
...so it is the closure passed to MyWebService.sharedInstance.sendRequest() that was keeoing my view controller alive. I fixed it by adding a capture list like this:
MyWebService.sharedInstance.sendRequest(
completion:{ [unowned self] error:NSError? in
However, I still don't quite understand why the short-lived completion handler, passed to the web service class, executed once, and disposed, was keeping my view controller alive. That closure, not stored anywhere as a property, should be deallocated as soon as it is exited, right?
I must be missing something. I guess I'm still not fully thinking in portals closures.