Property Initialization with closures - swift

I was looking into ARC and strong reference cycles and ran into this code of mine:
class TestClass: UIView {
let button: UIButton = {
let view = UIButton()
view.frame = CGRect(x: 50, y: 50, width: 200, height: 200)
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
view.setTitle("Button", for: .normal)
view.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
return view
}()
#objc private func buttonClicked() {
print("Clicked")
}
override init(frame: CGRect) {
super.init(frame: frame)
print("Object of TestClass initialized")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("Object of TestClass deinitialized")
}
}
reference to self in the addTarget method inside the closure doesn't seem to create a strong reference cycle.
Can someone explain why?
Also, I noticed that if I remove inheritance from UIView the compiler starts complaining: Use of unresolved identifier 'self'.
Can someone explain this as well, why does it happen in this case and doesn't in the first one?

This is not a retain cycle because self is not what you think it is :)
Properties with initial value are "executed" even before any initializer runs, and for those properties self points to a higher order function of this type:
(TestClass) -> () -> TestClass
So you don't really access the instance, but rather you access a static-like method that does the initialization of all properties that have a default value. This is why you don't have a retain cycle.
addTarget accepts an Any? value for it's first argument, so this violates no type rules so the compiler doesn't complain that you don't pass a NSObject instance there.
Checking the later behaviour - e.g. what happens if the button is added to the UI hierarchy and is tapped, reveals something interesting: the runtime sees that you passed a non-object as a target and sets null values for target and action:

Related

NSTouchBar not releasing last touched view

I am making an app which uses the NSTouchBar.
The touchbar is made by the NSWindowController.makeTouchBar()method.
In this touchbar I can place NSCustomTouchBarItems.
I have made two NSCustomTouchBarItems.
The first one sets a view to a default ui button, with this:
let item001 = NSCustomTouchBarItem(identifier: someIdentifier)
item001.view = NSButton(title: "myButton", target: nil, action: nil)
The second one sets a viewController, with this:
let item002 = NSCustomTouchBarItem(identifier: someIdentifier)
item002.viewController = TestViewController()
The TestViewController only loads a simple view inside its loadView()
method.
class TestViewController: NSViewController {
override func loadView() {
self.view = TestView001(frame: NSRect(x: 0, y: 0, width: 100, height: 30))
}
}
The TestView001 only creates a background color so you can see it.
TestView001 has the following code:
class TestView001: NSView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
print("TestView001.init()")
// Create a background color.
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.green.cgColor
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
All of this works perfectly.
But when I have touched the second item inside the touchbar,
and then close my app's window.
The windowController and everything else is nicely released
from memory.
But I can still see that TestView001 is in memory and not being
released.
When using a standard ui button like in item001, then you don't
have this problem.
It looks like some NSTouch still has a reference to the view
if you look at this image:
However, I do not completely understand this image.
What is the best way of solving this.
Thanks in advance.

What to use as NSCoder to initialize a View?

Hey guys I created a new custom View Class and now I want to build a instance of it, I initialized it with the following code:
required init?(coder aCoder: NSCoder) {
super.init(coder: aCoder)
tapRecognizer = UITapGestureRecognizer(
target: self, action: #selector(handleBarTap))
addGestureRecognizer(tapRecognizer)
}
deinit {
removeGestureRecognizer(tapRecognizer)
}
And this is the instance, but what can I use as coder?
lazy var chartView = TutorialChartView(coder: )
Thanks in advance!
When you say View, do you mean UIView? The problem is that that's the wrong initializer. init(coder:) is not something you call; it's a process initiated by the storyboard when there is one of these things in the storyboard and you load that view controller.
The code UIView designated initializer is init(frame:). Implement that and call it, and you're all set.
(You may also have to provide an implementation of init(coder:) but it should just throw a fatalError, because you do not expect to be called this way.)
You should cannot use coder as initializer for creating class, use frame instead, here is your code written in frame-style initializing.
override init(frame: CGRect) {
super.init(frame: frame)
tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleBarTap))
addGestureRecognizer(tapRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Here is how you use it:
lazy var chartView = TutorialChartView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)

Custom UIView retained in memory

I am having an annoying memory issue with a custom UIView when trying to add it as a tableView footer in a VC. Here is what I do:
I declare a custom UIView class called tableViewHeader. My footer is supposed to display a logo and some text, so it has 2 global variables (a UIImage View & a TextView) and one init method to initialize this UIView class (i.e., by adding the two subViews and set some constraints).
In my VC, in the TableView's footerForSection method I declare a new tableViewFooter variable, set the sub-views (attach my PNG, etc.)
and return that view.
The code works fine to display the footer but it wrecks havoc on my memory - the moment I run my app and open the VC that contains that tableViewHeader my memory jumps by ~20MB (~ 20% jump) and doesn't get released once my VC is released (I checked, the VC itself gets correctly de-initialized but it doesn't show any drop in memory).
I tested a few things:
Without adding that footerView and opening/ closing the VC doesn't show that 20MB jump, so I am fairly sure that this is caused by this custom tableViewHeader...
I also checked to make sure the UIImage I load from my Assets is not too large but it's only 200Kb. On top the problem is more the fact that the memory doesn't get released after my VC gets released...
I've tried declaring my variables (the tableViewFooter variable and the sub-views within that new class) but that leaves my variables as nil when I try to add them as sub-views...
I am running out of ideas as to what the culprit is/ how to fix it so any help you could give me would be really highly appreciated!
My code is below - thanks in advance!
Francois
My TableFooterView class:
class MessageTableFooterView: UIView {
var shouldUpdateConstraints = true
var logoView: UIImageView!
var explanationTextView: UITextView!
var addFriendBtn: UIButton!
override init(frame: CGRect){
super.init(frame: frame)
logoView = UIImageView(frame: CGRect.zero)
logoView?.autoSetDimension(.height, toSize: 145)
logoView?.autoSetDimension(.width, toSize: 145)
self.addSubview(logoView)
explanationTextView = UITextView(frame: CGRect.zero)
explanationTextView?.isEditable = false
explanationTextView?.isSelectable = false
explanationTextView?.font = UIFont(name: "HelveticaNeue", size: 17)
explanationTextView?.textColor = .darkGray
explanationTextView?.textAlignment = .center
self.addSubview(explanationTextView)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
My tableView viewForFooter Method:
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if MessageService.instance.savedMsgs.count > 0 {
return nil
} else {
let headerView = MessageTableFooterView(frame: CGRect.zero)
headerView.logoView.image = UIImage(named: "savedMessagesIcon")
headerView.explanationTextView?.text = "Blablabla"
return headerView
}
}

Swift - `self` in variable initialization closure

How does button.addTarget(self, action: #selector(taptap), for: .touchUpInside)
work without a lazy keyword?
Dropped lazy by mistake, and have a closure to initialize a button like below:
class MyView: UIView {
let button: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(taptap), for: .touchUpInside)
print(self) // (Function)
// button.frame = bounds <- Cannot assign here
return button
}()
lazy var button2: UIButton = {
let button = UIButton()
print(self) // <sample.MyView ...>
return button
}()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubview(button2)
addSubview(button)
button.frame = bounds
print(self) // <sample.MyView ...>
}
#objc func taptap() {
print("taptap")
}
}
And the printed result is:
(Function)
<sample.MyView: 0x7f961dd09d80; frame = (67 269; 240 128); autoresize = RM+BM; layer = <CALayer: 0x6080000268a0>>
<sample.MyView: 0x7f961dd09d80; frame = (67 269; 240 128); autoresize = RM+BM; layer = <CALayer: 0x6080000268a0>>
taptap
What's the difference self in button closure and self in others? And why my button is works?
It works in NSObject subclasses because NSObject (or rather NSObjectProtocol) declares method self. That method is also available on metatypes (which are also NSObject instances) and therefore you can call it in static context.
The fact that it actually works on UIButton is probably a quirk of the compiler and the fact that UIButton accepts Any? as target.
Don't use it, it's not how it's intended to work.
See the bug SR-4559
In short, when you declare an instance property using closure initialization, that property will be created before self would be available (before the instance had been initalized properly), hence you cannot access self. A lazy instance property can only ever be accessed after the instance had been initalized, so you can access self from a lazy propery.
Longer version:
If you use closure initializiation by let button: UIButton = { return UIButton() }(), the button variable will be handled the exact same way in runtime as if you simply declared it like let button:UIButton = UIButton(). Pay attention to the () at the end of the closure. That essentially executes the closure right away when the instance property is being initialized, this is why it can actually be declared as immutable. Since instance properties are being initialized before the class initializer would be called, self is not available inside the closure for of a property.
When you declare a variable using the lazy keyword, it will only be evaluated when it is first accessed. Due to this, self is available inside the closure declaring a lazy property, since a property cannot be accessed before the class instance would be created, so in all cases, self is already initialized and available by the time you'd ever access the lazy property.

Swift Programming UIButtons

Im relatively new to the swift language and I am having a difficult time with creating a button. I have created a new file as a UIButton, so how would I change the button type using "self"?
for example..
import UIKit
class LoginButton: UIButton {
override init(frame: CGRect){
super.init(frame: frame)
self.buttonWithType(UIButtonType.System) as UIButton
// Above is where I need the button type as "System"
self.enabled = true
self.setTitle("Log in", forState: UIControlState.Normal)
self.addTarget(self, action: "logInPressed", forControlEvents: UIControlEvents.TouchUpInside)
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func logInPressed(){
println("log in")
}
}
Do you really need to subclass UIButton? In most use cases, one uses UIButton directly and does not subclass it.
The buttonWithType() method you reference is a class method (not an instance method). You call it as UIButton.buttonWithType(type) which returns an instance of a UIButton so configured. You can't change the type after you instantiate the button; however, several methods are available for modifying the look of the button after you instantiate.
To add a brown border of size 2.0 to your button, you could add the following code:
myButton.layer.borderColor = UIColor.brownColor().CGColor
myButton.layer.borderWidth = 2.0