objc2
objc2 copied to clipboard
Add `extern_protocol!` macro and `ProtocolType` trait
Unsure about how to handle this.
The need arose because MTLBuffer
is a protocol, not an actual class (it can't be instantiated by itself), but is useful to handle it as-if it's a class that you're holding instances to.
See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html
Need to figure out how we reconcile protocols and traits - for example, NSCopying
is useful as a trait, while MTLDevice
is useful as a type.
Ideally we'd have one thing called NSCopying
, if not, we could perhaps have NSCopying
and NSCopyingTrait
, with a link between these somehow?
The last usecase of protocols would be like NSWindowDelegate
, which is useful inside declare_class!
, but that is even less thought-out than the other two.
Note also that there is not really such a thing as a "super" protocol; a protocol can have any number of parent/super protocols (similar to a trait having zero or more supertraits).
Idea:
unsafe trait ProtocolType {
// May not always return an protocol for _reasons_???
pub fn protocol() -> Option<&'static Protocol>;
}
extern_protocol!(
#[derive(Debug)]
pub struct NSCopying;
unsafe impl ProtocolType for NSCopying {}
pub trait NSCopyingProtocol {
#[sel_id(copy)]
fn copy(&self) -> Id<Self, Shared>;
}
);
// Becomes
pub struct NSCopying {
inner: Object,
}
impl NSCopyingProtocol for NSCopying {}
pub trait NSCopyingProtocol {
fn copy(&self) -> Id<Self, Shared> {
...
}
}
Need to actually figure out the use-cases for protocols.
So far I have:
- Many classes implement, want to call a method on the protocol (e.g.
NSCopying
)- Maybe interesting to be able to use as a bound?
- Ability to interact with protocol as a normal message-able object (e.g. as done in Metal)
But there's bound to be more than this!
A bit about "formal" and "informal" protocols: https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html
I have a use case---I'm trying to write bindings for the AuthenticationServices
framework and there's the ASAuthorizationProvider protocol.
Tbh I'm not entirely sure why this is a protocol rather than a superclass, but in the framework it's used as the return type for ASAuthorizationRequest's provider property.
I guess probably all I'd need out of some protocol binding macro is appropriate From
and TryFrom
implementations between the "protocol object" (not sure if that's the write term) and the implementing types.
Thanks for the use-case, I think it's very similar to what Metal does.
Also, further ideas:
// in objc2
unsafe trait ConformsTo<P: ProtocolType>: Message {
fn as_protocol(&self) -> &P {
...
}
fn as_protocol_mut(&mut self) -> &mut P {
...
}
}
// + Id::into_protocol
// Usage
extern_protocol!(
#[derive(Debug)]
pub struct ASAuthorizationProvider;
unsafe impl ProtocolType for ASAuthorizationProvider {
// No methods
// The useful part of declaring the methods in here is that we'd be able to
// do extra some tricks when using `declare_class!`.
}
);
// The set of protocols that the type implements
unsafe impl ConformsTo<NSObject> for ASAuthorizationProvider {}
// When defining these types, we can state which protocols they implement
unsafe impl ConformsTo<ASAuthorizationProvider> for ASAuthorizationAppleIDProvider {}
unsafe impl ConformsTo<ASAuthorizationProvider> for ASAuthorizationPasswordProvider {}
unsafe impl ConformsTo<ASAuthorizationProvider> for ASAuthorizationPlatformPublicKeyCredentialProvider {}
unsafe impl ConformsTo<ASAuthorizationProvider> for ASAuthorizationSecurityKeyPublicKeyCredentialProvider {}
unsafe impl ConformsTo<ASAuthorizationProvider> for ASAuthorizationSingleSignOnProvider {}
// Maybe with a macro so that we can `impl AsRef<ASAuthorizationProvider> for X`?
fn main() {
let obj = ASAuthorizationAppleIDProvider::new();
let proto: &ASAuthorizationProvider = obj.as_protocol();
// Do something with the protocol
}
Note: Some protocols are "virtual", see the objc_non_runtime_protocol
attribute, so as part of this it would make sense to store information about the protocol's methods in a way that we could then check that the protocol is properly implemented in declare_class!
(which we already do, but only for non-virtual protocols).
EDIT: Usage of this property is very rare, I confused it with something else.
I took a look at a lot of protocols, as a summary I've tried to categorize the different usage of protocols as follows:
- The user can implement it to change the behaviour of some class (aka. delegates, or delegates in disguise). If not a delegate, these usually have existing classes that implement them.
- Capture common behaviour on a few classes, and the user wants to call those methods.
- Has no methods, meant as a "marker" protocol - may be interesting to use as a generic bound, also interesting to use as a concrete type.
A. Uses instancetype
in return type to refer to the implementing class.
B. Protocols that really require some sort of associated type because of arg/return type in their methods.
C. The protocol is an "informal" protocol in the sense that types don't actually directly implement it, it is mostly there to make it look like they do (?)
Protocols in Foundation
:
NSPortDelegate, NSURLDownloadDelegate, and anything else that ends with `Delegate`: 1
NSDecimalNumberBehaviors: 1
NSExtensionRequestHandling: 1
NSFilePresenter: 1
NSItemProviderWriting: 1
NSItemProviderReading: 1, A
NSCoding: 1, 2, A
NSLocking: 2
NSSecureCoding: 1
NSCopying: 2, B
NSMutableCopying: 2, B
NSFastEnumeration: 2, B
NSDiscardableContent: 1, (2)
NSProgressReporting: 1, (2)
NSURLAuthenticationChallengeSender: 1
NSURLHandleClient: 1
NSURLProtocolClient: 2
NSXPCProxyCreating: 2
Protocols in CoreData
:
NSFetchedResultsSectionInfo: 2
NSFetchRequestResult: 3
Protocols in AppKit
:
NSWindowDelegate, other delegates: 1
NSAccessibilityGroup: 3
NSAccessibility...: 1, 2
NSAlignmentFeedbackToken: 3
NSAnimatablePropertyContainer: 1, 2, A
NSAppearanceCustomization: 2
NSServicesMenuRequestor: 2, C
NSCollectionViewElement: 1
... many more!
Metal
should mostly be case 2 as well (I at least know it doesn't do case A).
The current design with creating concrete types for each protocol, and allowing bounds to use ConformsTo
, seems to work for case 1, 2, 3 and C, but falls a bit short in case A and B.
I think I'll go with defining custom traits for a few of those important cases like NSCopying
, NSMutableCopying
, NSFastEnumeration
and NSCoding
, and then NSItemProviderReading
and NSAnimatablePropertyContainer
will probably just have to live with it being a bit suboptimal (translating instancetype
to Self
is still sound in those cases, just not as precise as it could be).
Wow, can't believe I didn't think of this before: Protocols can have class methods!
They're quite rare though, the only instances in Foundation
+ AppKit
are the following (so understandable that I forgot this):
+[NSSecureCoding supportsSecureCoding] // property
+[NSItemProviderWriting writableTypeIdentifiersForItemProvider] // property
+[NSItemProviderWriting itemProviderVisibilityForRepresentationWithTypeIdentifier:]
+[NSItemProviderReading readableTypeIdentifiersForItemProvider] // property
+[NSItemProviderReading objectWithItemProviderData:typeIdentifier:error:]
+[NSAnimatablePropertyContainer defaultAnimationForKey:]
+[NSPasteboardReading readableTypesForPasteboard:]
+[NSPasteboardReading readingOptionsForType:pasteboard:]
+[NSWindowRestoration restoreWindowWithIdentifier:state:completionHandler:]
Question is: Is that few enough that we'll just shrug it off and say "we won't try to handle this", or do we need to figure out a way to handle this?
We need some way to specify to the macro "this protocol inherits from NSObject
", so that the __inner
field can be NSObject
, and #[derive(Debug, PartialEq, Eq, Hash)]
works.
But maybe the macro can just extract those derives and call let obj: &NSObject = self.as_protocol();
?
Alternatively: We could allow specifying a parent type for the protocols that have a direct parent (such as MTLBuffer
) - this would allow Deref
impls to those as well, which would be beneficial for usability.
I had an idea for how we might even further improve things, but in the interest of speeding this PR up, I moved that to https://github.com/madsmtm/objc2/issues/291.
Missing parts of this is basically just getting declare_class!
to work with ConformsTo
.