Cannot declare a public protocol extension with internal requirements - ios

I am programming a media player app and created my own framework for managing all the player functionality. In this framework I have a public protocol called PlayerControllerType and an internal protocol _PlayerControllerType. In PlayerControllerType I have declared all the methods and properties, which should be accessible from outside the framework. In _PlayerControllerType I have defined a couple of properties, which are used by the concrete types implementing PlayerControllerType inside the framework. One of these types is PlayerController. Its declaration is as follows:
public class PlayerController<Item: Equatable>: NSObject, PlayerControllerType,
_PlayerControllerType, QueueDelegate
Now I want to provide a couple of default implementations for the classes in my framework, which conform to PlayerControllerType and the internal _PlayerControllerType, for example:
import Foundation
import MediaPlayer
public extension PlayerControllerType where Self: _PlayerControllerType, Item == MPMediaItem, Self.QueueT == Queue<Item>, Self: QueueDelegate {
public func setQueue(query query: MPMediaQuery) {
queue.items = query.items ?? []
}
}
This works as expected in Xcode 7 Beta 4. Yesterday I updated to Beta 6 and got this error:
"Extensions cannot be declared public because its generic requirement uses an internal type" (also see screenshot).
I find this error irritating. Of course no type outside of my framework benefits of this extension because it cannot access the internal protocol _PlayerControllerType, but it is very useful for the types inside my framework which implement both PlayerControllerType and _PlayerControllerType.
Is this just a bug in the Swift compiler or is this the intended behavior?
It's is pretty unfortunate that this doesn't work anymore because now I have to put these methods into a newly created base class for all my PlayerControllers.
Any help or feedback would greatly appreciated.
Kai
EDIT:
Here is a shortened example of the protocols and their extensions:
public protocol PlayerControllerType {
typealias Item
var nowPlayingItem: Item {get}
func play()
}
protocol _PlayerControllerType {
var nowPlayingItem: Item {get set}
}
public extension PlayerControllerType where Self: _PlayerControllerType {
/*
I want to provide a default implementation of play() for
all my PlayerControllers in my framework (there is more than one).
This method needs to be declared public, because it implements a
requirement of the public protocol PlayerControllerType.
But it cannot be implemented here, because this extension
has the requirement _PlayerControllerType. It needs this requirement,
because otherwise it cannot set the nowPlayingItem. I don't want to
expose the setter of nowPlayingItem.
I could make a base class for all PlayerControllers, but then I'm
restricted to this base class because Swift does not support
multiple inheritance.
*/
public func play() {
if nowPlayingItem == nil {
nowPlayingItem = queue.first
}
// Other stuff
}
}

You need to declare an access level of the _PlayerControllerType protocol as 'public'.
public protocol _PlayerControllerType {
// some code
}
According to (The Swift Programming Language - Access Control),
A public members cannot be defined as having an internal or private type, because the type might not be available everywhere that the
public variable is used. Classes are declared as internal by default,
so you have to add the public keyword to make them public.
A member (class/protocol/function/variable) cannot have a higher
access level than its parameter types and return type, because the
function could be used in situations where its constituent types are
not available to the surrounding code.

Related

Swift Concurrency: Conforming a 3rd party library to actor isolation

I am in the process of converting some code bases over to using Swift concurrency and am running into a few snags along the way.
The current project I'm working through has a few 3rd party libraries that it relies on, and in one of those libraries, there is a delegate protocol that requires some data values to be returned from its methods.
Here is an example of the type of delegate methods in the library:
public protocol FooDelegate: AnyObject {
func foo() -> CGFloat
}
I'm attempting to return some values from the implementation of the protocol like this:
extension ViewController: FooDelegate {
func foo() -> CGFloat { // <- Cannot satisfy requirement from protocol
view.bounds.height
}
}
Without any modification, the above is implicitly isolated to the MainActor and can not satisfy the requirement from the FooDelegate protocol.
One solution I've tried was to mark the function implementation with nonisolated:
extension ViewController: FooDelegate {
nonisolated func foo() -> CGFloat {
view.bounds.height // <- Cannot be referenced from a non-isolated context
}
}
This did not work though because it references the view controller's view. That results in the view being referenced from a non-isolated synchronous context. (There are also a few other issues with this since any values that are passed through into any of the delegate functions need to conform to Sendable to be able to be passed across actors).
My question is, is there a way to take a 3rd party library and extend it somehow so that it conforms to proper actor isolation without having to modify its source code?

How to add String Extension on Apple DocC

I am checking new Apple documentation on Xcode 13 beta. I am able to build documentation with DocC.
But I am not able to see any documentation created for extensions like String extension, Date extension.
As per example for below case it will not add any documentation.
extension String {
/// Description for DocC
func myMethod() {
}
}
Consider, If I have created an extension for any struct or class, then it is adding documentation like below code
public struct MyStruct {
}
extension MyStruct {
/// Description
func myMethod() {
}
}
Is this default behaviour by apple or I am missing anything?
This is not currently supported. DocC cannot generate documentation for anything but a single module's types. Extensions to types outside your module aren't handled. (It also won't allow you to link to types outside your module using ``...`` references.)

Swift access control on protocol conformance

I have a private protocol defined in a file as below
private protocol testProtocol {
func testFunc1()
func testFunc2()
}
A public class conforms to the above protocol as follows
public class testClass : testProtocol {
func testFunc1() {}
func testFunc2() {}
}
As per apples documentation , the members of a public class get internal access control by default unless it is explicitly set to a different access control modifier.
The documentation also says that a type's conformance to a protocol with a lower access control will make the type's implementation of the protocol access control the same as that of the protocol. In this scenario since the type's access control is public and the protocols access control is private , the methods testfunc1 and testfunc2 should get an access control of private.
When the class is instantiated in a different source file and the methods are accessed as below , the compiler does not show an error which is not expected as the methods should be private as per the guidelines
var test: testClass = testClass()
test.testFunc1()
Is this expected behavior ? Am i missing something?
Apple Documentation says:
When you write or extend a type to conform to a protocol, you must ensure that the type’s implementation of each protocol requirement has at least the same access level as the type’s conformance to that protocol.
According to this I assume that implementing methods testFunc1 and testFunc2 with another access control modifier inside testClass just overrides that from protocol. If you use default protocol implementation of this methods like the following compiler will return error:
extension testProtocol {
func testFunc1() {}
func testFunc2() {}
}
As far as Swift is Protocol Oriented Language with replacing inheritance with protocols it's probably reasonable if you want to change protocol defined access level of function inside your custom class.
According to Apple's documentation:
When you write or extend a type to conform to a protocol, you must ensure that > the type’s implementation of each protocol requirement has at least the same > access level as the type’s conformance to that protocol.
Please be aware of the "at least" in the doc, it means that the as long as the access level of the type's implementation of the protocol requirements is higher or equal to the access level of the protocol, it will be ok. In your case, testFunc1 and testFunc2 from testClass have the default access level of internal, it is higher than access level of private. So actually the two methods in testClass get the access level of internal, and compiler won't treat it as en error.
We can change your code a little bit as follows:
fileprivate protocol TestProtocol {
func testFunc1()
func testFunc2()
}
public class TestClass : TestProtocol {
public func testFunc1() {}
public func testFunc2() {}
}
This piece of code will also compile without an error.

How to access an internal Swift class in Objective-C within the same framework?

Working on a mixed framework. imported inside the Obj-C file but the internal classes are not visible, only the public ones.
The documentation clearly states the internal clasees should be available between Swift and Obj-C:
Importing Swift into Objective-C To import a set of Swift files in the same framework target as your Objective-C code, you don’t
need to import anything into the umbrella header for the framework.
Instead, import the Xcode-generated header file for your Swift code
into any Objective-C .m file you want to use your Swift code from.
Because the generated header for a framework target is part of the
framework’s public interface, only declarations marked with the public
modifier appear in the generated header for a framework target. You
can still use Swift methods and properties that are marked with the
internal modifier from within the Objective-C part of your framework,
as long they are declared within a class that inherits from an
Objective-C class. For more information on access-level modifiers, see
Access Control in The Swift Programming Language (Swift 2).
Code Sample (Create a new project with a framework)
// SwiftObject.swift
public class SwiftObject: NSObject {
public class func doSomething() {}
}
internal class YetAnotherSwiftObject: NSObject {
internal class func doSomething() {}
}
// SomeObject.m file
#implementation SomeObject
- (void)someMethod {
[SwiftObject doSomething];
}
- (void)someOtherMethod {
[YetAnotherSwiftObject doSomething]; // Use of undeclared identifier
}
#end
As indicated in the docs, declarations marked with internal modifier don't appear in the generated header, so the compiler does not know about them and thus complaints. Of course, you could send messages using performSelector approach, but that's not convenient and bug-prone. We just need to help the compiler know that those declarations are there.
First, we need to use #objc attribute variant that allows you to specify name for your symbol in Objective-C:
// SwiftObject.swift
#objc(SWIFTYetAnotherSwiftObject)
internal class YetAnotherSwiftObject: NSObject {
internal class func doSomething() {}
}
And then you just need to create #interface declaration with the methods you want to use in your code - so the compiler will be happy, and also apply SWIFT_CLASS macro with the symbol name you've specified earlier - so the linker would pick the actual implementation:
// SomeObject.m file
SWIFT_CLASS("SWIFTYetAnotherSwiftObject")
#interface YetAnotherSwiftObject : NSObject
+ (void)doSomething;
#end
#implementation SomeObject
- (void)someOtherMethod {
[YetAnotherSwiftObject doSomething]; // Should work now !!!
}
#end
I've used the interface declaration in .m file just for clarity, the better option would be to combine such declarations in .h file, and include it.
By declaring methods in that interface we're making a promise to compiler, and it won't complain if you'll put there a method that does not exist (or with wrong signature, etc.) Obviously, you'll crash in runtime in that case - so be cautious.
For me it just worked by checking: "Allow app extension API only". You find it by going to the project setting, select your target and then it is in the General tab under Deployment Info.
Can someone explain to me, why this does solve the problem?
While the above solution works (https://stackoverflow.com/a/33159964/5945317), it seems overly complicated and unintuitive:
Complicated, because it seems to add more things than necessary – I will provide a smoother solution below.
Unintuitive, because the objc macro SWIFT_CLASS resolves to SWIFT_RUNTIME_NAME, and the provided value is not actually the runtime name – nor is the objc class name in the header matching the Swift attribute param in #objc. Still, surprisingly, the solution works – but to me it is not clear why.
Here is what we have tested in our own project, and believe to be the better solution (using the example above):
// YetAnotherSwiftObject.swift
#objc(OBJCPREFIXYetAnotherSwiftObject)
internal class YetAnotherSwiftObject: NSObject {
#objc internal class func doSomething() {}
}
// OBJCPREFIXYetAnotherSwiftObject.h
#interface OBJCPREFIXYetAnotherSwiftObject : NSObject
+ (void)doSomething;
#end
That's it. The interface looks like a regular objc interface. This gives the added benefit that you can include it in other header files (which you cannot do if you use the SWIFT_CLASS macro, as it comes from the autogenerated Swift header file, which in turn you cannot include in an objc header, due to circular dependency).
On the Swift side, the only thing relevant is that you provide the class with the proper objc name. Mind that I only used the name prefix for language consistency – you can even just use YetAnotherSwiftObject everywhere (i.e., in the objc header and in the #objc attribute in Swift – but you need to keep this attribute with explicit naming in any case, and need to keep it consistent with the class name in the header).
This also makes your life easier if you're in the process of converting your objc framework step by step to Swift. You just keep the objc header as before, and now provide the implementation in Swift.
Methods and properties that are marked with the internal modifier and declared within a class that inherits from an Objective-C class are accessible to the Objective-C runtime.
so let's make use of that:
class MyInternalClass: NSObject {
#objc var internalProperty = 42
}
#interface MyPublicClass()
#end
#implementation MyPublicClass
+ (void) printValue {
Class myInternalClass = NSClassFromString(#"MyPackageNameIfAny.MyInternalClass");
id myInternalClassInstance = [myInternalClass new];
int value = [myInternalClassInstance performSelector:#selector(internalProperty)];
NSLog(#"Value is %d ", value); // "value is 42"
}
#end
Using the SWIFT_CLASS macro and #objc class attribute could easily lead to errors when archiving. This approach is safer.

Typhoon - How to inject parameter which conforms to PROTOCOL instead of CLASS

I have class which represents logged user
public class User: NSObject {
init(authenticator: Authenticator) {
self.authenticator = authenticator
}
...
}
Its only initial arguments is object which conforms to Authenticator protocol
protocol Authenticator
{
func authenticate(login:String , password:String , handler: (result:AuthenticationResult)->() )
}
In my case the Auth object is instance of class BackendService
My typhoon assembly definition is:
public dynamic func user() -> AnyObject {
return TyphoonDefinition.withClass(User.self) {
(definition) in
definition.useInitializer("initWithAuthenticator") {
(initializer) in
initializer.injectParameterWith( self.backendService() )
}
}
}
Application cause runtime-error
'Method 'initWithAuthenticator' has 0 parameters, but 1 was injected. Do you mean 'initWithAuthenticator:'?'
If i change init method to 'initWithAuthenticator:' it crashes with
'Method 'initWithAuthenticator:' not found on 'PersonalMessages.User'. Did you include the required ':' characters to signify arguments?'
At the present time, it is necessary to add the '#objc' directive to Swift protocols to have them be available for dependency injection with Typhoon. Without it the objective-c runtime's introspection and dynamic dispatch features arent available and these are required.
Similarly, in the case of a class it must extend from NSObject or have the '#objc' directive, otherwise it will also use C++ style vtable dispatch and have (essentially) no reflection. In the case of private vars or methods, they must also have the 'dynamic' modifier.
While vtable dispatch is faster, it prevents runtime method interception which many of Cocoa's most powerful features, such as KVO rely on. So both paradigms are important and its impressive that Swift can switch between them. In the case of protocols though, using the '#objc' directive is a little unfortunate as it implies a 'legacy' behavior. Perhaps 'dynamic' would've been better?
dynamic protocol Authenticator //Not supported but would've been a nicer than '#objc'?
Or perhaps another way to imply that dynamic behavior is required would be to have the protocol extend the NSObject protocol, however this does not work. So using '#objc' is the only choice.
Meanwhile, for classes the requirement to extend NSObject isn't really noticible as far as working with Cocoa/Touch apps goes.

Resources