Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/2978.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update to rubicon-objc v0.5.0.
2 changes: 1 addition & 1 deletion cocoa/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ root = ".."
[tool.setuptools_dynamic_dependencies]
dependencies = [
"fonttools >= 4.42.1, < 5.0.0",
"rubicon-objc >= 0.4.9, < 0.5.0",
"rubicon-objc @ git+https://github.com/samschott/rubicon-objc@updated-memory-management",
"toga-core == {version}",
]

Expand Down
3 changes: 1 addition & 2 deletions cocoa/src/toga_cocoa/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ def native_color(c):
try:
color = CACHE[c]
except KeyError:
# Color needs to be retained to be kept in cache.
color = NSColor.colorWithRed(
c.rgba.r / 255, green=c.rgba.g / 255, blue=c.rgba.b / 255, alpha=c.rgba.a
).retain()
)
CACHE[c] = color

return color
13 changes: 4 additions & 9 deletions cocoa/src/toga_cocoa/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ def _remove_constraints(self):
self.container.native.removeConstraint(self.left_constraint)
self.container.native.removeConstraint(self.top_constraint)

self.width_constraint.release()
self.height_constraint.release()
self.left_constraint.release()
self.top_constraint.release()

@property
def container(self):
return self._container
Expand All @@ -70,7 +65,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeLeft,
multiplier=1.0,
constant=10, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.left_constraint)

self.top_constraint = NSLayoutConstraint.constraintWithItem(
Expand All @@ -81,7 +76,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeTop,
multiplier=1.0,
constant=5, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.top_constraint)

self.width_constraint = NSLayoutConstraint.constraintWithItem(
Expand All @@ -92,7 +87,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeLeft,
multiplier=1.0,
constant=50, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.width_constraint)

self.height_constraint = NSLayoutConstraint.constraintWithItem(
Expand All @@ -103,7 +98,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeTop,
multiplier=1.0,
constant=30, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.height_constraint)

def update(self, x, y, width, height):
Expand Down
6 changes: 2 additions & 4 deletions cocoa/src/toga_cocoa/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(
attribute__2=NSLayoutAttributeLeft,
multiplier=1.0,
constant=min_width,
).retain()
)
self.native.addConstraint(self._min_width_constraint)

self._min_height_constraint = NSLayoutConstraint.constraintWithItem(
Expand All @@ -78,12 +78,10 @@ def __init__(
attribute__2=NSLayoutAttributeTop,
multiplier=1.0,
constant=min_height,
).retain()
)
self.native.addConstraint(self._min_height_constraint)

def __del__(self):
self._min_height_constraint.release()
self._min_width_constraint.release()
self.native = None

@property
Expand Down
2 changes: 1 addition & 1 deletion cocoa/src/toga_cocoa/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,6 @@ def __init__(self, interface):
else:
attributed_font = font

_FONT_CACHE[self.interface] = attributed_font.retain()
_FONT_CACHE[self.interface] = attributed_font

self.native = attributed_font
26 changes: 3 additions & 23 deletions cocoa/src/toga_cocoa/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,9 @@ def __init__(self, interface, path):
else:
self.path = path

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

def __del__(self):
if self.native:
self.native.release()
self.native = NSImage.alloc().initWithContentsOfFile(str(path))
if self.native is None:
raise ValueError(f"Unable to load icon from {path}")

def _as_size(self, size):
image = self.native.copy()
Expand Down
47 changes: 11 additions & 36 deletions cocoa/src/toga_cocoa/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,43 +23,18 @@ class Image:

def __init__(self, interface, path=None, data=None, raw=None):
self.interface = interface
self._needs_release = False

try:
# We *should* be able to do a direct NSImage.alloc.init...(), but if the
# image file is invalid, the init fails, returns NULL, and releases the
# Objective-C object. Since we've created an ObjC instance, when the object
# passes out of scope, Rubicon tries to free it, which segfaults.
# To avoid this, we retain result of the alloc() (overriding the default
# Rubicon behavior of alloc), then release that reference once we're done.
# If the image was created successfully, we temporarily have a reference
# count that is 1 higher than it needs to be; if it fails, we don't end up
# with a stray release.
image = NSImage.alloc().retain()
if path:
self.native = image.initWithContentsOfFile(str(path))
if self.native is None:
raise ValueError(f"Unable to load image from {path}")
else:
self._needs_release = True
elif data:
nsdata = NSData.dataWithBytes(data, length=len(data))
self.native = image.initWithData(nsdata)
if self.native is None:
raise ValueError("Unable to load image from data")
else:
self._needs_release = True
else:
self.native = raw
finally:
# Calling `release` here disabled Rubicon's "release on delete" automation.
# We therefore add an explicit `release` call in __del__ if the NSImage was
# initialized successfully.
image.release()

def __del__(self):
if self._needs_release:
self.native.release()
if path:
self.native = NSImage.alloc().initWithContentsOfFile(str(path))
if self.native is None:
raise ValueError(f"Unable to load image from {path}")
elif data:
nsdata = NSData.dataWithBytes(data, length=len(data))
self.native = NSImage.alloc().initWithData(nsdata)
if self.native is None:
raise ValueError("Unable to load image from data")
else:
self.native = raw

def get_width(self):
return self.native.size.width
Expand Down
3 changes: 1 addition & 2 deletions cocoa/src/toga_cocoa/statusicons.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@ def set_icon(self, icon):
def create(self):
self.native = NSStatusBar.systemStatusBar.statusItemWithLength(
NSSquareStatusItemLength
).retain()
)
self.native.button.toolTip = self.interface.text
self.set_icon(self.interface.icon)

def remove(self):
NSStatusBar.systemStatusBar.removeStatusItem(self.native)
self.native.release()
self.native = None


Expand Down
2 changes: 1 addition & 1 deletion cocoa/src/toga_cocoa/widgets/detailedlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def menuForEvent_(self, event):
)

# Create a popup menu to display the possible actions.
popup = NSMenu.alloc().initWithTitle("popup").autorelease()
popup = NSMenu.alloc().initWithTitle("popup")
if self.impl.primary_action_enabled:
primary_action_item = popup.addItemWithTitle(
self.interface._primary_action,
Expand Down
5 changes: 4 additions & 1 deletion cocoa/src/toga_cocoa/widgets/internal/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class TogaData(NSObject):
@objc_method
def copyWithZone_(self):
# TogaData is used as an immutable reference to a row
# so the same object can be returned as a copy.
# so the same object can be returned as a copy. We need
# to manually `retain` the object before returning because
# the "copy" methods are assumed to return an object that
# is owned by the caller.
self.retain()
return self
4 changes: 0 additions & 4 deletions cocoa/src/toga_cocoa/widgets/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ def tableView_viewForTableColumn_row_(self, table, column, row: int):
tcv = TogaIconView.alloc().init()
tcv.identifier = identifier

# Prevent tcv from being deallocated prematurely when no Python references
# are left
tcv.autorelease()

tcv.setText(str(value))
if icon:
tcv.setImage(icon._impl.native)
Expand Down
4 changes: 0 additions & 4 deletions cocoa/src/toga_cocoa/widgets/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,6 @@ def outlineView_viewForTableColumn_item_(self, tree, column, item):
tcv = TogaIconView.alloc().init()
tcv.identifier = identifier

# Prevent tcv from being deallocated prematurely when no Python references
# are left
tcv.autorelease()

tcv.setText(str(value))
if icon:
tcv.setImage(icon._impl.native)
Expand Down
15 changes: 3 additions & 12 deletions cocoa/src/toga_cocoa/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(
except KeyError: # Separator items
pass

# Prevent the toolbar item from being deallocated when
# no Python references remain
native.retain()
native.autorelease()
return native

@objc_method
Expand Down Expand Up @@ -162,9 +158,9 @@ def __init__(self, interface, title, position, size):

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

self.set_title(title)
self.set_size(size)
Expand All @@ -179,9 +175,6 @@ def __init__(self, interface, title, position, size):
self.native.wantsLayer = True
self.container.native.backgroundColor = self.native.backgroundColor

def __del__(self):
self.native.release()

######################################################################
# Window properties
######################################################################
Expand Down Expand Up @@ -326,7 +319,6 @@ def __init__(self, interface, title, position, size):

def __del__(self):
self.purge_toolbar()
super().__del__()

def create_menus(self):
# macOS doesn't have window-level menus
Expand Down Expand Up @@ -373,4 +365,3 @@ def purge_toolbar(self):

for item_native in dead_items:
cmd._impl.native.remove(item_native)
item_native.release()
2 changes: 1 addition & 1 deletion iOS/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ root = ".."
[tool.setuptools_dynamic_dependencies]
dependencies = [
"fonttools >= 4.42.1, < 5.0.0",
"rubicon-objc >= 0.4.9, < 0.5.0",
"rubicon-objc @ git+https://github.com/samschott/rubicon-objc@updated-memory-management",
"toga-core == {version}",
]

Expand Down
3 changes: 1 addition & 2 deletions iOS/src/toga_iOS/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ def native_color(c):
try:
color = CACHE[c]
except KeyError:
# Color needs to be retained to be kept in the cache
color = UIColor.colorWithRed(
c.rgba.r / 255, green=c.rgba.g / 255, blue=c.rgba.b / 255, alpha=c.rgba.a
).retain()
)
CACHE[c] = color

return color
13 changes: 4 additions & 9 deletions iOS/src/toga_iOS/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ def _remove_constraints(self):
self.container.native.removeConstraint(self.left_constraint)
self.container.native.removeConstraint(self.top_constraint)

self.width_constraint.release()
self.height_constraint.release()
self.left_constraint.release()
self.top_constraint.release()

@property
def container(self):
return self._container
Expand All @@ -65,7 +60,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeLeft,
multiplier=1.0,
constant=10, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.left_constraint)

self.top_constraint = NSLayoutConstraint.constraintWithItem(
Expand All @@ -76,7 +71,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeTop,
multiplier=1.0,
constant=5, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.top_constraint)

self.width_constraint = NSLayoutConstraint.constraintWithItem(
Expand All @@ -87,7 +82,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeLeft,
multiplier=1.0,
constant=50, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.width_constraint)

self.height_constraint = NSLayoutConstraint.constraintWithItem(
Expand All @@ -98,7 +93,7 @@ def container(self, value):
attribute__2=NSLayoutAttributeTop,
multiplier=1.0,
constant=30, # Use a dummy, non-zero value for now
).retain()
)
self.container.native.addConstraint(self.height_constraint)

def update(self, x, y, width, height):
Expand Down
2 changes: 1 addition & 1 deletion iOS/src/toga_iOS/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, title, message):

self.native = UIAlertController.alertControllerWithTitle(
title, message=message, preferredStyle=UIAlertControllerStyle.Alert
).retain()
)

self.populate_dialog()

Expand Down
2 changes: 1 addition & 1 deletion iOS/src/toga_iOS/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,6 @@ def __init__(self, interface):
else:
attributed_font = font

_FONT_CACHE[self.interface] = attributed_font.retain()
_FONT_CACHE[self.interface] = attributed_font

self.native = attributed_font
8 changes: 0 additions & 8 deletions iOS/src/toga_iOS/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ def __init__(self, interface, path):
if self.native is None:
raise ValueError(f"Unable to load icon from {path}")

# Multiple icon interface instances can end up referencing the same native
# instance, so make sure we retain a reference count at the impl level.
self.native.retain()

def __del__(self):
if self.native:
self.native.release()

def _as_size(self, size):
renderer = UIGraphicsImageRenderer.alloc().initWithSize(NSSize(size, size))

Expand Down
Loading