0
# Categories and Method Addition
1
2
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.
3
4
## Capabilities
5
6
### Class Method Addition
7
8
Functions for dynamically adding methods to existing Objective-C classes.
9
10
```python { .api }
11
def classAddMethod(targetClass, method, targetMethod):
12
"""
13
Add a method to an existing Objective-C class.
14
15
Args:
16
targetClass: The Objective-C class to modify
17
method: The method selector to add
18
targetMethod: The implementation function to add
19
20
Usage:
21
def new_method_impl(self, _cmd):
22
return "Hello from new method!"
23
24
objc.classAddMethod(
25
NSString,
26
"newMethod",
27
new_method_impl
28
)
29
"""
30
```
31
32
### Category Decorator
33
34
Decorator for creating Objective-C categories from Python classes.
35
36
```python { .api }
37
def category(baseClass):
38
"""
39
Decorator to create an Objective-C category from a Python class.
40
41
Args:
42
baseClass: The Objective-C class to extend with category methods
43
44
Returns:
45
Decorator function that creates the category
46
47
Usage:
48
@objc.category(NSString)
49
class NSStringExtensions:
50
def isPythonic(self):
51
return self.hasPrefix_("py") or self.hasSuffix_(".py")
52
53
@objc.signature(b'@@:@')
54
def stringByAppendingPythonSuffix_(self, suffix):
55
return self.stringByAppendingFormat_("_py_%@", suffix)
56
"""
57
```
58
59
### Category Implementation
60
61
Core category implementation class for managing method injection.
62
63
```python { .api }
64
class Category:
65
"""
66
Core implementation class for Objective-C categories.
67
68
Manages the process of injecting methods from Python classes
69
into existing Objective-C classes, handling method signatures,
70
type conversion, and proper integration with the Objective-C runtime.
71
"""
72
73
def __init__(self, baseClass):
74
"""
75
Initialize a category for the specified base class.
76
77
Args:
78
baseClass: The Objective-C class to extend
79
"""
80
81
def addMethod(self, method_name: str, implementation):
82
"""
83
Add a method to the category.
84
85
Args:
86
method_name (str): Name of the method to add
87
implementation: Python function implementing the method
88
"""
89
90
def inject(self):
91
"""
92
Inject all category methods into the target class.
93
94
This method performs the actual modification of the Objective-C
95
class, adding all methods defined in the category.
96
"""
97
```
98
99
## Usage Examples
100
101
### Adding Methods to NSString
102
103
```python
104
import objc
105
from Foundation import NSString
106
107
@objc.category(NSString)
108
class NSStringPythonExtensions:
109
110
def isPythonic(self):
111
"""Check if string contains Python-related keywords."""
112
return (self.hasPrefix_("py") or
113
self.hasSuffix_(".py") or
114
self.containsString_("python"))
115
116
@objc.signature(b'@@:@')
117
def stringByAppendingPythonSuffix_(self, suffix):
118
"""Append a Python-style suffix to the string."""
119
return self.stringByAppendingFormat_("_py_%@", suffix)
120
121
@objc.signature(b'@@:')
122
def pythonize(self):
123
"""Convert the string to a Python-friendly format."""
124
return self.lowercaseString().stringByReplacingOccurrencesOfString_withString_(
125
" ", "_"
126
)
127
128
# Usage after category definition
129
test_string = NSString.stringWithString_("Hello World")
130
print(test_string.isPythonic()) # False
131
print(test_string.pythonize()) # "hello_world"
132
133
py_string = NSString.stringWithString_("python_script.py")
134
print(py_string.isPythonic()) # True
135
print(py_string.stringByAppendingPythonSuffix_("backup")) # "python_script.py_py_backup"
136
```
137
138
### Adding Methods to NSArray
139
140
```python
141
import objc
142
from Foundation import NSArray, NSMutableArray
143
144
@objc.category(NSArray)
145
class NSArrayPythonExtensions:
146
147
def pythonList(self):
148
"""Convert NSArray to Python list."""
149
result = []
150
for i in range(self.count()):
151
result.append(self.objectAtIndex_(i))
152
return result
153
154
@objc.signature(b'@@:@')
155
def arrayByApplyingBlock_(self, block):
156
"""Apply a block to each element and return new array."""
157
result = NSMutableArray.alloc().init()
158
for i in range(self.count()):
159
item = self.objectAtIndex_(i)
160
transformed = block(item)
161
result.addObject_(transformed)
162
return result.copy()
163
164
def isEmpty(self):
165
"""Check if array is empty."""
166
return self.count() == 0
167
168
# Usage
169
array = NSArray.arrayWithObjects_("apple", "banana", "cherry", None)
170
print(array.pythonList()) # ['apple', 'banana', 'cherry']
171
print(array.isEmpty()) # False
172
173
# Transform array elements
174
upper_array = array.arrayByApplyingBlock_(lambda x: x.uppercaseString())
175
print(upper_array.pythonList()) # ['APPLE', 'BANANA', 'CHERRY']
176
```
177
178
### Adding Methods to Custom Classes
179
180
```python
181
import objc
182
from Foundation import NSObject
183
184
# Define a simple custom class
185
class MyCustomClass(NSObject):
186
def init(self):
187
self = objc.super(MyCustomClass, self).init()
188
if self is None:
189
return None
190
self._data = []
191
return self
192
193
# Create category to extend it
194
@objc.category(MyCustomClass)
195
class MyCustomClassExtensions:
196
197
def addItem_(self, item):
198
"""Add an item to the internal data storage."""
199
self._data.append(item)
200
201
def getItemCount(self):
202
"""Get the number of items stored."""
203
return len(self._data)
204
205
@objc.signature(b'@@:i')
206
def getItemAtIndex_(self, index):
207
"""Get item at specific index."""
208
if 0 <= index < len(self._data):
209
return self._data[index]
210
return None
211
212
def clearAllItems(self):
213
"""Remove all items from storage."""
214
self._data.clear()
215
216
# Usage
217
obj = MyCustomClass.alloc().init()
218
obj.addItem_("First item")
219
obj.addItem_("Second item")
220
print(obj.getItemCount()) # 2
221
print(obj.getItemAtIndex_(0)) # "First item"
222
obj.clearAllItems()
223
print(obj.getItemCount()) # 0
224
```
225
226
### Low-level Method Addition
227
228
```python
229
import objc
230
from Foundation import NSString
231
232
def custom_reverse_method(self, _cmd):
233
"""Custom method implementation that reverses the string."""
234
# Get the string content
235
original = str(self)
236
# Reverse it
237
reversed_str = original[::-1]
238
# Return as NSString
239
return NSString.stringWithString_(reversed_str)
240
241
# Add method directly to NSString class
242
objc.classAddMethod(
243
NSString,
244
"reverseString",
245
custom_reverse_method
246
)
247
248
# Usage
249
test_string = NSString.stringWithString_("Hello World")
250
reversed_string = test_string.reverseString()
251
print(reversed_string) # "dlroW olleH"
252
```
253
254
### Category with Complex Method Signatures
255
256
```python
257
import objc
258
from Foundation import NSString, NSArray
259
260
@objc.category(NSString)
261
class NSStringAdvancedExtensions:
262
263
@objc.signature(b'@@:@@i')
264
def stringByReplacingRange_withString_options_(self, range_dict, replacement, options):
265
"""
266
Replace a range of characters with new string and options.
267
268
Args:
269
range_dict: Dictionary with 'location' and 'length' keys
270
replacement: String to insert
271
options: Replacement options
272
"""
273
location = range_dict.get('location', 0)
274
length = range_dict.get('length', 0)
275
276
# Create NSRange equivalent
277
before = self.substringToIndex_(location)
278
after = self.substringFromIndex_(location + length)
279
280
return before.stringByAppendingString_(
281
replacement.stringByAppendingString_(after)
282
)
283
284
@objc.signature(b'@@:@')
285
def componentsSeparatedByMultipleDelimiters_(self, delimiters_array):
286
"""
287
Split string by multiple delimiters.
288
289
Args:
290
delimiters_array: NSArray of delimiter strings
291
"""
292
result = NSArray.arrayWithObject_(self)
293
294
for i in range(delimiters_array.count()):
295
delimiter = delimiters_array.objectAtIndex_(i)
296
new_result = []
297
298
for j in range(result.count()):
299
string_part = result.objectAtIndex_(j)
300
components = string_part.componentsSeparatedByString_(delimiter)
301
for k in range(components.count()):
302
component = components.objectAtIndex_(k)
303
if component.length() > 0:
304
new_result.append(component)
305
306
result = NSArray.arrayWithArray_(new_result)
307
308
return result
309
310
# Usage
311
text = NSString.stringWithString_("apple,banana;cherry:date")
312
delimiters = NSArray.arrayWithObjects_(",", ";", ":", None)
313
components = text.componentsSeparatedByMultipleDelimiters_(delimiters)
314
315
print("Components:")
316
for i in range(components.count()):
317
print(f" {components.objectAtIndex_(i)}")
318
# Output:
319
# apple
320
# banana
321
# cherry
322
# date
323
```
324
325
## Best Practices
326
327
### Method Naming
328
329
- Follow Objective-C naming conventions (camelCase)
330
- Use descriptive names that indicate the method's purpose
331
- For methods that take parameters, end with an underscore
332
333
### Type Signatures
334
335
- Always provide type signatures for methods with complex parameters
336
- Use `@objc.signature()` decorator for explicit type information
337
- Test method signatures thoroughly to ensure proper bridge behavior
338
339
### Memory Management
340
341
- Categories inherit the memory management rules of their target class
342
- Be careful with object retention and autorelease in category methods
343
- Use `objc.autorelease_pool()` for methods that create many temporary objects
344
345
### Error Handling
346
347
- Categories should handle errors gracefully
348
- Don't assume the target class's internal state
349
- Validate parameters before using them in category methods