k.benua
(Christian)
1
I use custom generic Sequence which stores closures inside:
final class ArrayHolder<T>: Sequence {
var arr = [() -> T?]()
public func add(_ element: @escaping () -> T?) {
arr.append(element)
}
public func makeIterator() -> Array<T>.Iterator {
return arr.compactMap { $0() }.makeIterator()
}
}
I also use generic function over T: AnyObject to create closure which returns weak object reference:
public func weakify<T: AnyObject>(_ object: T) -> (() -> T?) {
{ [weak object] in object }
}
And I get Segmentation Fault when I call Sequence.forEach method like this:
let c = MyClass()
let arrHolder = ArrayHolder<MyClass>()
arrHolder.add(weakify(c))
arrHolder.forEach { // Segmentation Fault
print($0)
}
While trying to investigate reasons behind Segmentation Fault, I observed weird behaviour:
// new weakify function with logs
func weakify<T: AnyObject>(_ object: T) -> (() -> T?) {
weak var obj: T? = object
return {
if obj == nil {
print("weakify nil")
} else {
print("weakify non nil") // prints "weakify non nil"
}
return obj
}
}
// modified call side
arrHolder.add(weakify(c))
let notNils = arrHolder.arr.compactMap {
let res = $0()
if let res {
print("compact map not nil")
} else {
print("compact map nil") // prints "compact map nil"
}
return res
}
print(notNils.count, arrHolder.count) // prints 0, 1
Surely, some dark magic is hidden there.
I don't know what to do next.
Thanks in advance.
2 Likes
k.benua
(Christian)
2
Recently we found interesting work-around:
let f = weakify(c)
arrHolder.add(f)
But it's still unclear, why inlining f leads to Segmentation Fault
k.benua
(Christian)
3
Also, we found more compact sample to illustrate issue
final class MyClass { }
func weakify<T: AnyObject>(_ object: T) -> (() -> T) {
{ object }
}
func printer<T>(_ factory: @escaping () -> T) {
print(factory())
}
let c = MyClass()
printer(weakify(c)) // Segmentation Fault
Both ways work here:
import Foundation
final class MyClass {
}
func weakify<T: AnyObject>(_ object: T) -> (() -> T) {
{ object }
}
func nonEscapingPrinter<T>(_ factory: () -> T) {
print(factory())
}
func escapingPrinter<T>(_ factory: @escaping () -> T) {
print(factory())
}
let c = MyClass()
nonEscapingPrinter(weakify(c))
let weakifyFunc = weakify(c)
escapingPrinter(weakifyFunc)
Not sure but using @escaping, where the function object is temporary, it is no longer there, the way you call it. When it is made to an object (let weakifyFunc = ...), it works since it is "alive" until the end of the scope. Maybe someone else knows better.
tera
5
The first (big) fragment crashes for me in Debug (-Onone).
Looks like a bug.
Interesting that with the code in the original question this works:
// ...
let weakified = weakify(c)
arrHolder.add(weakified)
let d = MyClass()
arrHolder.add(weakify(d))
arrHolder.forEach { // No Segmentation Fault
print($0)
}
And it prints out:
SwiftTestProject.MyClass
SwiftTestProject.MyClass
Program ended with exit code: 0
Edit: Perhaps because the weakify func is still in scope because of weakified?