diff --git a/changes/2978.misc.rst b/changes/2978.misc.rst new file mode 100644 index 0000000000..6646b7227d --- /dev/null +++ b/changes/2978.misc.rst @@ -0,0 +1 @@ +Update to rubicon-objc v0.5.0. diff --git a/cocoa/pyproject.toml b/cocoa/pyproject.toml index 1f12c8c313..1743ca6f9f 100644 --- a/cocoa/pyproject.toml +++ b/cocoa/pyproject.toml @@ -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@method-loading", "toga-core == {version}", ] diff --git a/cocoa/src/toga_cocoa/colors.py b/cocoa/src/toga_cocoa/colors.py index e204d36cfa..80c86195d2 100644 --- a/cocoa/src/toga_cocoa/colors.py +++ b/cocoa/src/toga_cocoa/colors.py @@ -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 diff --git a/cocoa/src/toga_cocoa/constraints.py b/cocoa/src/toga_cocoa/constraints.py index ca635a6301..6c545c4a30 100644 --- a/cocoa/src/toga_cocoa/constraints.py +++ b/cocoa/src/toga_cocoa/constraints.py @@ -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 @@ -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( @@ -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( @@ -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( @@ -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): diff --git a/cocoa/src/toga_cocoa/container.py b/cocoa/src/toga_cocoa/container.py index 8979114082..d855cbbbbe 100644 --- a/cocoa/src/toga_cocoa/container.py +++ b/cocoa/src/toga_cocoa/container.py @@ -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( @@ -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 diff --git a/cocoa/src/toga_cocoa/fonts.py b/cocoa/src/toga_cocoa/fonts.py index a8a902ab64..d6fa949b47 100644 --- a/cocoa/src/toga_cocoa/fonts.py +++ b/cocoa/src/toga_cocoa/fonts.py @@ -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 diff --git a/cocoa/src/toga_cocoa/icons.py b/cocoa/src/toga_cocoa/icons.py index da25d207e3..59dc2bb5aa 100644 --- a/cocoa/src/toga_cocoa/icons.py +++ b/cocoa/src/toga_cocoa/icons.py @@ -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() diff --git a/cocoa/src/toga_cocoa/images.py b/cocoa/src/toga_cocoa/images.py index 687a5e8dff..f3dd0565bf 100644 --- a/cocoa/src/toga_cocoa/images.py +++ b/cocoa/src/toga_cocoa/images.py @@ -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 diff --git a/cocoa/src/toga_cocoa/statusicons.py b/cocoa/src/toga_cocoa/statusicons.py index 3327c38139..688f4fc88d 100644 --- a/cocoa/src/toga_cocoa/statusicons.py +++ b/cocoa/src/toga_cocoa/statusicons.py @@ -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 diff --git a/cocoa/src/toga_cocoa/widgets/detailedlist.py b/cocoa/src/toga_cocoa/widgets/detailedlist.py index 8e007deee0..d1a17875ca 100644 --- a/cocoa/src/toga_cocoa/widgets/detailedlist.py +++ b/cocoa/src/toga_cocoa/widgets/detailedlist.py @@ -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, diff --git a/cocoa/src/toga_cocoa/widgets/internal/data.py b/cocoa/src/toga_cocoa/widgets/internal/data.py index b474d0e3a9..bbb05a64a2 100644 --- a/cocoa/src/toga_cocoa/widgets/internal/data.py +++ b/cocoa/src/toga_cocoa/widgets/internal/data.py @@ -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 diff --git a/cocoa/src/toga_cocoa/widgets/table.py b/cocoa/src/toga_cocoa/widgets/table.py index 635cf6e55e..c99cbde8ef 100644 --- a/cocoa/src/toga_cocoa/widgets/table.py +++ b/cocoa/src/toga_cocoa/widgets/table.py @@ -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) diff --git a/cocoa/src/toga_cocoa/widgets/tree.py b/cocoa/src/toga_cocoa/widgets/tree.py index 889481904b..48d723b6b0 100644 --- a/cocoa/src/toga_cocoa/widgets/tree.py +++ b/cocoa/src/toga_cocoa/widgets/tree.py @@ -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) diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index 5a0604c7d6..58086c16c8 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -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 @@ -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) @@ -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 ###################################################################### @@ -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 @@ -373,4 +365,3 @@ def purge_toolbar(self): for item_native in dead_items: cmd._impl.native.remove(item_native) - item_native.release() diff --git a/iOS/pyproject.toml b/iOS/pyproject.toml index 78c9a3e176..18f29af2f1 100644 --- a/iOS/pyproject.toml +++ b/iOS/pyproject.toml @@ -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@method-loading", "toga-core == {version}", ] diff --git a/iOS/src/toga_iOS/colors.py b/iOS/src/toga_iOS/colors.py index 25d80f3360..847233de66 100644 --- a/iOS/src/toga_iOS/colors.py +++ b/iOS/src/toga_iOS/colors.py @@ -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 diff --git a/iOS/src/toga_iOS/constraints.py b/iOS/src/toga_iOS/constraints.py index dc3e4df076..9d8da1d57e 100644 --- a/iOS/src/toga_iOS/constraints.py +++ b/iOS/src/toga_iOS/constraints.py @@ -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 @@ -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( @@ -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( @@ -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( @@ -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): diff --git a/iOS/src/toga_iOS/dialogs.py b/iOS/src/toga_iOS/dialogs.py index c2c97d6af3..13a043965e 100644 --- a/iOS/src/toga_iOS/dialogs.py +++ b/iOS/src/toga_iOS/dialogs.py @@ -34,7 +34,7 @@ def __init__(self, title, message): self.native = UIAlertController.alertControllerWithTitle( title, message=message, preferredStyle=UIAlertControllerStyle.Alert - ).retain() + ) self.populate_dialog() diff --git a/iOS/src/toga_iOS/fonts.py b/iOS/src/toga_iOS/fonts.py index e34dfa830d..fa9585b898 100644 --- a/iOS/src/toga_iOS/fonts.py +++ b/iOS/src/toga_iOS/fonts.py @@ -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 diff --git a/iOS/src/toga_iOS/icons.py b/iOS/src/toga_iOS/icons.py index 25a7b3e83f..53611e8869 100644 --- a/iOS/src/toga_iOS/icons.py +++ b/iOS/src/toga_iOS/icons.py @@ -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)) diff --git a/iOS/src/toga_iOS/images.py b/iOS/src/toga_iOS/images.py index 5ab906ecf0..99872079d3 100644 --- a/iOS/src/toga_iOS/images.py +++ b/iOS/src/toga_iOS/images.py @@ -36,12 +36,6 @@ def __init__(self, interface, path=None, data=None, raw=None): else: self.native = raw - self.native.retain() - - def __del__(self): - if self.native: - self.native.release() - def get_width(self): return self.native.size.width diff --git a/iOS/src/toga_iOS/widgets/detailedlist.py b/iOS/src/toga_iOS/widgets/detailedlist.py index c124c65068..5dd9b8a51a 100644 --- a/iOS/src/toga_iOS/widgets/detailedlist.py +++ b/iOS/src/toga_iOS/widgets/detailedlist.py @@ -39,10 +39,8 @@ def tableView_numberOfRowsInSection_(self, tableView, section: int) -> int: def tableView_cellForRowAtIndexPath_(self, tableView, indexPath): cell = tableView.dequeueReusableCellWithIdentifier("row") if cell is None: - cell = ( - UITableViewCell.alloc() - .initWithStyle(UITableViewCellStyleSubtitle, reuseIdentifier="row") - .autorelease() + cell = UITableViewCell.alloc().initWithStyle( + UITableViewCellStyleSubtitle, reuseIdentifier="row" ) value = self.interface.data[indexPath.item]