Skip to content

Instantly share code, notes, and snippets.

@amomchilov
Forked from CentrumGuy/PointerIssue.swift
Last active November 12, 2022 13:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amomchilov/a664fbd2bb0da47a219f826f2a35703e to your computer and use it in GitHub Desktop.
Save amomchilov/a664fbd2bb0da47a219f826f2a35703e to your computer and use it in GitHub Desktop.
import Dispatch
import Foundation
// The Weak struct is the weak wrapper
struct Weak<T: AnyObject> {
weak var object: T?
}
// Stand-in for AUGraphAddRenderNotify
func call(
cCallback: @escaping @convention(c) (UnsafeMutableRawPointer) -> Void,
inRefCon: UnsafeMutableRawPointer
) {
cCallback(inRefCon) // call once immediately
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
cCallback(inRefCon) // call once after deallocation of the TestObject
}
}
// A callback that's guarenteed to not capture a TestObject
let theCallBack: @convention(c) (UnsafeMutableRawPointer) -> Void = { rawPointer in
print("Running callback")
let weakWrapperPointer = rawPointer.assumingMemoryBound(to: Weak<TestObject>.self)
let weakWrapper = weakWrapperPointer.pointee
if let object = weakWrapper.object {
print(object.value)
}
}
// TestObject represents the AudioPlayer class
class TestObject {
let value = "test"
init () {
print("init")
// Create a wrapper for this object
let weakWrapper = Weak<TestObject>(object: self)
// Create the wrapper pointer
let size = MemoryLayout<Weak<TestObject>>.size
let weakWrapperPointer = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: 1)
weakWrapperPointer.storeBytes(of: weakWrapper, as: Weak<TestObject>.self)
// This is the problem. I can't figure out how to get this to work without crashing
call(cCallback: theCallBack, inRefCon: weakWrapperPointer)
}
deinit {
print("deinit")
}
}
// Create a test object, which immediately gets deallocated (represents the AudioPlayer)
_ = TestObject()
RunLoop.main.run(until: Date().addingTimeInterval(2))
print("The end")
@amomchilov
Copy link
Author

amomchilov commented Apr 15, 2020

And here's the fix:

let weakWrapperPointer = UnsafeMutableRawPointer.allocate(
	byteCount: MemoryLayout<Weak<TestObject>>.size,
	alignment: MemoryLayout<Weak<TestObject>>.alignment
)

var weakWrapper = Weak<TestObject>(object: self)

_ = withUnsafeMutablePointer(to: &weakWrapper) { source in
	weakWrapperPointer.moveInitializeMemory(as: Weak<TestObject>.self, from: source, count: 1)
	// weakWrapper is now uninitialized. It can't be accessed any longer
}

call(cCallback: theCallBack, inRefCon: weakWrapperPointer)

@CentrumGuy
Copy link

And here's the fix:

let weakWrapperPointer = UnsafeMutableRawPointer.allocate(
	byteCount: MemoryLayout<Weak<TestObject>>.size,
	alignment: MemoryLayout<Weak<TestObject>>.alignment
)

var weakWrapper = Weak<TestObject>(object: self)

_ = withUnsafeMutablePointer(to: &weakWrapper) { source in
	weakWrapperPointer.moveInitializeMemory(as: Weak<TestObject>.self, from: source, count: 1)
	// weakWrapper is now uninitialized. It can be accessed any longer
}

// This is the problem. I can't figure out how to get this to work without crashing
call(cCallback: theCallBack, inRefCon: weakWrapperPointer)

IT WORKED!!!!
Thank you so much 😊!!!
Was the problem the way I was making the pointer?

@amomchilov
Copy link
Author

Was the problem the way I was making the pointer?

Copying my answer here for passers-by:

storeBytes(of:as:) doesn't work here. Look at the documentation

The type T to be stored must be a trivial type.

struct Weak<T> isn't a trivial type, because it has an implicit deinitializer which decrements the reference count of the weak ref's side table entry.

What's happening is that the deinit runs are the end of TestObject.init, when the weakWrapper local variable goes out of scope.

But then, hen you load it from the raw pointer, you're copying the same weak ref, and tryign to deinitialize again, causing a double-free error

Instead, I move initialize the pointer's destination, destroying the weakWrapper local variable (without running its deinitializer, thus not decrementing the weak ref)

@mori-nobuteru
Copy link

I think this may be a simpler solution:

class WeakVar<T: AnyObject> {
    weak var object: T?

    init(object: T) {
      self.object = object
    }
}

// Stand-in for AUGraphAddRenderNotify
func call(
	cCallback: @escaping @convention(c) (UnsafeMutableRawPointer) -> Void,
	inRefCon: UnsafeMutableRawPointer
) {
	cCallback(inRefCon) // call once immediately
	
	DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
		cCallback(inRefCon) // call once after deallocation of the TestObject
	}
}

// A callback that's guarenteed to not capture a TestObject
let theCallBack:  @convention(c) (UnsafeMutableRawPointer) -> Void = { rawPointer in
		print("Running callback")

		let weakWrapper = Unmanaged<WeakVar>.fromOpaque(opaque).takeRetainedValue()
		if let object = weakWrapper.object {
			print(object.value)
		}
	}

class TestObject {
    init() {
        let weakVar = Weak<TestObject>(object: self)
        let opaque = Unmanaged.passRetained(weakVar).toOpaque()
        call(cCallback: theCallBack, inRefCon: opaque)
    }
}

@amomchilov
Copy link
Author

@mori-nobuteru if I understand correctly, you’re passing in a strong reference to a WeakVar object (now a class), whereas I was passing in a a raw pointer to the struct.

I like your approach better! It involves one more reference counted object, but it’s semantics are clearer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment