Skip to content

Commit 83d8744

Browse files
authored
Rubicon update (#2978)
Clean up memory handling following the memory referencing cleanup in Rubicon 0.5.0.
1 parent 682b064 commit 83d8744

File tree

22 files changed

+43
-132
lines changed

22 files changed

+43
-132
lines changed

changes/2978.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update to rubicon-objc v0.5.0.

cocoa/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ root = ".."
6262
[tool.setuptools_dynamic_dependencies]
6363
dependencies = [
6464
"fonttools >= 4.42.1, < 5.0.0",
65-
"rubicon-objc >= 0.4.9, < 0.5.0",
65+
"rubicon-objc >= 0.5.0, < 0.6.0",
6666
"toga-core == {version}",
6767
]
6868

cocoa/src/toga_cocoa/colors.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ def native_color(c):
1010
try:
1111
color = CACHE[c]
1212
except KeyError:
13-
# Color needs to be retained to be kept in cache.
1413
color = NSColor.colorWithRed(
1514
c.rgba.r / 255, green=c.rgba.g / 255, blue=c.rgba.b / 255, alpha=c.rgba.a
16-
).retain()
15+
)
1716
CACHE[c] = color
1817

1918
return color

cocoa/src/toga_cocoa/constraints.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ def _remove_constraints(self):
4444
self.container.native.removeConstraint(self.left_constraint)
4545
self.container.native.removeConstraint(self.top_constraint)
4646

47-
self.width_constraint.release()
48-
self.height_constraint.release()
49-
self.left_constraint.release()
50-
self.top_constraint.release()
51-
5247
@property
5348
def container(self):
5449
return self._container
@@ -73,7 +68,7 @@ def container(self, value):
7368
attribute__2=NSLayoutAttributeLeft,
7469
multiplier=1.0,
7570
constant=10, # Use a dummy, non-zero value for now
76-
).retain()
71+
)
7772
self.container.native.addConstraint(self.left_constraint)
7873

7974
self.top_constraint = NSLayoutConstraint.constraintWithItem(
@@ -84,7 +79,7 @@ def container(self, value):
8479
attribute__2=NSLayoutAttributeTop,
8580
multiplier=1.0,
8681
constant=5, # Use a dummy, non-zero value for now
87-
).retain()
82+
)
8883
self.container.native.addConstraint(self.top_constraint)
8984

9085
self.width_constraint = NSLayoutConstraint.constraintWithItem(
@@ -95,7 +90,7 @@ def container(self, value):
9590
attribute__2=NSLayoutAttributeLeft,
9691
multiplier=1.0,
9792
constant=50, # Use a dummy, non-zero value for now
98-
).retain()
93+
)
9994
self.container.native.addConstraint(self.width_constraint)
10095

10196
self.height_constraint = NSLayoutConstraint.constraintWithItem(
@@ -106,7 +101,7 @@ def container(self, value):
106101
attribute__2=NSLayoutAttributeTop,
107102
multiplier=1.0,
108103
constant=30, # Use a dummy, non-zero value for now
109-
).retain()
104+
)
110105
self.container.native.addConstraint(self.height_constraint)
111106

112107
def update(self, x, y, width, height):

cocoa/src/toga_cocoa/container.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(
6767
attribute__2=NSLayoutAttributeLeft,
6868
multiplier=1.0,
6969
constant=min_width,
70-
).retain()
70+
)
7171
self.native.addConstraint(self._min_width_constraint)
7272

7373
self._min_height_constraint = NSLayoutConstraint.constraintWithItem(
@@ -78,12 +78,10 @@ def __init__(
7878
attribute__2=NSLayoutAttributeTop,
7979
multiplier=1.0,
8080
constant=min_height,
81-
).retain()
81+
)
8282
self.native.addConstraint(self._min_height_constraint)
8383

8484
def __del__(self):
85-
self._min_height_constraint.release()
86-
self._min_width_constraint.release()
8785
self.native = None
8886

8987
@property

cocoa/src/toga_cocoa/fonts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,6 @@ def __init__(self, interface):
124124
else:
125125
attributed_font = font
126126

127-
_FONT_CACHE[self.interface] = attributed_font.retain()
127+
_FONT_CACHE[self.interface] = attributed_font
128128

129129
self.native = attributed_font

cocoa/src/toga_cocoa/icons.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,9 @@ 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+
self.native = NSImage.alloc().initWithContentsOfFile(str(path))
42+
if self.native is None:
43+
raise ValueError(f"Unable to load icon from {path}")
6444

6545
def _as_size(self, size):
6646
image = self.native.copy()

cocoa/src/toga_cocoa/images.py

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,43 +23,18 @@ 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()
59-
60-
def __del__(self):
61-
if self._needs_release:
62-
self.native.release()
27+
if path:
28+
self.native = NSImage.alloc().initWithContentsOfFile(str(path))
29+
if self.native is None:
30+
raise ValueError(f"Unable to load image from {path}")
31+
elif data:
32+
nsdata = NSData.dataWithBytes(data, length=len(data))
33+
self.native = NSImage.alloc().initWithData(nsdata)
34+
if self.native is None:
35+
raise ValueError("Unable to load image from data")
36+
else:
37+
self.native = raw
6338

6439
def get_width(self):
6540
return self.native.size.width

cocoa/src/toga_cocoa/statusicons.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,12 @@ def set_icon(self, icon):
3030
def create(self):
3131
self.native = NSStatusBar.systemStatusBar.statusItemWithLength(
3232
NSSquareStatusItemLength
33-
).retain()
33+
)
3434
self.native.button.toolTip = self.interface.text
3535
self.set_icon(self.interface.icon)
3636

3737
def remove(self):
3838
NSStatusBar.systemStatusBar.removeStatusItem(self.native)
39-
self.native.release()
4039
self.native = None
4140

4241

cocoa/src/toga_cocoa/widgets/detailedlist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def menuForEvent_(self, event):
3737
)
3838

3939
# Create a popup menu to display the possible actions.
40-
popup = NSMenu.alloc().initWithTitle("popup").autorelease()
40+
popup = NSMenu.alloc().initWithTitle("popup")
4141
if self.impl.primary_action_enabled:
4242
primary_action_item = popup.addItemWithTitle(
4343
self.interface._primary_action,

cocoa/src/toga_cocoa/widgets/internal/data.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ class TogaData(NSObject):
55
@objc_method
66
def copyWithZone_(self):
77
# TogaData is used as an immutable reference to a row
8-
# so the same object can be returned as a copy.
8+
# so the same object can be returned as a copy. We need
9+
# to manually `retain` the object before returning because
10+
# the "copy" methods are assumed to return an object that
11+
# is owned by the caller.
912
self.retain()
1013
return self

cocoa/src/toga_cocoa/widgets/table.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@ def tableView_viewForTableColumn_row_(self, table, column, row: int):
7171
tcv = TogaIconView.alloc().init()
7272
tcv.identifier = identifier
7373

74-
# Prevent tcv from being deallocated prematurely when no Python references
75-
# are left
76-
tcv.autorelease()
77-
7874
tcv.setText(str(value))
7975
if icon:
8076
tcv.setImage(icon._impl.native)

cocoa/src/toga_cocoa/widgets/tree.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,6 @@ def outlineView_viewForTableColumn_item_(self, tree, column, item):
101101
tcv = TogaIconView.alloc().init()
102102
tcv.identifier = identifier
103103

104-
# Prevent tcv from being deallocated prematurely when no Python references
105-
# are left
106-
tcv.autorelease()
107-
108104
tcv.setText(str(value))
109105
if icon:
110106
tcv.setImage(icon._impl.native)

cocoa/src/toga_cocoa/window.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,6 @@ def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(
162162
except KeyError: # Separator items
163163
pass
164164

165-
# Prevent the toolbar item from being deallocated when
166-
# no Python references remain
167-
native.retain()
168-
native.autorelease()
169165
return native
170166

171167
@objc_method
@@ -219,9 +215,9 @@ def __init__(self, interface, title, position, size):
219215

220216
# Cocoa releases windows when they are closed; this causes havoc with
221217
# Toga's widget cleanup because the ObjC runtime thinks there's no
222-
# references to the object left. Add a reference that can be released
223-
# in response to the close.
224-
self.native.retain()
218+
# references to the object left. Explicitly prevent this and let Rubicon
219+
# manage the release when no Python references are left.
220+
self.native.releasedWhenClosed = False
225221

226222
# Pending Window state transition variable:
227223
self._pending_state_transition = None
@@ -240,9 +236,6 @@ def __init__(self, interface, title, position, size):
240236
self.native.wantsLayer = True
241237
self.container.native.backgroundColor = self.native.backgroundColor
242238

243-
def __del__(self):
244-
self.native.release()
245-
246239
######################################################################
247240
# Window properties
248241
######################################################################
@@ -511,7 +504,6 @@ def __init__(self, interface, title, position, size):
511504

512505
def __del__(self):
513506
self.purge_toolbar()
514-
super().__del__()
515507

516508
def create_menus(self):
517509
# macOS doesn't have window-level menus
@@ -558,4 +550,3 @@ def purge_toolbar(self):
558550

559551
for item_native in dead_items:
560552
cmd._impl.native.remove(item_native)
561-
item_native.release()

iOS/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ root = ".."
6161
[tool.setuptools_dynamic_dependencies]
6262
dependencies = [
6363
"fonttools >= 4.42.1, < 5.0.0",
64-
"rubicon-objc >= 0.4.9, < 0.5.0",
64+
"rubicon-objc >= 0.5.0, < 0.6.0",
6565
"toga-core == {version}",
6666
]
6767

iOS/src/toga_iOS/colors.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ def native_color(c):
1111
try:
1212
color = CACHE[c]
1313
except KeyError:
14-
# Color needs to be retained to be kept in the cache
1514
color = UIColor.colorWithRed(
1615
c.rgba.r / 255, green=c.rgba.g / 255, blue=c.rgba.b / 255, alpha=c.rgba.a
17-
).retain()
16+
)
1817
CACHE[c] = color
1918

2019
return color

iOS/src/toga_iOS/constraints.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ def _remove_constraints(self):
3939
self.container.native.removeConstraint(self.left_constraint)
4040
self.container.native.removeConstraint(self.top_constraint)
4141

42-
self.width_constraint.release()
43-
self.height_constraint.release()
44-
self.left_constraint.release()
45-
self.top_constraint.release()
46-
4742
@property
4843
def container(self):
4944
return self._container
@@ -68,7 +63,7 @@ def container(self, value):
6863
attribute__2=NSLayoutAttributeLeft,
6964
multiplier=1.0,
7065
constant=10, # Use a dummy, non-zero value for now
71-
).retain()
66+
)
7267
self.container.native.addConstraint(self.left_constraint)
7368

7469
self.top_constraint = NSLayoutConstraint.constraintWithItem(
@@ -79,7 +74,7 @@ def container(self, value):
7974
attribute__2=NSLayoutAttributeTop,
8075
multiplier=1.0,
8176
constant=5, # Use a dummy, non-zero value for now
82-
).retain()
77+
)
8378
self.container.native.addConstraint(self.top_constraint)
8479

8580
self.width_constraint = NSLayoutConstraint.constraintWithItem(
@@ -90,7 +85,7 @@ def container(self, value):
9085
attribute__2=NSLayoutAttributeLeft,
9186
multiplier=1.0,
9287
constant=50, # Use a dummy, non-zero value for now
93-
).retain()
88+
)
9489
self.container.native.addConstraint(self.width_constraint)
9590

9691
self.height_constraint = NSLayoutConstraint.constraintWithItem(
@@ -101,7 +96,7 @@ def container(self, value):
10196
attribute__2=NSLayoutAttributeTop,
10297
multiplier=1.0,
10398
constant=30, # Use a dummy, non-zero value for now
104-
).retain()
99+
)
105100
self.container.native.addConstraint(self.height_constraint)
106101

107102
def update(self, x, y, width, height):

iOS/src/toga_iOS/dialogs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self, title, message):
3636

3737
self.native = UIAlertController.alertControllerWithTitle(
3838
title, message=message, preferredStyle=UIAlertControllerStyle.Alert
39-
).retain()
39+
)
4040

4141
self.populate_dialog()
4242

iOS/src/toga_iOS/fonts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,6 @@ def __init__(self, interface):
123123
else:
124124
attributed_font = font
125125

126-
_FONT_CACHE[self.interface] = attributed_font.retain()
126+
_FONT_CACHE[self.interface] = attributed_font
127127

128128
self.native = attributed_font

iOS/src/toga_iOS/icons.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ def __init__(self, interface, path):
2323
if self.native is None:
2424
raise ValueError(f"Unable to load icon from {path}")
2525

26-
# Multiple icon interface instances can end up referencing the same native
27-
# instance, so make sure we retain a reference count at the impl level.
28-
self.native.retain()
29-
30-
def __del__(self):
31-
if self.native:
32-
self.native.release()
33-
3426
def _as_size(self, size):
3527
renderer = UIGraphicsImageRenderer.alloc().initWithSize(NSSize(size, size))
3628

0 commit comments

Comments
 (0)