[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
>
[snip]
> (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