Ronald Oussoren avatar Ronald Oussoren committed 1a1a763

implement support for defining new formal protocols

Comments (0)

Files changed (23)

 a regular basis, we should check if using freelists would speed this up. See
 also `Performance tuning/testing`.
 
-
-Formal Protocols, Distributed Objects
-.....................................
-
-``DO`` seems to use formal protocols, we don't fully support those. There is 
-an equivalent to ``@protocol(Foo)``, and classes can declare that they
-implement formal protocols, but it is not yet possible to define
-a new formal protocol in Python.
-
 Links to Apple documentation
 ............................
 
 objects to add the right method signatures to methods, and to warn about
 classes that partially implement a protocol.
 
+See `PyObjC protocol support`__ for more information.
+
+.. __: protocols.html
+
 Cocoa Bindings
 ..............
 

Doc/protocols.txt

+=======================
+PyObjC protocol support
+=======================
+
+Introduction
+------------
+
+Apple makes use of both formal and informal protocols in the Cocoa framework.
+Formal protocols are those protocols that are implemented using Objective-C
+protocols::
+
+	@protocol NSFoo <NSSomeProtocol>
+	-(int)doesIt;
+	@end
+
+Informal protocols on the other hand are purely documentation, the Objective-C 
+compiler doesn't know about them. For end users the main difference between
+formal and informal protocols is that you have to declare that you implement a
+formal protocol and that formal protocols do not have optional methods.
+
+Informal protocols and PyObjC
+-----------------------------
+
+PyObjC does have an explicit representation for informal protocols. This makes
+it possible to use the protocol description to provide better error messages and
+to automaticly deduce the method signatures for classes that implement an
+informal protocol.
+
+Informal protocols are represented using instances of 
+``objc.informal_protocol``. These instances are automaticly added to a registry,
+therefore it is not necessary to declare that you implement an informal 
+protocol.
+
+Formal protocols and PyObjC
+---------------------------
+
+PyObjC also has an explicit representation for formal protocols. 
+
+Formal protocols are represented as instances of ``objc.formal_protocol``. 
+Unlike informal protocols you have to declare that you implement a formal 
+protocol to get all features of formal protocols. However, all formal protocols
+in Cocoa are also described using ``objc.informal_protocol`` objects.
+
+XXX: is this necessary? we could also use the same strategy as for informal
+protocols, and drop the informal_protocol wrappers for formal protocols.
+
+You can declare that you implement a formal protocol by using the protocol as
+a mix-in::
+
+	class MyLockingObject (NSObject, NSLocking):
+		def lock(self): pass
+		def unlock(self): pass
+
+The class now formally implements the ``NSLocking`` protocol, you can check this
+using the Objective-C introspection methods::
+
+	>>> MyLockingObject.pyobjc_classMethods.conformsToProtocol_(NSLocking)
+	1
+
+This is useful for API's that require (and check) the implementation of formal
+protocols.
+
+XXX: might also be useful for Distributed Objects, create an example
 NEWS.txt,user,1
 intro.txt,user,5
 tutorial.txt,user,12
+protocols.txt,user,13
 extending_objc_with_python.txt,user,13
 tutorial_reading.txt,user,14
 PyObjCTools.txt,user,16

Lib/objc/_protocols.py

         pass
     for cls in _objc.getClassList():
         for p in _objc.protocolsForClass(cls):
-            pname = p.name()
+            pname = p.__name__
             PROTOCOL_CACHE.setdefault(pname, p)
             if pname == name:
                 return p

Lib/objc/test/test_identity.py

         container.setStoredObjectToAProtocol()
         v = container.storedObject()
         self.assert_(container.isSameObjectAsStored_(v), repr(v))
-        self.assert_(isinstance(v, objc.lookUpClass("Protocol")))
+        self.assert_(isinstance(v, objc.formal_protocol))
 
     def testObject(self):
         container = OC_TestIdentity.alloc().init()

Lib/objc/test/test_protocol.py

 import objc
 import warnings
 
+from objc.test.protocol import OC_TestProtocol
+
 # Most useful systems will at least have 'NSObject'.
 NSObject = objc.lookUpClass('NSObject')
 
 MyProto = objc.informal_protocol("MyProto", (
-    objc.selector(None, selector="testMethod", signature="v@:", isRequired=1),
+    objc.selector(None, selector="testMethod", signature="I@:", isRequired=1),
     objc.selector(None, selector="testMethod2:", signature="v@:i", isRequired=0)
 ))
 
         class ProtoClass1 (NSObject):
             def testMethod(self):
                 pass
-        self.assertEquals(ProtoClass1.testMethod.signature, "v@:")
+        self.assertEquals(ProtoClass1.testMethod.signature, "I@:")
 
 
     def doIncompleteClass(self):
             def testMethod(self):
                 pass
 
+
+
+
+EmptyProtocol = objc.formal_protocol("EmptyProtocol", None, ())
+
+MyProtocol = objc.formal_protocol("MyProtocol", None, (
+    objc.selector(None, selector="protoMethod", signature="I@:"),
+    objc.selector(None, selector="anotherProto:with:", signature="v@:ii"),
+))
+
+MyOtherProtocol = objc.formal_protocol("MyOtherProtocol", 
+        (MyProtocol,), [
+            objc.selector(None, selector="yetAnother:", signature="i@:I")
+        ])
+
+MyClassProtocol = objc.formal_protocol("MyClassProtocol", None, [
+    objc.selector(None, selector="anAnotherOne:", signature="i@:i"),
+    objc.selector(None, selector="aClassOne:", signature="@@:i", isClassMethod=1),
+])
+
 class TestFormalProtocols (unittest.TestCase):
     # Implement unittests for formal protocols here.
     #
 
     def testImplementFormalProtocol(self):
 
-        NSLocking = objc.protocolNamed('NSLocking')
-
-        class MyClassNotImplementingLocking(NSObject):
+        class MyClassNotImplementingProtocol(NSObject):
             pass
 
-        self.assert_(not MyClassNotImplementingLocking.pyobjc_classMethods.conformsToProtocol_(NSLocking))
+        self.assert_(not MyClassNotImplementingProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
 
-        class MyClassImplementingLocking(NSObject, NSLocking):
+        try:
+            class MyClassNotAlsoImplementingProtocol(NSObject, OC_TestProtocol):
+                def method1(self): pass
+
+            self.fail()
+        except TypeError:
             pass
 
-        self.assert_(MyClassImplementingLocking.pyobjc_classMethods.conformsToProtocol_(NSLocking))
+        class MyClassImplementingProtocol(NSObject, OC_TestProtocol):
+            def method1(self): pass
+            def method2_(self, a): pass
+
+        self.assert_(MyClassImplementingProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
+
+
+
+        # The PyObjC implementation of formal protocols is slightly looser
+        # than Objective-C itself: you can inherit part of the protocol
+        # from the superclass.
+        # XXX: not really: you won't inherit the right signatures by default
+
+        class MyClassImplementingHalfOfProtocol(NSObject):
+                def method1(self): pass
+                method1 = objc.selector(method1, signature='i@:')
+
+        self.assert_(not MyClassImplementingHalfOfProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
+
+        class MyClassImplementingAllOfProtocol(MyClassImplementingHalfOfProtocol, OC_TestProtocol):
+                def method2_(self, v): pass
+
+        self.assert_(MyClassImplementingAllOfProtocol.pyobjc_classMethods.conformsToProtocol_(OC_TestProtocol))
+
 
     def testImplementAnotherObject(self):
         anObject = NSObject.alloc().init()
         except TypeError: 
             pass
 
+    def testDefiningingProtocols(self):
+
+        # Pretty useless, but should work
+
+        self.assert_(MyOtherProtocol.conformsTo_(MyProtocol))
+
+
+        try:
+            class MyClassImplementingMyProtocol(NSObject, MyProtocol):
+                pass
+
+            # Declare to implement a protocol, but don't do it?
+            self.fail()
+        except TypeError:
+            pass
+
+
+        class MyClassImplementingMyProtocol(NSObject, MyProtocol):
+            def protoMethod(self):
+                return 1
+
+            def anotherProto_with_(self, a1, a2):
+                pass
+
+        self.assertEquals(MyClassImplementingMyProtocol.protoMethod.signature, "I@:")
+        self.assertEquals(MyClassImplementingMyProtocol.anotherProto_with_.signature, "v@:ii")
+        self.assert_(MyClassImplementingMyProtocol.pyobjc_classMethods.conformsToProtocol_(MyProtocol))
+
+        class MyClassImplementingMyOtherProtocol(NSObject, MyOtherProtocol):
+            def protoMethod(self): pass
+            def anotherProto_with_(self, a1, a2): pass
+            def yetAnother_(self, a): pass
+
+        self.assertEquals(MyClassImplementingMyOtherProtocol.protoMethod.signature, "I@:")
+        self.assertEquals(MyClassImplementingMyOtherProtocol.anotherProto_with_.signature, "v@:ii")
+        self.assertEquals(MyClassImplementingMyOtherProtocol.yetAnother_.signature, "i@:I")
+        self.assert_(MyClassImplementingMyOtherProtocol.pyobjc_classMethods.conformsToProtocol_(MyProtocol))
+        self.assert_(MyClassImplementingMyOtherProtocol.pyobjc_classMethods.conformsToProtocol_(MyOtherProtocol))
+
+        try:
+            class ImplementingMyClassProtocol(NSObject, MyClassProtocol):
+                pass
+
+            self.fail()
+        except TypeError:
+            pass
+
+        class ImplementingMyClassProtocol(NSObject, MyClassProtocol):
+                def anAnotherOne_(self, a):
+                    pass
+
+                def aClassOne_(self, a):
+                    pass
+
+                aClassOne_ = classmethod(aClassOne_)
+
+        self.assertEquals(ImplementingMyClassProtocol.anAnotherOne_.signature, 'i@:i')
+        self.assertEquals(ImplementingMyClassProtocol.aClassOne_.isClassMethod, True)
+        self.assertEquals(ImplementingMyClassProtocol.aClassOne_.signature,'@@:i')
+
+        # TODO: protocol with class and instance method with different
+        # signatures.
+        # TODO: should not need to specify classmethod() if it can be 
+        # deduced from the protocol
+
+
+    def testIncorrectlyDefiningFormalProtocols(self):
+        # Some bad calls to objc.formal_protocol
+        self.assertRaises(TypeError, objc.formal_protocol, [], None, ())
+        self.assertRaises(TypeError, objc.formal_protocol, 'supers', (NSObject,) , ())
+        self.assertRaises(TypeError, objc.formal_protocol, 'supers', objc.protocolNamed('NSLocking') , ())
+        self.assertRaises(TypeError, objc.formal_protocol, 'supers', [
+                objc.protocolNamed('NSLocking'),
+                "hello",
+            ], ())
+        self.assertRaises(TypeError, objc.formal_protocol, 'supers', None, [
+                objc.selector(None, selector='fooMethod:', signature='v@:i'),
+                "hello",
+            ])
+
+    def testMethodInfo(self):
+        self.assertEquals(
+                MyProtocol.descriptionForInstanceMethod_("protoMethod"),
+                    ("protoMethod", "I@:"))
+
+        self.assertEquals(
+                MyProtocol.descriptionForInstanceMethod_("nosuchmethod"),
+                    None)
+
+        self.assertEquals(
+                MyClassProtocol.descriptionForClassMethod_("aClassOne:"),
+                    ("aClassOne:", "@@:i"))
+
+        self.assertEquals(
+                MyClassProtocol.descriptionForClassMethod_("nosuchmethod"),
+                    None)
+
+
+    def dont_testObjCInterface(self):
+        # TODO: tests that access the Objective-C interface of protocols
+        # (those methods should be forwarded to the underlying object, as 
+        #  with objc.pyobjc_unicode).
+        # NOTE: This is not very important, the only methods that are not
+        # explicitly wrapped should be compatibility methods that will
+        # cause a warning when called.
+        self.assertEquals(1, 0)
 
 if __name__ == '__main__':
     unittest.main()

Lib/objc/test/test_protocolNamed.py

 class TestProtocols (unittest.TestCase):
     def testBasic(self):
         p = objc.protocolNamed('NSObject')
-        self.assert_(isinstance(p, objc.lookUpClass('Protocol')))
+        self.assert_(isinstance(p, objc.formal_protocol))
+        #self.assert_(isinstance(p, objc.lookUpClass('Protocol')))
 
     def testNoProtocol(self):
         self.assertRaises(objc.ProtocolError, objc.protocolNamed, "PyObjCFooBarProtocol")

Modules/objc/OC_PythonObject.m

 			instance = PyObjCObject_GetObject(obj);
 			PyObjC_GIL_RETURN(instance);
 		}
+		if(PyObjCFormalProtocol_Check(obj)) {
+			instance = PyObjCFormalProtocol_GetProtocol(obj);
+			PyObjC_GIL_RETURN(instance);
+		}
 		if (OC_PythonObject_DepythonifyTable != NULL &&
 			PyList_Check(OC_PythonObject_DepythonifyTable)) {
 			int i;

Modules/objc/class-builder.m

 			goto error_cleanup;
 		}
 		for (i=0; i < protocol_count; i++) {
-			Protocol *protocol;
 			PyObject *wrapped_protocol;
 			wrapped_protocol = PyList_GET_ITEM(protocols, i);
-			if (PyObjCInformalProtocol_Check(wrapped_protocol)) {
+			if (!PyObjCFormalProtocol_Check(wrapped_protocol)) {
 				continue;
 			}
-			if (!PyObjCObject_Check(wrapped_protocol)) {
-				PyErr_Format(PyExc_TypeError,
-					"protocol must be a 'Protocol', not an '%s'",
-					wrapped_protocol->ob_type->tp_name);
-				goto error_cleanup;
-			}
-			protocol = (Protocol *)PyObjCObject_GetObject(wrapped_protocol);
-			if (!PyObjCClass_IsSubClass(GETISA((id)protocol), [Protocol class])) {
-				PyErr_Format(PyExc_TypeError,
-					"protocol must be a 'Protocol', not an '%s'",
-					wrapped_protocol->ob_type->tp_name);
-				goto error_cleanup;
-			}
-			protocol_list->list[cur_protocol] = protocol;
+			protocol_list->list[cur_protocol] = PyObjCFormalProtocol_GetProtocol(wrapped_protocol);
 			cur_protocol++;
 		}
 		protocol_list->list[cur_protocol] = nil;

Modules/objc/formal-protocol.h

+#ifndef PyObjC_FORMAL_PROTOCOL_H
+#define PyObjC_FORMAL_PROTOCOL_H
+/*!
+ * @header formal-protocol.h
+ * @abstruct Support for formal protocols (aka @protocol)
+ * @discussion
+ * 	This module defines functions and types for working with formal 
+ * 	protocols. 
+ *
+ * 	NOTE: We also use these functions when looking for the method signatures
+ * 	declared in formal protocols, as we don't have specific support for
+ * 	formal protocols.
+ */
+
+extern PyTypeObject PyObjCFormalProtocol_Type;
+#define PyObjCFormalProtocol_Check(obj) PyObject_TypeCheck(obj, &PyObjCFormalProtocol_Type)
+
+int PyObjCFormalProtocol_CheckClass(PyObject*, char*, PyObject*, PyObject*);
+const char* PyObjCFormalProtocol_FindSelectorSignature(PyObject* obj, SEL selector, int isClassMethod);
+PyObject* PyObjCFormalProtocol_ForProtocol(Protocol* protocol);
+Protocol* PyObjCFormalProtocol_GetProtocol(PyObject* protocol);
+
+#endif /* PyObjC_FORMAL_PROTOCOL_H */

Modules/objc/formal-protocol.m

+/*
+ * Implementation of support type for formal protocols.
+ *
+ * See the module DOCSTR for more information.
+ */
+#include "pyobjc.h"
+
+struct Protocol_struct {
+	@defs(Protocol);
+};
+
+PyDoc_STRVAR(proto_cls_doc,
+"objc.formal_protocol(name, supers, selector_list)\n"
+"\n"
+"This class is used to proxy Objective-C formal protocols, and can also be \n"
+"used to define new formal protocols.\n"
+"");
+
+typedef struct {
+	PyObject_HEAD
+
+	Protocol* objc;
+} PyObjCFormalProtocol;
+
+
+static void
+proto_dealloc(PyObject* object)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	PyObjC_UnregisterPythonProxy(self->objc, object);
+	object->ob_type->tp_free(object);
+}
+
+
+static PyObject*
+proto_repr(PyObject* object)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	const char* name;
+
+	name = [self->objc name];
+	if (name == NULL) {
+		name = "<nil>";
+	}
+
+	return PyString_FromFormat("<%s %s at %p>", self->ob_type->tp_name, name, (void*)self);
+}
+
+static PyObject*
+proto_get__class__(PyObject* object __attribute__((__unused__)), void* closure __attribute__((__unused__)))
+{
+	return PyObjCClass_New([Protocol class]);
+}
+
+static PyObject*
+proto_get__name__(PyObject* object, void* closure __attribute__((__unused__)))
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	const char* name = [self->objc name];
+
+	if (name == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+	return PyString_FromString(name);
+}
+
+
+static PyObject*
+proto_new(PyTypeObject* type __attribute__((__unused__)), 
+	PyObject* args, PyObject* kwds)
+{
+static	char*	keywords[] = { "name", "supers", "selectors", NULL };
+	PyObjCFormalProtocol* result = NULL;
+	char* name;
+	PyObject* supers;
+	PyObject* selectors;
+	int i, len;
+	int numInstance = 0;
+	int numClass = 0;
+	struct Protocol_struct* theProtocol = NULL;
+	struct objc_method_description* c;
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOO:formal_protocol",
+			keywords, &name, &supers, &selectors)) { 
+		return NULL;
+	}
+
+	if (supers != Py_None) {
+		supers = PySequence_Fast(supers, "supers need to be a sequence of objc.formal_protocols");
+		if (supers == NULL) return NULL;
+		len = PySequence_Fast_GET_SIZE(supers);
+		for (i = 0; i < len; i++) {
+			PyObject* v = PySequence_Fast_GET_ITEM(supers, i);
+			if (!PyObjCFormalProtocol_Check(v)) {
+				PyErr_SetString(PyExc_TypeError, "supers need to be a sequence of objc.formal_protocols");
+				Py_DECREF(supers);
+				return NULL;
+			}
+		}
+
+	} else {
+		Py_INCREF(supers);
+	}
+
+	selectors = PySequence_Fast(selectors, "selectors need to be a sequence of selectors");
+	if (selectors == NULL) {
+		Py_DECREF(supers);
+		return NULL;
+	}
+
+	len = PySequence_Fast_GET_SIZE(selectors);
+	for (i = 0; i < len; i++) {
+		PyObject* sel = PySequence_Fast_GET_ITEM(selectors, i);
+		if (sel == NULL || !PyObjCSelector_Check(sel)) {
+			PyErr_SetString(PyExc_TypeError,
+				"selectors need to be a sequence of selectors");
+			Py_DECREF(supers);
+			Py_DECREF(selectors);
+			return NULL;
+		}
+		if (PyObjCSelector_GetFlags(sel)&PyObjCSelector_kCLASS_METHOD) {
+			numClass++;
+		} else {
+			numInstance++;
+		}
+	}
+
+	/* Allocate the protocol with alloc+init, don't shortcut through
+	 * malloc, that doesn't work.
+	 */
+	theProtocol = (struct Protocol_struct*)[[Protocol alloc] init];
+	if (theProtocol == NULL) {
+		PyErr_NoMemory();
+		goto error;
+	}
+
+	theProtocol->protocol_name = strdup(name);
+	if (theProtocol->protocol_name == NULL) {
+		PyErr_NoMemory();
+		goto error;
+	}
+
+	if (supers == Py_None) {
+		theProtocol->protocol_list = NULL;
+	} else {
+		len = PySequence_Fast_GET_SIZE(supers);
+		theProtocol->protocol_list = malloc(
+			sizeof(struct objc_protocol_list) +
+			(1+len) * sizeof(Protocol*));
+		theProtocol->protocol_list->next = NULL;
+		theProtocol->protocol_list->count = len;
+		for (i = 0; i < len; i++) {
+			PyObject* v = PySequence_Fast_GET_ITEM(supers, i);
+			theProtocol->protocol_list->list[i] =
+				PyObjCFormalProtocol_GetProtocol(v);
+			if (theProtocol->protocol_list->list[i] == NULL) {
+				goto error;
+			}
+		}
+		theProtocol->protocol_list->list[i] = NULL;
+	}
+
+	if (numInstance != NULL) {
+		theProtocol->instance_methods = malloc(
+			sizeof(struct objc_method_description_list) +
+			(1+numInstance)  * sizeof(struct objc_method_description));
+		if (theProtocol->instance_methods == NULL) {
+			PyErr_NoMemory();
+			goto error;
+		}
+		theProtocol->instance_methods->count = 0;
+	}
+	if (numClass != NULL) {
+		theProtocol->class_methods = malloc(
+			sizeof(struct objc_method_description_list) + 
+			(1+numClass)  * sizeof(struct objc_method_description));
+		if (theProtocol->class_methods == NULL) {
+			PyErr_NoMemory();
+			goto error;
+		}
+		theProtocol->class_methods->count = 0;
+	}
+
+	len = PySequence_Fast_GET_SIZE(selectors);
+	for (i = 0; i < len; i++) {
+		PyObject* sel = PySequence_Fast_GET_ITEM(selectors, i);
+		SEL theSel = PyObjCSelector_GetSelector(sel);
+		char* theSignature = PyObjCSelector_Signature(sel);
+
+		if (PyObjCSelector_GetFlags(sel)&PyObjCSelector_kCLASS_METHOD) {
+			c = &(theProtocol->class_methods->list[
+				theProtocol->class_methods->count++]);
+			c->name = theSel;
+			c->types = strdup(theSignature);
+			if (c->types == NULL) goto error;
+		} else {
+			c = &(theProtocol->instance_methods->list[
+				theProtocol->instance_methods->count++]);
+			c->name = theSel;
+			c->types = strdup(theSignature);
+			if (c->types == NULL) goto error;
+		}
+	}
+
+	if (theProtocol->instance_methods) {
+		c = &(theProtocol->instance_methods->list[
+			theProtocol->instance_methods->count]);
+		c->name = NULL;
+		c->types = NULL;
+	}
+
+	if (theProtocol->class_methods) {
+		c = &(theProtocol->class_methods->list[
+			theProtocol->class_methods->count]);
+		c->name = NULL;
+		c->types = NULL;
+	}
+
+
+	result = (PyObjCFormalProtocol*)PyObject_New(
+			PyObjCFormalProtocol, &PyObjCFormalProtocol_Type);
+	if (result == NULL) goto error;
+
+	Py_DECREF(selectors);
+	Py_DECREF(supers);
+
+	result->objc = (Protocol*)theProtocol;
+	PyObjC_RegisterPythonProxy(result->objc, (PyObject*)result);
+	return (PyObject*)result;
+
+error:
+	Py_DECREF(selectors);
+	Py_DECREF(supers);
+
+	if (theProtocol == NULL) return NULL;
+
+	if (theProtocol->protocol_name != NULL) {
+		free(theProtocol->protocol_name);
+	}
+
+	if (theProtocol->protocol_list != NULL) {
+		free(theProtocol->protocol_list);
+	}
+
+	if (theProtocol->instance_methods != NULL) {
+		for (i = 0; i < theProtocol->instance_methods->count; i++) {
+			c = theProtocol->instance_methods->list + i;
+			if (c->name) {
+				free(c->name);
+			}
+		}
+		free(theProtocol->instance_methods);
+	}
+
+	if (theProtocol->class_methods != NULL) {
+		for (i = 0; i < theProtocol->class_methods->count; i++) {
+			c = theProtocol->class_methods->list + i;
+			if (c->name) {
+				free(c->name);
+			}
+		}
+		free(theProtocol->class_methods);
+	}
+	return NULL;
+}
+
+static PyObject*
+proto_name(PyObject* object)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	const char* name = [self->objc name];
+
+	if (name == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
+	return PyString_FromString(name);
+}
+
+static PyObject*
+proto_conformsTo_(PyObject* object, PyObject* args)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	PyObject* protocol;
+	Protocol* objc_protocol;
+
+	if (!PyArg_ParseTuple(args, "O", &protocol)) {
+		return NULL;
+	}
+
+	if (!PyObjCFormalProtocol_Check(protocol)) {
+		PyErr_SetString(PyExc_TypeError, "Expecting a formal protocol");
+		return NULL;
+	}
+	objc_protocol = PyObjCFormalProtocol_GetProtocol(protocol);
+
+	if ([self->objc conformsTo:objc_protocol]) {
+		return PyBool_FromLong(1);
+	} else {
+		return PyBool_FromLong(0);
+	}
+}
+
+static PyObject*
+descriptionForInstanceMethod_(PyObject* object, PyObject* sel)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	SEL aSelector;
+	struct objc_method_description* descr;
+
+	if (PyObjCSelector_Check(sel)) {
+		aSelector = PyObjCSelector_GetSelector(sel);
+	} else if (PyString_Check(sel)) {
+		char* s = PyString_AsString(sel);
+		if (*s == '\0') {
+			PyErr_SetString(PyExc_ValueError, 
+					"empty selector name");
+			return NULL;
+		}
+
+		aSelector = PyObjCRT_SELUID(s);
+	}
+
+	descr = [self->objc descriptionForInstanceMethod:aSelector];
+	if (descr == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	} else {
+		return Py_BuildValue("(ss)",
+			PyObjCRT_SELName(descr->name),
+			descr->types);
+	}
+}
+
+static PyObject*
+descriptionForClassMethod_(PyObject* object, PyObject* sel)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	SEL aSelector;
+	struct objc_method_description* descr;
+
+	if (PyObjCSelector_Check(sel)) {
+		aSelector = PyObjCSelector_GetSelector(sel);
+	} else if (PyString_Check(sel)) {
+		char* s = PyString_AsString(sel);
+		if (*s == '\0') {
+			PyErr_SetString(PyExc_ValueError, 
+					"empty selector name");
+			return NULL;
+		}
+
+		aSelector = PyObjCRT_SELUID(s);
+	}
+
+	descr = [self->objc descriptionForClassMethod:aSelector];
+	if (descr == NULL) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	} else {
+		return Py_BuildValue("(ss)",
+			PyObjCRT_SELName(descr->name),
+			descr->types);
+	}
+}
+
+static PyMethodDef proto_methods[] = {
+	{
+		"name",
+		(PyCFunction)proto_name,
+		METH_NOARGS,
+		"Return the  protocol name",
+	},
+	{
+		"conformsTo_",
+		(PyCFunction)proto_conformsTo_,
+		METH_VARARGS,
+		"Does this protocol conform to another protocol"
+	},
+	{
+		"descriptionForInstanceMethod_",
+		(PyCFunction)descriptionForInstanceMethod_,
+		METH_O,
+		"Description for an instance method in the protocol"
+	},
+	{
+		"descriptionForClassMethod_",
+		(PyCFunction)descriptionForClassMethod_,
+		METH_O,
+		"Description for a class method in the protocol"
+	},
+	{ 0, 0, 0, 0 }
+};
+
+static PyGetSetDef proto_getset[] = {
+	{
+		"X__class__",
+		(getter)proto_get__class__,
+		NULL,
+		NULL,
+		0
+	},
+	{
+		"__name__",
+		(getter)proto_get__name__,
+		NULL,
+		NULL,
+		0
+	},
+	{ NULL, NULL, NULL, NULL, 0 }
+};
+
+PyTypeObject PyObjCFormalProtocol_Type = {
+	PyObject_HEAD_INIT(&PyType_Type)
+	0,					/* ob_size */
+	"objc.formal_protocol",			/* tp_name */
+	sizeof(PyObjCFormalProtocol),		/* tp_basicsize */
+	0,					/* tp_itemsize */
+	/* methods */
+	proto_dealloc,	 			/* tp_dealloc */
+	0,					/* tp_print */
+	0,					/* tp_getattr */
+	0,					/* tp_setattr */
+	0,					/* tp_compare */
+	proto_repr,				/* tp_repr */
+	0,					/* tp_as_number */
+	0,					/* tp_as_sequence */
+	0,		       			/* tp_as_mapping */
+	0,					/* tp_hash */
+	0,					/* tp_call */
+	0,					/* tp_str */
+	PyObject_GenericGetAttr,		/* tp_getattro */
+	0,					/* tp_setattro */
+	0,					/* tp_as_buffer */
+	Py_TPFLAGS_DEFAULT,			/* tp_flags */
+ 	proto_cls_doc,				/* tp_doc */
+ 	0,					/* tp_traverse */
+ 	0,					/* tp_clear */
+	0,					/* tp_richcompare */
+	0,					/* tp_weaklistoffset */
+	0,					/* tp_iter */
+	0,					/* tp_iternext */
+	proto_methods,				/* tp_methods */
+	0,					/* tp_members */
+	proto_getset,				/* tp_getset */
+	0,					/* tp_base */
+	0,					/* tp_dict */
+	0,					/* tp_descr_get */
+	0,					/* tp_descr_set */
+	0,					/* tp_dictoffset */
+	0,					/* tp_init */
+	0,					/* tp_alloc */
+	proto_new,				/* tp_new */
+	0,		        		/* tp_free */
+	0,					/* tp_is_gc */
+        0,                                      /* tp_bases */
+        0,                                      /* tp_mro */
+        0,                                      /* tp_cache */
+        0,                                      /* tp_subclasses */
+        0,                                      /* tp_weaklist */
+        0                                       /* tp_del */
+};
+
+
+/*
+ * Find information about a selector in the protocol.
+ *
+ * Return NULL if no information can be found, but does not set an
+ * exception.
+ */
+const char* 
+PyObjCFormalProtocol_FindSelectorSignature(PyObject* object, SEL selector, int isClassMethod)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+	struct objc_method_description* descr;
+
+	if (isClassMethod) {
+		descr = [self->objc descriptionForClassMethod: selector];
+	} else {
+		descr = [self->objc descriptionForInstanceMethod: selector];
+	}
+	if (descr) {
+		return descr->types;
+	}
+	return NULL;
+}
+
+static int 
+signaturesEqual(char* sig1, char* sig2)
+{
+	char buf1[1024];
+	char buf2[1024];
+	int r;
+
+	/* Return 0 if the two signatures are not equal */
+	if (strcmp(sig1, sig2) == 0) return 1;
+
+	/* For some reason compiler-generated signatures contain numbers that
+	 * are not used by the runtime. These are irrelevant for our comparison
+	 */
+	r = PyObjCRT_SimplifySignature(sig1, buf1, sizeof(buf1));
+	if (r == -1) { 
+		return 0; 
+	}
+
+	r = PyObjCRT_SimplifySignature(sig2, buf2, sizeof(buf2));
+	if (r == -1) { 
+		return 0; 
+	}
+
+
+	return strcmp(buf1, buf2) == 0;
+}
+
+
+extern PyObject* findSelInDict(PyObject* clsdict, SEL selector);
+extern int signaturesEqual(char* sig1, char* sig2);
+
+
+static int
+do_verify(
+	const char* protocol_name, 
+	struct objc_method_description* descr, 
+	int is_class __attribute__((__unused__)), 
+	char* name,
+	PyObject* super_class, 
+	PyObject* clsdict)
+{
+	PyObject* meth;
+
+	/* TODO: take classmethodness into account */
+
+	meth = findSelInDict(clsdict, descr->name);
+	if (meth == NULL || !PyObjCSelector_Check(meth)) {
+
+		meth = PyObjCClass_FindSelector(super_class, descr->name);
+		if (meth == NULL || !PyObjCSelector_Check(meth)) {
+			PyErr_Format(PyExc_TypeError,
+				"class %s does not full implement protocol "
+				"%s: no implementation for %s",
+				name,
+				protocol_name,
+				PyObjCRT_SELName(descr->name));
+			return 0;
+		}
+	}
+
+	if (signaturesEqual(descr->types, 
+				PyObjCSelector_Signature(meth))) {
+		return 1;
+	} 
+
+	PyErr_Format(PyExc_TypeError,
+		"class %s does not correctly implement "
+		"protocol %s: the signature for method %s "
+		"is %s instead of %s",
+		name,
+		protocol_name,
+		PyObjCRT_SELName(descr->name),
+		PyObjCSelector_Signature(meth),
+		descr->types);
+	return 0;
+}
+
+static int
+do_check(
+    const char* protocol_name,
+    Protocol* protocol, 
+    char* name, 
+    PyObject* super_class, 
+    PyObject* clsdict)
+{
+	struct Protocol_struct* cur; 
+	int i, r;
+
+	cur = (struct Protocol_struct*)protocol;
+
+	if (cur->protocol_list) {
+		for (i = 0; i < cur->protocol_list->count; i++) {
+			r = do_check(protocol_name, cur->protocol_list->list[i], name, super_class, clsdict);
+			if (r == 0) return r;
+		}
+	}
+
+	if (cur->instance_methods) {
+		for (i = 0; i < cur->instance_methods->count; i++) {
+			if (!do_verify(protocol_name, cur->instance_methods->list + i, 0, name, super_class, clsdict)) {
+				return 0;
+			}
+		}
+	} 
+	if (cur->class_methods) {
+		for (i = 0; i < cur->class_methods->count; i++) {
+			if (!do_verify(protocol_name, cur->class_methods->list + i, 1, name, super_class, clsdict)) {
+				return 0;
+			}
+		}
+	} 
+	return 1;
+}
+
+int	
+PyObjCFormalProtocol_CheckClass(
+	PyObject* obj, char* name, PyObject* super_class, PyObject* clsdict)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)obj;	
+
+	if (!PyObjCFormalProtocol_Check(obj)) {
+		PyErr_Format(PyExc_TypeError,
+			"First argument is not an 'objc.formal_protocol' but "
+			"'%s'", obj->ob_type->tp_name);
+		return 0;
+	}
+	if (!PyObjCClass_Check(super_class)) {
+		PyErr_Format(PyExc_TypeError,
+			"Third argument is not an 'objc.objc_class' but "
+			"'%s'", super_class->ob_type->tp_name);
+		return 0;
+	}
+	if (!PyDict_Check(clsdict)) {
+		PyErr_Format(PyExc_TypeError,
+			"Fourth argument is not a 'dict' but '%s'",
+			clsdict->ob_type->tp_name);
+		return 0;
+	}
+
+	return do_check([self->objc name], self->objc, name, super_class, clsdict);
+}
+
+PyObject* PyObjCFormalProtocol_ForProtocol(Protocol* protocol)
+{
+	PyObjCFormalProtocol* result;
+
+	result = (PyObjCFormalProtocol*)PyObject_New(
+			PyObjCFormalProtocol, &PyObjCFormalProtocol_Type);
+	if (result == NULL) {
+		return NULL;
+	}
+
+	result->objc = protocol;
+	PyObjC_RegisterPythonProxy(result->objc, (PyObject*)result);
+	return (PyObject*)result;
+}
+
+Protocol* PyObjCFormalProtocol_GetProtocol(PyObject* object)
+{
+	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;	
+
+	if (!PyObjCFormalProtocol_Check(self)) {
+		PyErr_Format(PyExc_TypeError, 
+			"Expecting objc.formal_protocol, got %s",
+			self->ob_type->tp_name);
+		return NULL;
+	}
+
+	return self->objc;
+}

Modules/objc/informal-protocol.h

 #define PyObjCInformalProtocol_Check(obj) PyObject_TypeCheck(obj, &PyObjCInformalProtocol_Type)
 
 int PyObjCInformalProtocol_CheckClass(PyObject*, char*, PyObject*, PyObject*);
-PyObject* PyObjCInformalProtocol_FindSelector(PyObject* obj, SEL selector);
+PyObject* PyObjCInformalProtocol_FindSelector(PyObject* obj, SEL selector, int isClassMethod);
 int PyObjCInformalProtocol_Warnings(char* name, PyObject* clsdict, PyObject* protocols);
 PyObject* PyObjCInformalProtocol_FindProtocol(SEL selector);
 

Modules/objc/informal-protocol.m

  * exception.
  */
 PyObject* 
-PyObjCInformalProtocol_FindSelector(PyObject* obj, SEL selector)
+PyObjCInformalProtocol_FindSelector(PyObject* obj, SEL selector, int isClassMethod)
 {
 	PyObjCInformalProtocol* self = (PyObjCInformalProtocol*)obj;	
 	int i, len;
 		}
 
 		if (PyObjCSelector_Check(cur)) {
-			if (PyObjCSelector_GetSelector(cur) == selector) {
+			int class_sel = (
+				PyObjCSelector_GetFlags(cur) 
+				& PyObjCSelector_kCLASS_METHOD) != 0;
+			if ((isClassMethod && !class_sel) 
+					|| (!isClassMethod && class_sel)) {
+				continue;
+			}
+
+			if (PyObjCRT_SameSEL(PyObjCSelector_GetSelector(cur), selector)) {
 				Py_DECREF(seq);
 				return cur;
 			}
 	return NULL;
 }
 
-static PyObject*
+/* XXX: Make public */
+PyObject*
 findSelInDict(PyObject* clsdict, SEL selector)
 {
 	PyObject* values;
 	return NULL;
 }
 
-static int 
+int 
 signaturesEqual(char* sig1, char* sig2)
 {
 	char buf1[1024];

Modules/objc/module.m

 	while (protocol_list != NULL) {
 		int i;
 		for (i = 0; i < protocol_list->count; i++) {
-			PyObject *protocol = PyObjCObject_NewClassic(protocol_list->list[i]);
+			PyObject *protocol = PyObjCFormalProtocol_ForProtocol(protocol_list->list[i]);
 			if (protocol == NULL) {
 				Py_DECREF(protocols);
 				return NULL;
 	PyType_Ready(&PyObjCPythonSelector_Type);
 	PyType_Ready(&PyObjCInstanceVariable_Type);
 	PyType_Ready(&PyObjCInformalProtocol_Type);
+	PyType_Ready(&PyObjCFormalProtocol_Type);
 	PyType_Ready(&PyObjCUnicode_Type);
-	PyType_Ready(&PyObjCInformalProtocol_Type);
 	PyType_Ready(&PyObjCIMP_Type);
 	PyType_Ready(&PyObjCMethodAccessor_Type);
 	PyType_Ready(&PyObjCZoneWrapper_Type);
 	PyDict_SetItemString(d, "selector", (PyObject*)&PyObjCSelector_Type);
 	PyDict_SetItemString(d, "ivar", (PyObject*)&PyObjCInstanceVariable_Type);
 	PyDict_SetItemString(d, "informal_protocol", (PyObject*)&PyObjCInformalProtocol_Type);
+	PyDict_SetItemString(d, "formal_protocol", (PyObject*)&PyObjCFormalProtocol_Type);
 	PyDict_SetItemString(d, "function", (PyObject*)&PyObjCFunc_Type);
 	PyDict_SetItemString(d, "IMP", (PyObject*)&PyObjCIMP_Type);
 

Modules/objc/objc-class.m

 			return NULL;
 		} else if (PyObjCInformalProtocol_Check(v)) {
 			PyList_Append(protocols, v);
-		} else if (PyObjCObject_Check(v)) {
-			// XXX: Check to see if it is *actually* a Protocol?
+		} else if (PyObjCFormalProtocol_Check(v)) {
 			PyList_Append(protocols, v);
 		} else {
 			PyList_Append(real_bases, v);
 			continue;
 		}
 
-		// XXX: Check goes here for formal protocols
-		if (!PyObjCInformalProtocol_Check(p)) {
-			continue;
-		}
-
-		if (!PyObjCInformalProtocol_CheckClass(
+		if (PyObjCInformalProtocol_Check(p)) {
+			if (!PyObjCInformalProtocol_CheckClass(
 					p, name, py_super_class, dict)) {
-			Py_DECREF(real_bases);
-			Py_DECREF(protocols);
-			PyObjCClass_UnbuildClass(objc_class);
-			return NULL;
+				Py_DECREF(real_bases);
+				Py_DECREF(protocols);
+				PyObjCClass_UnbuildClass(objc_class);
+				return NULL;
+			}
+		} else if (PyObjCFormalProtocol_Check(p)) {
+			if (!PyObjCFormalProtocol_CheckClass(
+					p, name, py_super_class, dict)) {
+				Py_DECREF(real_bases);
+				Py_DECREF(protocols);
+				PyObjCClass_UnbuildClass(objc_class);
+				return NULL;
+			}
 		}
 	}
 

Modules/objc/objc-runtime-apple.h

 #import <Foundation/NSString.h>
 
 #include <objc/objc-runtime.h>
+#include <objc/Protocol.h>
 
 static inline int 
 PyObjCRT_SameSEL(SEL a, SEL b)

Modules/objc/objc_support.m

 
 	rval = PyObjC_FindPythonProxy(self);
 	if (rval == NULL) {
-		rval = (PyObject *)PyObjCObject_NewClassic(self);
-		PyObjC_RegisterPythonProxy(self, rval);
+		rval = PyObjCFormalProtocol_ForProtocol(self);
 	}
 	return rval;
 }

Modules/objc/pyobjc.h

 #include "class-builder.h" 
 #include "ObjCPointer.h"
 #include "informal-protocol.h"
+#include "formal-protocol.h"
 #include "alloc_hack.h"
 #include "unicode-object.h"
 #include "class-descriptor.h"

Modules/objc/selector.m

  * Find the signature of 'selector' in the list of protocols.
  */
 static char*
-find_protocol_signature(PyObject* protocols, SEL selector)
+find_protocol_signature(PyObject* protocols, SEL selector, int is_class_method)
 {
 	int len;
 	int i;
 			PyErr_Clear();
 			continue;
 		}
-		// XXX: Check goes here for formal protocols
-		if (!PyObjCInformalProtocol_Check(proto)) continue;
 
-		info = PyObjCInformalProtocol_FindSelector(proto, selector);
+		if (PyObjCFormalProtocol_Check(proto)) {
+			const char* signature;
+			
+			signature = PyObjCFormalProtocol_FindSelectorSignature(
+					proto, selector, is_class_method
+			);
+			if (signature != NULL) {
+				return (char*)signature;
+			}
+		}
+
+		info = PyObjCInformalProtocol_FindSelector(proto, selector, is_class_method);
 		if (info != NULL) {
 			return PyObjCSelector_Signature(info);
 		}
 		return NULL;
 	}
 
-	info = PyObjCInformalProtocol_FindSelector(proto, selector);
+	info = PyObjCInformalProtocol_FindSelector(proto, selector, is_class_method);
 	if (info != NULL) {
 		if (PyList_Append(protocols, proto) < 0) {
 			return NULL;
 		PyErr_Clear(); /* The call to PyObjCClass_FindSelector failed */
 		if (protocols != NULL) {
 			signature = find_protocol_signature(
-					protocols, selector);
+					protocols, selector, is_class_method);
 			if (signature == NULL && PyErr_Occurred()) {
 				return NULL;
 			}

Modules/objc/test/protocol.m

+/*
+ * This module is used in the unittests for object identity.
+ */
+#include "Python.h"
+#include "pyobjc-api.h"
+
+#import <Foundation/Foundation.h>
+
+@protocol OC_TestProtocol
+-(int)method1;
+-(void)method2:(int)v;
+@end
+
+static PyMethodDef protocol_methods[] = {
+	{ 0, 0, 0, 0 }
+};
+
+void initprotocol(void);
+void initprotocol(void)
+{
+	PyObject* m;
+	Protocol* p;
+
+	m = Py_InitModule4("protocol", protocol_methods, 
+			NULL, NULL, PYTHON_API_VERSION);
+
+	PyObjC_ImportAPI(m);
+
+	p = @protocol(OC_TestProtocol);
+	PyModule_AddObject(m, "OC_TestProtocol", PyObjC_ObjCToPython("@", &p));
+}
     class MyLockingClass(NSObject, objc.protocolNamed('NSLocking')):
         # implementation
         pass
+
+  It is also possible to define new protocols::
+
+     MyProtocol = objc.formal_protocol("MyProtocol", None, [
+     	selector(None, selector='mymethod', signature='v@:'),
+     ])
+
+  All formal protocols are instances of ``objc.formal_protocol``.
         
 - PyObjCTools.KeyValueCoding has a new ``kvc`` class that allows
   Pythonic Key-Value Coding.
         "-no-cpp-precomp",
         "-Wno-long-double",
         "-g",
+        "-O0",
 
         # Loads of warning flags
         "-Wall", "-Wstrict-prototypes", "-Wmissing-prototypes",
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.