[swift-evolution] [Mini-proposal] Require @nonobjc on members of @objc protocol extensions

Douglas Gregor dgregor at apple.com
Tue Jan 5 11:52:52 CST 2016

> On Jan 5, 2016, at 5:41 AM, Charles Srstka <cocoadev at charlessoft.com> wrote:
>> On Jan 4, 2016, at 10:32 PM, Douglas Gregor via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> There is no direct way to implement Objective-C entry points for protocol extensions. One would effectively have to install a category on every Objective-C root class [*] with the default implementation or somehow intercept all of the operations that might involve that selector. 
> I can almost do it right now, just hacking with the Objective-C runtime functions, so I’d think that if you were actually working with the compiler sources, it should be doable. The trouble is on the Swift side; currently there aren’t any reflection features that I can find that work on Swift protocols.

The compiler isn’t the limitation here, it’s the Objective-C runtime. That’s somewhat malleable, but making changes there to support a Swift feature affects backward deployment.

> @implementation NSObject (Swizzle)

Note that all approaches based on adding categories to a root class require you to enumerate root classes, as I noted in my original message. That’s unfortunate and requires more trickery.

> + (void)load {
>     CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
>     unsigned int classCount = 0;
>     Class *classes = objc_copyClassList(&classCount);

Doing it this way won’t handle protocol conformances or classes loaded later via a dlopen’d dylib.

>     Protocol *proto = @protocol(HasSwiftExtension);
>     for (unsigned int i = 0; i < classCount; i++) {
>         Class eachClass = classes[i];
>         if (class_conformsToProtocol(eachClass, proto)) {
>             unsigned int protoCount = 0;
>             Protocol * __unsafe_unretained *protocols = class_copyProtocolList(eachClass, &protoCount);
>             for (unsigned int j = 0; j < protoCount; j++) {
>                 Protocol *eachProto = protocols[j];
>                 if (protocol_conformsToProtocol(eachProto, proto)) {
>                     unsigned int methodCount = 0;
>                     // what we would want would be to pass YES for isRequiredMethod; unfortunately,
>                     // adding optional methods to an @objc protocol in an extension currently just
>                     // crashes the compiler when I try it. So pass NO, for the demonstration.

The crash is a bug; please file it.

>                     struct objc_method_description *methods = protocol_copyMethodDescriptionList(eachProto, NO, YES, &methodCount);
>                     for (unsigned int k = 0; k < methodCount; k++) {
>                         struct objc_method_description method = methods[k];
>                         if (!class_respondsToSelector(eachClass, method.name)) {
>                             [SwizzleWrapper swizzleClass:[eachClass class] protocol:eachProto method:method];
>                         }
>                     }
>                     free(methods);
>                 }
>             }
>             free(protocols);
>         }
>     }
>     free(classes);
>     NSLog(@"took %f seconds", CFAbsoluteTimeGetCurrent() - startTime);
> }
> @end


> (For the record, I’m not advocating actually using the swizzling method described above; just pointing out that intercepting the selector is possible. Working with the compiler sources, I’d expect more elegant solutions would be possible.)

There are better mechanisms for this than +load. But one would have to deal with the dylib loading issue and the need to enumerate root classes to get to a complete implementation. Frankly, I don’t think this level of Objective-C runtime hackery is worth the effort, hence my suggestion to make the existing behavior explicit.

	- Doug

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160105/58d99c24/attachment.html>

More information about the swift-evolution mailing list