Skip to content

Commit bc35c3a

Browse files
committed
prevent duplicate release of NSImage
1 parent 596ca0f commit bc35c3a

File tree

2 files changed

+36
-58
lines changed

2 files changed

+36
-58
lines changed

cocoa/src/toga_cocoa/icons.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,20 @@ def __init__(self, interface, path):
3838
else:
3939
self.path = path
4040

41-
try:
42-
# We *should* be able to do a direct NSImage.alloc.init...(), but if the
43-
# image file is invalid, the init fails, returns NULL, and releases the
44-
# Objective-C object. Since we've created an ObjC instance, when the
45-
# object passes out of scope, Rubicon tries to free it, which segfaults.
46-
# To avoid this, we retain result of the alloc() (overriding the default
47-
# Rubicon behavior of alloc), then release that reference once we're
48-
# done. If the image was created successfully, we temporarily have a
49-
# reference count that is 1 higher than it needs to be; if it fails, we
50-
# don't end up with a stray release.
51-
image = NSImage.alloc().retain()
52-
self.native = image.initWithContentsOfFile(str(path))
53-
if self.native is None:
54-
raise ValueError(f"Unable to load icon from {path}")
55-
finally:
56-
# Calling `release` here disabled Rubicon's "release on delete" automation.
57-
# We therefore add an explicit `release` call in __del__ if the NSImage was
58-
# initialized successfully.
59-
image.release()
60-
61-
def __del__(self):
62-
if self.native:
63-
self.native.release()
41+
# We *should* be able to do a direct NSImage.alloc.init...(), but if the
42+
# image file is invalid, the init fails, returns NULL, and releases the
43+
# Objective-C object. Since we've created an ObjC instance, when the
44+
# object passes out of scope, Rubicon tries to free it, which segfaults.
45+
# To avoid this, we retain result of the alloc() (overriding the default
46+
# Rubicon behavior of alloc), then release that reference once we're
47+
# done. If the image was created successfully, we temporarily have a
48+
# reference count that is 1 higher than it needs to be; if it fails, we
49+
# don't end up with a stray release.
50+
image = NSImage.alloc().retain()
51+
self.native = image.initWithContentsOfFile(str(path))
52+
if self.native is None:
53+
raise ValueError(f"Unable to load icon from {path}")
54+
image.release()
6455

6556
def _as_size(self, size):
6657
image = self.native.copy()

cocoa/src/toga_cocoa/images.py

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,43 +23,30 @@ class Image:
2323

2424
def __init__(self, interface, path=None, data=None, raw=None):
2525
self.interface = interface
26-
self._needs_release = False
2726

28-
try:
29-
# We *should* be able to do a direct NSImage.alloc.init...(), but if the
30-
# image file is invalid, the init fails, returns NULL, and releases the
31-
# Objective-C object. Since we've created an ObjC instance, when the object
32-
# passes out of scope, Rubicon tries to free it, which segfaults.
33-
# To avoid this, we retain result of the alloc() (overriding the default
34-
# Rubicon behavior of alloc), then release that reference once we're done.
35-
# If the image was created successfully, we temporarily have a reference
36-
# count that is 1 higher than it needs to be; if it fails, we don't end up
37-
# with a stray release.
38-
image = NSImage.alloc().retain()
39-
if path:
40-
self.native = image.initWithContentsOfFile(str(path))
41-
if self.native is None:
42-
raise ValueError(f"Unable to load image from {path}")
43-
else:
44-
self._needs_release = True
45-
elif data:
46-
nsdata = NSData.dataWithBytes(data, length=len(data))
47-
self.native = image.initWithData(nsdata)
48-
if self.native is None:
49-
raise ValueError("Unable to load image from data")
50-
else:
51-
self._needs_release = True
52-
else:
53-
self.native = raw
54-
finally:
55-
# Calling `release` here disabled Rubicon's "release on delete" automation.
56-
# We therefore add an explicit `release` call in __del__ if the NSImage was
57-
# initialized successfully.
58-
image.release()
27+
# We *should* be able to do a direct NSImage.alloc.init...(), but if the
28+
# image file is invalid, the init fails, returns NULL, and releases the
29+
# Objective-C object. Since we've created an ObjC instance, when the object
30+
# passes out of scope, Rubicon tries to free it, which segfaults.
31+
# To avoid this, we retain result of the alloc() (overriding the default
32+
# Rubicon behavior of alloc), then release that reference once we're done.
33+
# If the image was created successfully, we temporarily have a reference
34+
# count that is 1 higher than it needs to be; if it fails, we don't end up
35+
# with a stray release.
36+
image = NSImage.alloc().retain()
37+
if path:
38+
self.native = image.initWithContentsOfFile(str(path))
39+
if self.native is None:
40+
raise ValueError(f"Unable to load image from {path}")
41+
elif data:
42+
nsdata = NSData.dataWithBytes(data, length=len(data))
43+
self.native = image.initWithData(nsdata)
44+
if self.native is None:
45+
raise ValueError("Unable to load image from data")
46+
else:
47+
self.native = raw
5948

60-
def __del__(self):
61-
if self._needs_release:
62-
self.native.release()
49+
image.release()
6350

6451
def get_width(self):
6552
return self.native.size.width

0 commit comments

Comments
 (0)