If i create an new "Single View Application" project with xcode(6.3.2) and add only a deinit function to the ViewController. Run app, close app. Deinit is never called! I tested it with several devices and the simulator.
deinit {
println("deinit:")
}
Why is that? Is that the standard behaviour? What can i do to make it work?
/* Edit */ After reading the comments i put this code to my Viewcontroller.
import UIKit
class A {
func test() {
println("A test")
}
deinit {
println("A deinit")
}
}
class ViewController: UIViewController {
var a:A? = A()
override func viewDidLoad() {
super.viewDidLoad()
println("ViewController viewDidLoad")
a?.test()
}
override func viewDidDisappear(animated:Bool) {
println("ViewController viewDidDisappear")
a = nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
println("ViewController didReceiveMemoryWarning")
}
deinit {
println("ViewController deinit")
a = nil
}
}
Now i get
ViewController viewDidLoad:
A test
ViewController viewDidDisappear:
A deinit
Does this mean that i have to cleanup my objects by hand within the viewDidDisappear call because the view controllers deinit function is not reliable?
No. In this case the ViewController deinit isn't even being called because ViewController doesn't go out of memory.
A way to test this is to create a new ViewController and replace the current ViewController with it. This should remove your current ViewController out of the memory, hence calling it's deinit method.
As apple states in documentation, App termination is done via SIGKILL that it a lot faster than exiting via sending message.
So no code will be called, neither applicationWillTerminate, for example.
try this code:
// CustomView.swift
import UIKit
class CustomView: UIView {
deinit{
print("Swift CustomView dying...\n")
}
}
in controller:
var v : CustomView?
v = CustomView(frame: CGRectZero)
v = nil
You will we in console:
Swift CustomView dying...
A Sample project is available on request. (write me)
Related
I created an outlet in ViewController class and I'd like to modify it.
In the ViewController.swift file I have
import Cocoa
class ViewController: NSViewController {
#IBOutlet var LabelText: NSTextFieldCell?
override func viewDidLoad() {
super.viewDidLoad()
}
//other things
}
I'd like to change the background color of the label. How can I do that from AppDelegate?
At first I thought I could solve this problem using a function in ViewController and calling it in AppDelegate
func changeBackground() {
LabelText.textColor = NSColor.red
}
But soon I realised that it wasn't possible unless I used a static function. Then I tried to modify the code in ViewController like that
static func changeBackground() {
LabelText.textColor = NSColor.red
}
and call this function in AppDelegate like that
ViewController.changeBackground()
In this way I can access to changeBackground() function from AppDelegate, but in ViewController it gives me an error: Instance member 'LabelText' cannot be used on type 'ViewController'
I understood that this cannot be possible because somehow I'm calling "LabelText" before it's initialised (or something like that).
I don't know much about Swift and I'm trying to understand how it works. I've been searching for the answer to my question for hours, but still I don't know how to solve this.
Solution
As Rob suggested, the solution is to use NotificationCenter.
A useful link to understand how it works: https://www.appypie.com/notification-center-how-to-swift
Anyway, here how I modified the code.
In ViewController:
class ViewController: NSViewController {
#IBOutlet var label: NSTextFieldCell!
let didReceiveData = Notification.Name("didReceiveData")
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: didReceiveData, object: nil)
super.viewDidLoad()
}
#objc func onDidReceiveData(_ notification: Notification) {
label.textColor = NSColor.red
}
}
And then, in AppDelegate:
let didReceiveData = Notification.Name("didReceiveData")
NotificationCenter.default.post(name: didReceiveData, object: nil)
I've tried, without success, respond to events such as windowWillClose() and windowShouldClose() inside NSWindowController (yes conforming to NSWindowDelegate).
Later, to my surprise, I was able to receive those events if I make my contentViewController (NSViewController) conform to NSWindowDelegate.
Unfortunately, later on, found out that view.window?.windowController is nil inside windowWillClose() or windowShouldClose(), code:
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
self.view.window?.windowController // not nil!
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // nil!!
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // nil!!
return true
}
After realizing that view.window?.windowController is not nil inside viewDidAppear() the next thing I thought was that Swift garbage collected the controller, so I changed viewDidAppear() in a way that creates another reference of windowController thus preventing garbage collection on said object, code:
var windowController: NSWindowController?
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
windowController = view.window?.windowController
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // NOT nil
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // NOT nil
return true
}
My hypothesis turned out to be correct (I think).
Is this the same issue that is preventing me from receiving those events inside NSWindowController?
Is there another way I can achieve the same thing without creating more object references?
In order to post code, I use the Answer option even though it is more of a comment.
I added in NSViewController:
override func viewDidAppear() {
super.viewDidAppear()
parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC. // The NSWC class, which conforms to NSWindowDelegate
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
I get print log:
viewDidAppear() windowController Optional()
and notification is passed.
But if I change to
override func viewDidAppear() {
super.viewDidAppear()
// parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
by commenting out parentWindowController, notification don't go anymore to the WindowController…
Edited: I declared in ViewController:
var parentWindowController: NSWindowController? // Helps keep a reference to the controller
The proposed solutions are, in my opinion, hacks that can cause serious problems with memory management by creating circular references. You definitely can make instances of NSWindowController work as the window’s delegate. The proper way is to wire it up correctly in either code or in Interface Builder in Xcode. An example of how to do it properly is offered here.
If the delegate methods are not called is because the wiring up is not done correctly.
Another thing that must be done in Swift is when you add the name of the NSWindowController subclass in Interface Builder in Xcode is to check the checkbox of Inherits from Module. If you fail to do this, none of your subclass methods will be called.
In my HomeViewController have four section:
Banner
Popular Product
New Product
Old Product
All are .xib files and they are working fine.
Now I want to add ActivityIndicator in my HomeViewController
So now I want to show ActivityIndicator on HomeViewController until all the .xib's file not fully loaded in HomeViewController as .xib's ActivityIndicator should hide.
Now, the problem for me is that how can I get the confirmation about .xib's loaded to HomeViewController or not?
As a pretty simple direct solution, you could follow the delegation approach. So, when "Popular Product" View complete the needed process, you should fire a delegate method which will be handled by the view controller.
Example:
consider that in PopularProduct class you are implementing a method called doSomething() which need to get called and finish its work to hide the activity indicator from the view controller and it should send Data instance to the view controller. You could do it like this:
protocol PopularProductDelegate: class {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data)
}
class PopularProduct: UIView {
// ...
weak var delegate: PopularProductDelegate?
func doSomething() {
// consider that there is much work to be done here
// which generates a data instance:
let data = Data(base64Encoded: "...")!
// therefore:
delegate?.popularProduct(popularProduct: self, doSomethingDidPerformWith: data)
}
// ...
}
Therefore, in ViewController:
class ViewController: UIViewController {
// ...
var popularProduct: PopularProduct!
override func viewDidLoad() {
super.viewDidLoad()
// show indicator
popularProduct.doSomething()
}
// ...
}
extension ViewController: PopularProductDelegate {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data) {
print(data)
// hide indicator
}
}
Furthermore, you could check my answer for better understanding for delegates.
I have the following code:
extension ViewController {
func AddLeftGesture(){
let SwipeLeft:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyDismissOnSwipeLeft))
self.view.addGestureRecognizer(SwipeLeft)
}
func MyDismissOnSwipeLeft(){
self.dismiss(animated: true, completion: nil)
}
and What I would like to accomplish is that override the viewDidLoad and
call AddLeftGesture method so that it'll be part of each VC I make
and I don't have to type it again and again in each viewDidLoad,
is this possible? or do you guys have any other suggestions?
well I don't think it's a good idea, because typically viewDidLoad is used for setting most properties and if you would like to override it in a view controller you should write it again.What I can suggest is that to make a base ViewController and add this code in the viewDidLoad of that and then subclass every viewController from the base view controller , This way whenever you want to change anything you just call super.viewDidLoad
class BaseViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
addLeftGesture()
}
}
class CustomViewController: BaseViewController{
}
Make this class which inherits UITapGestureRecognizer
open class BlockTap: UITapGestureRecognizer {
fileprivate var tapAction: ((UITapGestureRecognizer) -> Void)?
public override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
}
public convenience init (
tapCount: Int = 1,
fingerCount: Int = 1,
action: ((UITapGestureRecognizer) -> Void)?) {
self.init()
self.numberOfTapsRequired = tapCount
#if os(iOS)
self.numberOfTouchesRequired = fingerCount
#endif
self.tapAction = action
self.addTarget(self, action: #selector(BlockTap.didTap(_:)))
}
open func didTap (_ tap: UITapGestureRecognizer) {
tapAction? (tap)
}
}
then make an extension of UIView
extension UIView {
public func addTapGesture(tapNumber: Int = 1, action: ((UITapGestureRecognizer) -> ())?) {
let tap = BlockTap(tapCount: tapNumber, fingerCount: 1, action: action)
addGestureRecognizer(tap)
isUserInteractionEnabled = true
}
}
Then You can use this as
override func viewDidLoad() {
super.viewDidLoad()
self.view.addTapGesture(action: {[unowned self] (_) in
//Do whatever on click of View
})
}
Hope it helps!
There's two options AFAIK. Either you can subclass UIViewController and then make all of your controllers inherit from the subclassed one, or you can swizzle UIViewController's viewDidLoad().
I personally would choose swizzling, although it has one disadvantage - it hides the implementation and might be confusing for a new developer coming onto a project. So make sure you document this properly, somewhere in your project README and in the code as well.
Now for some code examples:
Subclassing UIViewController
MyViewController.swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addGesture()
}
func addGesture() {
// Do what you need
}
}
class OtherViewController: MyViewController {
// Automatically will add gesture because it's a subclass of MyViewController
}
Swizzling viewDidLoad
What method swizzling does is, that it exchanges implementations of your methods. That simply means that the name of your function points at code from a different function. For more information on this topic read this article.
UIViewController+Swizzle.swift
static func swizzle(selector originalSelector: Selector,
with newSelector: Selector,
on targetClass: AnyClass) {
let originalMethod = class_getInstanceMethod(targetClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(targetClass, newSelector)
// If we were able to add the swizzled function, replace methods.
// Otherwise exchange implementations if method already exists.
if class_addMethod(targetClass, originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)) {
class_replaceMethod(targetClass, newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
extension UIViewController {
// This function is getting called automatically by the runtime,
// when this class is loaded to perform some additional intiialization.
// However, this has now been deprecated in Swift, so only option is to
// declare a static function which you need to remember to call from
// somewhere, preferably early in your app initialization, like your
// didFinishLaunching function in AppDelegate or even AppDelegate's init
// function. I kept the initialize function in the code as a reference,
// however you would probably want to write it like in the comment
// below, to silence the warning.
//
// class func swizzle()
//
open override class func initialize() {
if self != UIViewController.self { return }
let swizzlingClosure: () = {
swizzle(selector: #selector(UIViewController.viewDidLoad),
with: #selector(UIViewController.swizzled_viewDidLoad),
on: UIViewController.self)
}()
swizzlingClosure
}
#objc private func swizzled_viewDidLoad() {
// Calls the original implementation,
// because implementations are switched.
swizzled_viewWillAppear(animated)
// Do whatever you need
addGesture()
}
#objc func addGesture() {
// Add your gesture
}
}
I have this hierarchy - UIViewController -> ChildUIViewController -> WKWebView.
I had an issue with the WKWebView message handler that leaked and prevented the child view controller from being released.
After some reading I found a way to fix the retain cycle by using this fix - WKWebView causes my view controller to leak
Now I can see that the child view controller is reaching deinit but right after that the WKWebView is crashing on deinit (No useful log from Xcode).
Any Idea or direction what could be the issue ?
Thanks
UPDATE
here is my code - Code Gist
Put this inside deinit method of child view controller:
webView.scrollView.delegate = nil
Don't forget to remove WKWebView's delegates you added:
deinit {
webView.navigationDelegate = nil
webView.scrollView.delegate = nil
}
Looks like WKWebView stores __unsafe_unretained pointer to your delegate. Sometimes when web view deallocated not immediate after view controller deallocation. This cause crash when web view tries to notify delegate something.
I tried with same way as you mentioned. It works perfectly for me. Code which i tried is,
class CustomWKWebView : WKWebView {
deinit {
print("CustomWKWebView - dealloc")
}
}
class LeakAvoider : NSObject, WKScriptMessageHandler {
weak var delegate : WKScriptMessageHandler?
init(delegate:WKScriptMessageHandler) {
self.delegate = delegate
super.init()
}
func userContentController(userContentController: WKUserContentController,
didReceiveScriptMessage message: WKScriptMessage) {
self.delegate?.userContentController(
userContentController, didReceiveScriptMessage: message)
}
deinit {
print("LeakAvoider - dealloc")
}
}
class ChildViewController: UIViewController , WKScriptMessageHandler{
var webView = CustomWKWebView()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.frame = self.view.bounds;
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let url = NSURL(string: "https://appleid.apple.com")
webView.loadRequest(NSURLRequest(URL:url!))
webView.configuration.userContentController.addScriptMessageHandler(
LeakAvoider(delegate: self), name: "dummy")
}
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
{
}
deinit {
print("ChaildViewController- dealloc")
webView.stopLoading()
webView.configuration.userContentController.removeScriptMessageHandlerForName("dummy")
}
}
class ViewController: UIViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override func viewDidLoad() {
super.viewDidLoad()
}
deinit {
print("ViewController - dealloc")
}
}
Log after popping ViewController is:
ViewController - dealloc
ChaildViewController- dealloc
LeakAvoider - dealloc
CustomWKWebView - dealloc
UPDATE
Put below lines in your WebViewViewController's viewWillDisappear function.
webView.navigationDelegate = nil
webView.scrollView.delegate = nil
I tried by setting these two delegates in my code and it started crashing. Solved by putting above lines in viewWillDisappear of ChildViewController.
Remember, the reason might caused by weak reference to it.
I'm sure that you have instantiated local variable of wrapped class with WKWebView.