Objective-C interoperability: Eliminate NSObjectProtocol

That seems OK to me. In the #1 case, could we reasonably migrate the responds(to:) check to do a Swift-ier check on the optional protocol requirement or other protocol that provides the desired method? As you noted yourself, most of the NSObject protocol has more "Swift-native" alternatives that work on all Swift types already, and if we can plausibly migrate explicit use of its methods to those alternatives, that seems like a win for the ecosystem overall.

1 Like

In this specific case, the argument to responds(to:) is a parameter we get from somewhere else, so we don't get to do a Swift-ier check. But, I think your general point is good, and one could probably build a migration workflow for many cases.

Doug

Couldn't global functions taking an AnyObject be added as a straightforward migration path? For instance:

func responds(to: Selector, from: AnyObject) -> Bool

What would APIs like func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol return in this case? Just AnyObject?

But yes, it would be great for NSObjectProtocol to die.

I think this is a great idea...

AnyObject, or if we go with @jckarter's suggestion, just Any.

Doug

1 Like

What would be required exactly to allow value-types to conform to @objc protocols? I mean, theoretically it should be fairly simple - you can box the value in a subclass of SwiftObject which forwards the appropriate methods. Either the compiler synthesises custom subclasses for each value-type, or we use a single subclass and have the compiler populate a thunk-table which we consult in responds(to: Selector), etc.

Has this been investigated? If not, it could make for a great GSoC project for somebody...

I mean, if Array could conform to UITableViewDataSource (even conditionally), there would be lots of happy Swift developers.

3 Likes

I agree it would be a great feature if value types could conform to @objc protocols. As you noted, a value type conforming to @objc classes would need to have its own ObjC boxing class defined for it, in order for us to be able to put methods on it. Since conformances can be introduced by external modules, this boxing class might have to be generated in an external module or uniqued across modules, or even introduced dynamically if a conformance ended being introduced by dlopen-ing a dynamic library. Making that work without requiring us to eagerly create a boxing class for every Swift value type would be tricky.

I'm pretty against the idea of value types conforming to @objc protocols. Value types inherently don't have identity, and most @objc protocols really do expect things to behave with identity. Value types also don't have any real notion of ownership, but data sources and delegates are weakly referenced by view classes in UIKit. Finally, there are lots of ways to conform to UITableViewDataSource, but a type can only conform to a protocol in one way. It just doesn't fit.

(It's more interesting for things like NSCoding, but I still think there's a limited set of @objc protocols that would actually make this worth doing, and it would continue to make the language bigger.)

2 Likes

There are plenty of protocols for which identity isn't a core requirement of the interface, and there are plenty of Foundation protocols that it'd be natural for value types to be able to conform to. I think it would in a sense make the language smaller, since it removes an artificial barrier between "pure Swift" and the Objective-C world—you no longer have to do things a different way just because of Cocoa. Maybe we should start another thread to discuss this, though, in order to not derail the discussion about NSObjectProtocol.

1 Like

Sure, I just brought it up because this proposal calls NSObjectProtocol and NSObject inheritance "vacuous" because that functionality is all in SwiftObject. So that's just the immediate next question that occurs to me.

But +1 on this pitch/proposal. Everybody hits this.

Could we also remove the concrete NSObject type, not just the protocol? Why do we still need it?

Without the NSObject, the informal protocols (i.e. unimplemented categories on NSObject) would become extensions of AnyObject (which currently is not allowed in Swift) which would bring a lot of unwanted ObjC stuff into Swift.

Not to mention that e.g. all ObjC KVO (not a big deal on iOS, I guess, but with bindings on Mac it is) depends on NSObject implementation - removing NSObject would strip you of a way to use ObjC KVO... Or it would have to be brought directly into Swift.

3 Likes

How would this interact with weak though? We don't currently have a way to express "weak if reference type, otherwise don't care". If everything coming from ObjC is Any then I can't form weak references to them without casting.

Actually I think I've talked myself into this being an orthogonal issue.

1 Like

If a property is declared as weak in ObjC headers, we could still import is as class-constrained. If we went down the "value types can conform to @objc protocols" rabbithole, that would be a trickier question to answer. ObjC protocols that are idiomatically used with weak references could be annotated as maintaining their class constraint somehow.

I am suspicious of this claim that NSObject inheritance is unnecessary. You may be putting too much trust in type declarations in Objective-C code.

Objective-C code often implicitly depends on objects being true instances of NSObject. It's not the methods in NSObjectProtocol, it's the methods that aren't in NSObjectProtocol. Sometimes it is due to methods on NSObject itself that are not part of NSObjectProtocol. Sometimes it is due to categories on NSObject.

SwiftObject lacks both of those. SwiftObject does not implement everything that NSObject itself does. SwiftObject can't get NSObject's categories.

By removing the NSObject inheritance requirement from things like UITableViewDelegate you are trusting that the users of that delegate are in fact avoiding methods outside NSObjectProtocol. In theory those users can declare whether they do so: it's precisely the distinction between id<SomeDelegate> and NSObject<SomeDelegate>*. In practice I don't know if we can get away taking them at their word. It is almost certainly untested in nearly all cases, and you know what they say about untested code.

SwiftObject already implements some parts of NSObject other than NSObjectProtocol for similar reasons: it turned out that Foundation's collection classes were themselves assuming that their contents implemented methods that were added to NSObject by categories.

1 Like

Yeah, SwiftObject being its own root class has caused us a continuing long tail of breakage. It might be reasonable for SwiftObject to itself be an NSObject subclass. Is there anything else on an Apple system that's id-compatible and not also an NSObject subclass, or an NSProxy thereof?

Wouldn't any "root" class, like Object or NSLeafProxy, fit that definition?

Wouldn't then removing the NSObject inheritance (e.g. in the distant future when we move away from ObjC altogether) be a breaking change on many levels? I'm just wondering if tying up with NSObject that tightly is a good idea. Perhaps for now, but the idea here is to eventually move Swift away from the ObjC runtime, isn't it?

I mean the ObjC runtime was a good starting point, but I'd say if Swift wasn't so tied to Apple platforms, so much ObjC support wouldn't be there and some of the runtime structures (e.g. defining classes) would be quite different.

But I definitely see what @gparker42 is saying - I was recently mocking up some classes using protocols and was basing the protocol on NSObjectProtocol - but then I realized the the NSObjectProtocol doesn't define the KVO methods (these are informal protocol on NSObject), so I had to define those within the protocol as well. And SwiftObject currently doesn't implement KVO methods. Which is both unlucky (e.g. when you use bindings, the object needs to be NSObject-based), yet liberating for the future of the language IMHO.

We already know how Swift behaves without ObjC interop, since that's how it works on non-Apple platforms today. Nobody's in any rush to move away from ObjC on platforms where it's already existing, which is why refining the interop between Swift and ObjC on those platforms is important.

1 Like

Sure, but where do you see Swift and ObjC interaction to be in 10 years? My objection was against bringing NSObject dependency (even hidden), as removing it later on would be a breaking change as you'd be removing various categories on NSObject that the ObjC runtime would be able to call on "pure" Swift objects and suddenly wouldn't.

Also, it would actually kind of allow extensions on AnyObject via extensions on NSObject with its members marked as @objc which I'm not sure we want.

It's IMHO better to keep the "pure" Swift objects without the NSObject inheritance. If you need e.g. Cocoa's KVO, etc. the NSObject inheritance is an opt-in.