Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ObjCProtocol class #76

Merged
merged 12 commits into from
Nov 3, 2017
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dist
build
_build
distribute-*
env
local
.tox
*.o
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ tests/objc/librubiconharness.dylib: $(OBJ_FILES)
clean:
rm -rf tests/objc/*.o tests/objc/*.d tests/objc/librubiconharness.dylib

%.o: %.m
%.o: %.m tests/objc/*.h
clang -x objective-c -I./tests/objc -c $(EXTRA_FLAGS) $< -o $@

2 changes: 1 addition & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ spelling:

install:
@echo "... setting up virtualenv"
virtualenv env
virtualenv ../env
. $(VENV); pip install -r requirements_docs.txt
@echo "\n" \
"-------------------------------------------------------------------------------------------------- \n" \
Expand Down
1 change: 1 addition & 0 deletions docs/how-to/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ How-to guides are recipes that take the user through steps in key subjects. They

Get started <get-started>
Mapping Python types to Objective-C <type-mapping>
Using and creating Objective-C protocols <protocols>
Using AsyncIO in your app <async>
Contribute to Toga <contribute>
89 changes: 89 additions & 0 deletions docs/how-to/protocols.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
========================================
Using and creating Objective-C protocols
========================================

Protocols are used in Objective-C to declare a set of methods and properties for a class to implement. They have a similar purpose to ABCs (abstract base classes) in Python.

Looking up a protocol
---------------------

Protocol objects can be looked up using the ``ObjCProtocol`` constructor, similar to how classes can be looked up using ``ObjCClass``:

.. code-block:: python

>>> NSCopying = ObjCProtocol('NSCopying')
>>> NSCopying
<rubicon.objc.runtime.ObjCProtocol: NSCopying at 0x7fff76543210>

A protocol object can be passed to the ``conformsToProtocol:`` method of an Objective-C object, to check whether it conforms to the given protocol:

.. code-block:: python

>>> NSObject.new().conformsToProtocol(NSCopying)
0
>>> NSArray.array().conformsToProtocol(NSCopying)
1

Implementing a protocol
------------------------

When writing a custom Objective-C class, you might want to have it conform to one or multiple protocols. In Rubicon, this is done by using the ``protocols`` keyword argument in the base class list. For example, if you have a class ``UserAccount`` and want it to conform to ``NSCopyable``, you would write it like this:

.. code-block:: python

class UserAccount(NSObject, protocols=[NSCopying]):
username = objc_property()
emailAddress = objc_property()

@objc_method
def initWithUsername_emailAddress_(self, username, emailAddress):
self = self.init()
if self is None:
return None
self.username = username
self.emailAddress = emailAddress
return self

# This method is required by NSCopying.
# The "zone" parameter is obsolete and can be ignored, but must be included for backwards compatibility.
# This method is not normally used directly. Usually you call the copy method instead,
# which calls copyWithZone: internally.
@objc_method
def copyWithZone_(self, zone):
return UserAccount.alloc().initWithUsername(self.username, emailAddress=self.emailAddress)

We can now use our class. The ``copy`` method (which uses our implemented ``copyWithZone:`` method) can also be used:

.. code-block:: python

>>> ua = UserAccount.alloc().initWithUsername_emailAddress_(at('person'), at('[email protected]'))
>>> ua
<rubicon.objc.runtime.ObjCInstance 0x106543210: UserAccount at 0x106543220: <UserAccount: 0x106543220>>
>>> ua.copy()
<rubicon.objc.runtime.ObjCInstance 0x106543210: UserAccount at 0x106543220: <UserAccount: 0x106543220>>

And we can check that the class conforms to the protocol:

.. code-block:: python

>>> ua.conformsToProtocol(NSCopying)
1

Writing custom protocols
------------------------

You can also create custom protocols. This works similarly to creating custom Objective-C classes:

.. code-block:: python

class Named(metaclass=ObjCProtocol):
name = objc_property()

@objc_method
def sayName(self):
...

There are two notable differences between creating classes and protocols:

1. Protocols do not need to extend exactly one other protocol - they can also extend multiple protocols, or none at all. When not extending other protocols, as is the case here, we need to explicitly add ``metaclass=ObjCProtocol`` to the base class list, to tell Python that this is a protocol and not a regular Python class. When extending other protocols, Python detects this automatically.
2. Protocol methods do not have a body. Python has no dedicated syntax for functions without a body, so we put ``...`` in the body instead. (You could technically put code in the body, but this would be misleading and is not recommended.)
4 changes: 2 additions & 2 deletions rubicon/objc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
to_number, to_set, to_str, to_value,
)
from .runtime import ( # noqa: F401
IMP, SEL, Block, Class, Ivar, Method, NSObject, ObjCBlock, ObjCClass,
ObjCInstance, ObjCMetaClass, objc_classmethod, objc_const, objc_id,
IMP, SEL, Block, Class, Ivar, Method, NSObject, NSObjectProtocol, ObjCBlock, ObjCClass,
ObjCInstance, ObjCMetaClass, ObjCProtocol, objc_classmethod, objc_const, objc_id,
objc_ivar, objc_method, objc_property, objc_property_t, objc_rawmethod,
send_message, send_super,
)
Expand Down
Loading