I reported this to Apple with a reproducing project as FB13297964, but I have questions.
TL;dr
How does CGEvent.copy()
differ from CGEventCreateCopy()
? Is it just syntactic sugar? Is there some glue code that retains or releases? Is it generated by the compiler, or provided by Apple (i.e. written by a human and possibly buggy)?
Detail
I recently ran into an issue when I converted some working CGEventTap
code from C to Swift. A CGEventTap
is created in C with CGEventTapCreate()
and passed a pointer to a C callback function. The callback is passed the incoming event, and you can return that event, or a new one, to be handled by the rest of the system.
My C code worked fine. But when translated to Swift, I was getting âInvalid event returned by callbackâ log messages (and my events were ignored). After investigating, I discovered that CGEventCreateCopy()
creates a CGEvent with a retain count of 3, while CGEvent.copy()
creates a CGEvent with a retain count of 2 (why these aren't both 1 escapes me).
The C callback looks like this:
typedef CGEventRef __nullable (*CGEventTapCallBack)(CGEventTapProxy proxy,
CGEventType type, CGEventRef event, void * __nullable userInfo);
This callback returns the supplied event or a newly-created one. In my code, I sometimes create a new event to return. In the C code, I did that with CGEventCreateCopy()
, which gives me a new CGEventRef
, and returned that.
In Swift, the callback signature looks like this:
public typealias CGEventTapCallBack = @convention(c) (CGEventTapProxy, CGEventType, CGEvent, UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>?
and you make copies with
class CGEvent {
public func copy() -> CGEvent?
}
Because the Swift callback returns an unmanaged reference, I was returning the event like this:
return Unmanaged.passUnretained(newEvent)
The callback has this comment:
The event passed to the callback is retained by the calling code, and is released after the callback returns and the data is passed back to the event system. If a different event is returned by the callback function,then that event will be released by the calling code along with the original event, after the event data has been passed back to the event system.
Maybe I should be returning the object using passRetained()
, but that seems wrong to me, especially if I'm returning the event supplied to me (which is retained by the CGEvent system). It especially feels wrong that I should return it differently depending on whether or not I created a copy, and I don't understand why the C copy function has a higher retain count than the Swift copy function.
And I should note that if I do return it passRetained()
without making a copy, I leak memory.
The documentation for passRetained
and passUnretained
talk about their use when passing to code: âThis is useful when passing a reference to an API which Swift does not know the ownership rules for, but you know that the API expects you to pass the object at +0 [or +1 for passRetained].â But it doesnât talk about returning to code that called you.