PyObjC is a bridge between Python and Objective-C that allows full featured Cocoa applications to be written in pure Python.
—
Category support for extending existing Objective-C classes with new methods. Categories allow adding functionality to existing classes without subclassing or modifying the original class definition, providing a powerful mechanism for extending system frameworks and third-party classes.
Functions for dynamically adding methods to existing Objective-C classes.
def classAddMethod(targetClass, method, targetMethod):
"""
Add a method to an existing Objective-C class.
Args:
targetClass: The Objective-C class to modify
method: The method selector to add
targetMethod: The implementation function to add
Usage:
def new_method_impl(self, _cmd):
return "Hello from new method!"
objc.classAddMethod(
NSString,
"newMethod",
new_method_impl
)
"""Decorator for creating Objective-C categories from Python classes.
def category(baseClass):
"""
Decorator to create an Objective-C category from a Python class.
Args:
baseClass: The Objective-C class to extend with category methods
Returns:
Decorator function that creates the category
Usage:
@objc.category(NSString)
class NSStringExtensions:
def isPythonic(self):
return self.hasPrefix_("py") or self.hasSuffix_(".py")
@objc.signature(b'@@:@')
def stringByAppendingPythonSuffix_(self, suffix):
return self.stringByAppendingFormat_("_py_%@", suffix)
"""Core category implementation class for managing method injection.
class Category:
"""
Core implementation class for Objective-C categories.
Manages the process of injecting methods from Python classes
into existing Objective-C classes, handling method signatures,
type conversion, and proper integration with the Objective-C runtime.
"""
def __init__(self, baseClass):
"""
Initialize a category for the specified base class.
Args:
baseClass: The Objective-C class to extend
"""
def addMethod(self, method_name: str, implementation):
"""
Add a method to the category.
Args:
method_name (str): Name of the method to add
implementation: Python function implementing the method
"""
def inject(self):
"""
Inject all category methods into the target class.
This method performs the actual modification of the Objective-C
class, adding all methods defined in the category.
"""import objc
from Foundation import NSString
@objc.category(NSString)
class NSStringPythonExtensions:
def isPythonic(self):
"""Check if string contains Python-related keywords."""
return (self.hasPrefix_("py") or
self.hasSuffix_(".py") or
self.containsString_("python"))
@objc.signature(b'@@:@')
def stringByAppendingPythonSuffix_(self, suffix):
"""Append a Python-style suffix to the string."""
return self.stringByAppendingFormat_("_py_%@", suffix)
@objc.signature(b'@@:')
def pythonize(self):
"""Convert the string to a Python-friendly format."""
return self.lowercaseString().stringByReplacingOccurrencesOfString_withString_(
" ", "_"
)
# Usage after category definition
test_string = NSString.stringWithString_("Hello World")
print(test_string.isPythonic()) # False
print(test_string.pythonize()) # "hello_world"
py_string = NSString.stringWithString_("python_script.py")
print(py_string.isPythonic()) # True
print(py_string.stringByAppendingPythonSuffix_("backup")) # "python_script.py_py_backup"import objc
from Foundation import NSArray, NSMutableArray
@objc.category(NSArray)
class NSArrayPythonExtensions:
def pythonList(self):
"""Convert NSArray to Python list."""
result = []
for i in range(self.count()):
result.append(self.objectAtIndex_(i))
return result
@objc.signature(b'@@:@')
def arrayByApplyingBlock_(self, block):
"""Apply a block to each element and return new array."""
result = NSMutableArray.alloc().init()
for i in range(self.count()):
item = self.objectAtIndex_(i)
transformed = block(item)
result.addObject_(transformed)
return result.copy()
def isEmpty(self):
"""Check if array is empty."""
return self.count() == 0
# Usage
array = NSArray.arrayWithObjects_("apple", "banana", "cherry", None)
print(array.pythonList()) # ['apple', 'banana', 'cherry']
print(array.isEmpty()) # False
# Transform array elements
upper_array = array.arrayByApplyingBlock_(lambda x: x.uppercaseString())
print(upper_array.pythonList()) # ['APPLE', 'BANANA', 'CHERRY']import objc
from Foundation import NSObject
# Define a simple custom class
class MyCustomClass(NSObject):
def init(self):
self = objc.super(MyCustomClass, self).init()
if self is None:
return None
self._data = []
return self
# Create category to extend it
@objc.category(MyCustomClass)
class MyCustomClassExtensions:
def addItem_(self, item):
"""Add an item to the internal data storage."""
self._data.append(item)
def getItemCount(self):
"""Get the number of items stored."""
return len(self._data)
@objc.signature(b'@@:i')
def getItemAtIndex_(self, index):
"""Get item at specific index."""
if 0 <= index < len(self._data):
return self._data[index]
return None
def clearAllItems(self):
"""Remove all items from storage."""
self._data.clear()
# Usage
obj = MyCustomClass.alloc().init()
obj.addItem_("First item")
obj.addItem_("Second item")
print(obj.getItemCount()) # 2
print(obj.getItemAtIndex_(0)) # "First item"
obj.clearAllItems()
print(obj.getItemCount()) # 0import objc
from Foundation import NSString
def custom_reverse_method(self, _cmd):
"""Custom method implementation that reverses the string."""
# Get the string content
original = str(self)
# Reverse it
reversed_str = original[::-1]
# Return as NSString
return NSString.stringWithString_(reversed_str)
# Add method directly to NSString class
objc.classAddMethod(
NSString,
"reverseString",
custom_reverse_method
)
# Usage
test_string = NSString.stringWithString_("Hello World")
reversed_string = test_string.reverseString()
print(reversed_string) # "dlroW olleH"import objc
from Foundation import NSString, NSArray
@objc.category(NSString)
class NSStringAdvancedExtensions:
@objc.signature(b'@@:@@i')
def stringByReplacingRange_withString_options_(self, range_dict, replacement, options):
"""
Replace a range of characters with new string and options.
Args:
range_dict: Dictionary with 'location' and 'length' keys
replacement: String to insert
options: Replacement options
"""
location = range_dict.get('location', 0)
length = range_dict.get('length', 0)
# Create NSRange equivalent
before = self.substringToIndex_(location)
after = self.substringFromIndex_(location + length)
return before.stringByAppendingString_(
replacement.stringByAppendingString_(after)
)
@objc.signature(b'@@:@')
def componentsSeparatedByMultipleDelimiters_(self, delimiters_array):
"""
Split string by multiple delimiters.
Args:
delimiters_array: NSArray of delimiter strings
"""
result = NSArray.arrayWithObject_(self)
for i in range(delimiters_array.count()):
delimiter = delimiters_array.objectAtIndex_(i)
new_result = []
for j in range(result.count()):
string_part = result.objectAtIndex_(j)
components = string_part.componentsSeparatedByString_(delimiter)
for k in range(components.count()):
component = components.objectAtIndex_(k)
if component.length() > 0:
new_result.append(component)
result = NSArray.arrayWithArray_(new_result)
return result
# Usage
text = NSString.stringWithString_("apple,banana;cherry:date")
delimiters = NSArray.arrayWithObjects_(",", ";", ":", None)
components = text.componentsSeparatedByMultipleDelimiters_(delimiters)
print("Components:")
for i in range(components.count()):
print(f" {components.objectAtIndex_(i)}")
# Output:
# apple
# banana
# cherry
# date@objc.signature() decorator for explicit type informationobjc.autorelease_pool() for methods that create many temporary objectsInstall with Tessl CLI
npx tessl i tessl/pypi-pyobjc