Reputation: 36427
I've read How to demonstrate memory leak and zombie objects in Xcode Instruments? but that's for objective-c. The steps don't apply.
From reading here I've understood zombies are objects which are:
not exactly sure how that's different from accessing a deallocated object.
I mean in Swift you can do:
var person : Person? = Person(name: "John")
person = nil
print(person!.name)
Is person deallocated? Yes!
Are we trying to point to it? Yes!
So can someone share the most common mistake which leads to creating a dangling pointer?
Upvotes: 6
Views: 5434
Reputation: 34391
Swift Zombie Object
Zombie Object
is a deallocated object(ref count is nil) which behaves itself like alive(receive messages). It is possible because of dangling pointer
.
dangling pointer
points on some address in the memory where data is unpredictable
Objective-C has unsafe_unretained
[About] property attributes. [Example]
Swift has
unowned(unsafe)
[About] referenceUnmanaged
- a wrapper for non ARC
Objc- codeunowned(unsafe) let unsafeA: A
func main() {
let a = A() // A(ref count = 1)
unsafeA = a
} // A(ref count = 0), deinit() is called
func additional() {
unsafeA.foo() //<- unexpected
}
Upvotes: 1
Reputation: 540005
Zombie objects are Objective-C objects which have been deallocated, but still receive messages.
In Objective-C, __unsafe_unretained
can be used to create an additional pointer to an object which does not increase the reference count. In Swift that would be unowned(unsafe)
.
Here is a self-contained example:
import Foundation
class MyClass: NSObject {
func foo() { print("foo"); }
deinit { print("deinit") }
}
unowned(unsafe) let obj2: MyClass
do {
let obj1 = MyClass()
obj2 = obj1
print("exit scope")
}
obj2.foo()
obj1
is a pointer to a newly created object, and obj2
is another pointer to the same object, but without increasing the reference counter. When the do { ... }
block is left, the object is deallocated. Sending a message to it via obj2
causes the Zombie error:
exit scope
deinit
*** -[ZombieTest.MyClass retain]: message sent to deallocated instance 0x1005748d0
This can also happen if you use Managed
(e.g. to convert pointers to Swift objects to C void
pointers in order to pass them to C callback functions) and you don't choose the correct retained/unretained combination. A contrived example is:
let obj2: MyClass
do {
let obj1 = MyClass()
obj2 = Unmanaged.passUnretained(obj1).takeRetainedValue()
print("exit scope")
}
obj2.foo()
Upvotes: 7
Reputation: 32879
Here's zombie attack in under 15 lines of code:
class Parent { }
class Child {
unowned var parent: Parent // every child needs a parent
init(parent: Parent) {
self.parent = parent
}
}
var parent: Parent? = Parent()
let child = Child(parent: parent!) // let's pretend the forced unwrap didn't happen
parent = nil // let's deallocate this bad parent
print(child.parent) // BOOM!!!, crash
What happens in this code is that Child
holds an unowned reference to Parent
, which becomes invalid once Parent
gets deallocated. The reference holds a pointer to the no longer living parent (RIP), and accessing that causes a crash with a message similar to this:
Fatal error: Attempted to read an unowned reference but object 0x1018362d0 was already deallocated2018-10-29 20:18:39.423114+0200 MyApp[35825:611433] Fatal error: Attempted to read an unowned reference but object 0x1018362d0 was already deallocated
Note The code won't work in a Playground, you need a regular app for this.
Upvotes: 7
Reputation: 299565
This is not a dangling pointer or a zombie. When you use !
you're saying "if this is nil, then crash." You should not think of person
as a pointer in Swift. It's a value. That value may be .some(T)
or it may be .none
(also called nil
). Neither of those is dangling. They're just two different explicit values. Swift's nil
is nothing like null pointers in other languages. It only crashes like null pointers when you explicitly ask it to.
To create zombies, you'll need to be using something like Unmanaged
. This is extremely uncommon in Swift.
Upvotes: 6