Skip to content

Commit 9fbfe18

Browse files
authored
NSView lifetime fixes (#104)
* drain autorelease pools in Window::open_* methods * fixes to NSView lifetime logic: - in open_parented and open_blocking, release NSView after adding it as a subview of the parent - in open_blocking, don't call autorelease on NSWindow. previously it was a no-op, but now that we are actually draining our autorelease pools, it ends up prematurely releasing the window. * fixes to NSView cleanup logic: - Move retainCount check to before calling [super release]. If [super release] happens first, then in the final call to release, [super release] deallocates the object and the call to retainCount results in a segfault. - Move objc_disposeClassPair to dealloc. Previously we were calling it when retainCount == 1, but that's exactly when dealloc is called, so this is cleaner. Also, we need to call objc_disposeClassPair after [super dealloc]. NOTE: The circular-reference-breaking logic in release is definitely broken. It's easy to thwart it by e.g. creating a wgpu surface at some point after build() or dropping one at some point before drop(). Need to come up with a better solution.
1 parent 6172090 commit 9fbfe18

2 files changed

Lines changed: 46 additions & 17 deletions

File tree

src/macos/view.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ unsafe fn create_view_class() -> &'static Class {
119119
sel!(release),
120120
release as extern "C" fn(&mut Object, Sel)
121121
);
122+
class.add_method(
123+
sel!(dealloc),
124+
dealloc as extern "C" fn(&mut Object, Sel)
125+
);
122126
class.add_method(
123127
sel!(viewWillMoveToWindow:),
124128
view_will_move_to_window as extern "C" fn(&Object, Sel, id)
@@ -229,11 +233,17 @@ extern "C" fn accepts_first_mouse(
229233

230234

231235
extern "C" fn release(this: &mut Object, _sel: Sel) {
232-
unsafe {
233-
let superclass = msg_send![this, superclass];
234-
235-
let () = msg_send![super(this, superclass), release];
236-
}
236+
// Hack for breaking circular references. We store the value of retainCount
237+
// after build(), and then when retainCount drops back to that value, we
238+
// drop the WindowState, hoping that any circular references it holds back
239+
// to the NSView (e.g. wgpu surfaces) get released.
240+
//
241+
// This is definitely broken, since it can be thwarted by e.g. creating a
242+
// wgpu surface at some point after build() (which will mean the NSView
243+
// never gets dealloced) or dropping a wgpu surface at some point before
244+
// drop() (which will mean the WindowState gets dropped early).
245+
//
246+
// TODO: Find a better solution for circular references.
237247

238248
unsafe {
239249
let retain_count: usize = msg_send![this, retainCount];
@@ -257,11 +267,23 @@ extern "C" fn release(this: &mut Object, _sel: Sel) {
257267
}
258268
}
259269

260-
if retain_count == 1 {
261-
// Delete class
262-
let class = msg_send![this, class];
263-
::objc::runtime::objc_disposeClassPair(class);
264-
}
270+
}
271+
272+
unsafe {
273+
let superclass = msg_send![this, superclass];
274+
let () = msg_send![super(this, superclass), release];
275+
}
276+
}
277+
278+
extern "C" fn dealloc(this: &mut Object, _sel: Sel) {
279+
unsafe {
280+
let class = msg_send![this, class];
281+
282+
let superclass = msg_send![this, superclass];
283+
let () = msg_send![super(this, superclass), dealloc];
284+
285+
// Delete class
286+
::objc::runtime::objc_disposeClassPair(class);
265287
}
266288
}
267289

src/macos/window.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl Window {
4141
B: FnOnce(&mut crate::Window) -> H,
4242
B: Send + 'static,
4343
{
44-
let _pool = unsafe { NSAutoreleasePool::new(nil) };
44+
let pool = unsafe { NSAutoreleasePool::new(nil) };
4545

4646
let handle = if let RawWindowHandle::MacOS(handle) = parent.raw_window_handle() {
4747
handle
@@ -60,6 +60,9 @@ impl Window {
6060

6161
unsafe {
6262
let _: id = msg_send![handle.ns_view as *mut Object, addSubview: ns_view];
63+
let () = msg_send![ns_view as id, release];
64+
65+
let () = msg_send![pool, drain];
6366
}
6467
}
6568

@@ -69,7 +72,7 @@ impl Window {
6972
B: FnOnce(&mut crate::Window) -> H,
7073
B: Send + 'static,
7174
{
72-
let _pool = unsafe { NSAutoreleasePool::new(nil) };
75+
let pool = unsafe { NSAutoreleasePool::new(nil) };
7376

7477
let ns_view = unsafe { create_view(&options) };
7578

@@ -82,6 +85,10 @@ impl Window {
8285

8386
Self::init(window, build);
8487

88+
unsafe {
89+
let () = msg_send![pool, drain];
90+
}
91+
8592
raw_window_handle
8693
}
8794

@@ -91,7 +98,7 @@ impl Window {
9198
B: FnOnce(&mut crate::Window) -> H,
9299
B: Send + 'static,
93100
{
94-
let _pool = unsafe { NSAutoreleasePool::new(nil) };
101+
let pool = unsafe { NSAutoreleasePool::new(nil) };
95102

96103
// It seems prudent to run NSApp() here before doing other
97104
// work. It runs [NSApplication sharedApplication], which is
@@ -131,8 +138,7 @@ impl Window {
131138
NSWindowStyleMask::NSTitledWindowMask,
132139
NSBackingStoreBuffered,
133140
NO,
134-
)
135-
.autorelease();
141+
);
136142
ns_window.center();
137143

138144
let title = NSString::alloc(nil)
@@ -156,9 +162,10 @@ impl Window {
156162

157163
unsafe {
158164
ns_window.setContentView_(ns_view);
159-
}
165+
let () = msg_send![ns_view as id, release];
166+
167+
let () = msg_send![pool, drain];
160168

161-
unsafe {
162169
app.run();
163170
}
164171
}

0 commit comments

Comments
 (0)