deinitialization, self, and closures. [duplicate] - swift

This question already has answers here:
Different closures giving different results for retain cycles in swift
(2 answers)
Closed 5 years ago.
Now when a closure refers to self within its body the closure captures self, which means that it holds a strong reference back to the HTMLElement instance(see below). A strong reference cycle is created between the two. This means that the instance won't deinitialize (as shown below); however when I tried to replace self with heading (the instance) deinitilization did work which means that a strong reference cycle didn't exist. My understanding is that heading in this case is the same as self, since self is the instance itself. So why did the instance deinitialize when I replaced it with the instance 'heading'?
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
let defaultText = "some default text"
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var heading = HTMLElement(name: "h1")
let defaultText = "some default text"
print(heading.asHTML())
// Prints "<h1>some default text</h1>”
heading = HTMLElement(name: "h4")
output:
< h1 > some default text < /h1 >
h1 is being deinitialized
Now if the closure was replaced as follows:
lazy var asHTML: () -> String = {
let defaultText = "some default text"
return "<\(self.name)>\(self.text ?? defaultText)</\(self.name)>"
}
the output would be:
< h1 > some default text < /h1 >

The correct way to avoid strong reference cycle with closure is to mark self as either unowned or weak, e.g.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = { [unowned self] in
let defaultText = "some default text"
return "<\(self.name)>\(self.text ?? defaultText)</\(self.name)>"
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
It begs the question why you don't just use a function
func asHTML() -> String {
let defaultText = "some default text"
return "<\(name)>\(text ?? defaultText)</\(name)>"
}
or computed property:
var asHTML: String {
let defaultText = "some default text"
return "<\(name)>\(text ?? defaultText)</\(name)>"
}

Related

How to get the property names for `UIFont` (and other UIKit classes)?

This simple code:
func getProperties(object: AnyObject) -> [String] {
var result = [String]()
let mirror = Mirror(reflecting: object)
mirror.children.forEach { property in
guard let label = property.label else {
return
}
result.append(label)
}
return result
}
works for custom classes, e.g.:
class A {
var one: String
var two: Int
init() {
one = "hello"
two = 1
}
}
var a = A()
print(getProperties(object: a))
// prints ["one", "two"]
However the same code for UIFont (and other UIKit classes), returns nothing:
var font = UIFont.systemFont(ofSize: 10)
print(getProperties(object: font))
// prints []
I also tried an old-school extension for NSObject:
extension NSObject {
var propertyNames: [String] {
var result = [String]()
let clazz = type(of: self)
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else {
return result
}
for i in 0..<Int(count) {
let property: objc_property_t = properties[i]
guard let name = NSString(utf8String: property_getName(property)) else {
continue
}
result.append(name as String)
}
return result
}
}
And again, it works for my custom classes (if they extend NSobject and the properties are marked as #objc):
class B: NSObject {
#objc var one: String
#objc var two: Int
override init() {
one = "hello"
two = 1
}
}
var b = B()
print(b.propertyNames)
// prints ["one", "two"]
but doesn't find any properties for UIFont.
If I look at the UIFont definition, the properties are seems defined like regular properties (or at least this is what Xcode shows:
open class UIFont : NSObject, NSCopying, NSSecureCoding {
//...
open var familyName: String { get }
open var fontName: String { get }
// etc
The type of the class is UICTFont though, not sure if it matters.
I would like to understand why this is not working, but my main question is more generic: is there any way to obtain the properties of the UIKit class (e.g. UIFont)?

Is it possible to exclude certain property (Like class type) from being copied during struct copy?

I have the following struct, which contains class.
import Foundation
func generateRichText(body: String?) -> NSMutableAttributedString? {
if body == nil {
return nil
}
// TODO: Some complex logic to decorate body string will be added soon...
let myAttrString = NSMutableAttributedString(string: body!)
return myAttrString
}
struct Note {
var body: String?
// Technique described in https://stackoverflow.com/a/25073176/72437
var bodyAsRichText: NSMutableAttributedString? {
mutating get {
if (cachedBodyAsRichText == nil) {
cachedBodyAsRichText = generateRichText(body: body)
}
return cachedBodyAsRichText
}
}
// TODO: This is a class. I don't want it to be copied over during struct copy.
// If it is copied during struct copy, both struct will be sharing the same
// class instance.
private var cachedBodyAsRichText: NSMutableAttributedString?
}
var note = Note()
note.body = "hello"
print("note.bodyAsRichText = \(Unmanaged.passUnretained(note.bodyAsRichText!).toOpaque())")
var note_copy = note
print("note_copy.bodyAsRichText = \(Unmanaged.passUnretained(note_copy.bodyAsRichText!).toOpaque())")
For the above code, the output will be
note.bodyAsRichText = 0x000055c035cfce70
note_copy.bodyAsRichText = 0x000055c035cfce70
What my desired output is, different struct instance, should be having their very own class instance (cachedBodyAsRichText)
Hence, is there a way, to exclude cachedBodyAsRichText from being copied over, during struct copy?
Your solution is incomplete. Here is a complete and correct solution.
struct Note {
var body: String = "" {
didSet {
cachedBodyAsRichText = nil
}
}
var bodyAsRichText: NSAttributedString {
mutating get {
if (cachedBodyAsRichText == nil) {
cachedBodyAsRichText = generateRichText(body: body)
}
return cachedBodyAsRichText!.copy() as! NSAttributedString
}
}
private var cachedBodyAsRichText: NSAttributedString? = nil
}
You need to clear out the cache every time the body is modified. Once you do that, it won't matter if the object is shared among structs.

lazy instantiation closures vs. just using methods

I was following a Swift tutorial on closures and ran across this piece of code.
class HTMLEelement {
let name: String
let text: String
lazy var asHTML: () -> String = {
[weak self] in
guard let this = self else { return "" }
return "<\(this.name)> \(this.text) </\(this.name)>"
}
init(name:String, text: String) {
self.name = name
self.text = text
}
deinit {
print("HTMLELEMENT \(name) is being deallocated")
}
}
Why did they define the function asHTML like they did instead of just using a regular public method?
Without declaring var as lazy, you cannot use "self" during its initialization. I think this was the main reason to use lazy in your example.

Inline KVO of a Property in another view controller

I have a vc with a dynamic var "value" and i need to know when it's changed in a closure in the calling cv.
target vc:
#objc dynamic var value: String = ""
source:
if let vc: TagButtonPopupViewController = sb.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("TagPopupViewController")) as? TagButtonPopupViewController {
// configure vc
vc.value = sender.title
// observe
_ = vc.observe(\.value) { (tbvc, change) in
print("new string")
}
// present popup
presentViewController(vc, asPopoverRelativeTo: sender.bounds, of: sender, preferredEdge: NSRectEdge.maxY, behavior: NSPopover.Behavior.transient)
}
but "observe" is never called.
Any Ideas how to get notified in a closure whenever "value" has changed in Swift4?
The observer is destroyed because there is no reference
to it after the other view controller has been presented.
You have to store it
observer = vc.observe(\.value) { ... }
where observer is a property of the calling view controller.
A self-contained command-line project example: This prints "new string" as expected:
class A: NSObject {
#objc dynamic var value: String = ""
}
let a = A()
let observer = a.observe(\.value) { (_, _) in print("new string") } // (*)
a.value = "Hello world"
But nothing is printed if (*) is replaced by
_ = a.observe(\.value) { (_, _) in print("new string") }

Strong reference cycle for closures in Swift

I was going through the documentation (chapter on “Automatic Reference Counting” section “Strong Reference Cycles for Closures”) and I can't seem to figure out cases, when defining a class, in which I should keep a strong reference to self (the instance of that class) in a closure to a property.
Capture Lists seem always the best solution to avoid memory leaks, and I really can't think of any scenarios in which I should keep a strong reference cycle.
Here are the examples that the documentation gives:
class HTMLElement {
let name: String
let text: String?
// Without Capture List
#lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
println("\(name) is being deinitialized")
}
}
class HTMLElement {
let name: String
let text: String?
// With Capture List
#lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
println("\(name) is being deinitialized")
}
}
You need to keep a strong reference to self if you’re creating a closure to be executed by an object or function whose lifetime may not match self’s.
For example:
class A {
func do() {
dispatch_async(dispatch_get_global_queue(0, 0)) {
println("I printed \(self) some time in the future.")
}
}
}
var a : A? = A()
a.do()
a = nil // <<<
At the arrow the main function body will release its last reference to the newly created instance of A, but the dispatch queue needs to keep a hold on it until the closure is done executing.