The rule for access control for required initializer seems to be different than one that does not specify required. Why?
public class A {
// required init() must be public, why?
public required init() { }
}
public class B {
// init() does not need to be public, why?
init() { }
}
First, let's make the rule clear. It is not required for required initializers to be marked as public. It is only required that required initializers be as accessible as the class is. If your class is public, it's required initializers must also be public. If your class is internal, its required initializers must also be internal (technically, you could make it public, but that'd make no sense and generates a warning). And of course, if your class is private, the required initializer should also be private.
So, why?
There are two reasons here, but they require an understanding of what the required keyword is actually doing.
First of all, the required keyword is guaranteeing this class and all of its subclasses implement this particular initializer. One of the main reasons to make an initializer required is for protocol conformance, with the most popular example of this being NSCoding, which requires the init(coder:) initializer. So with that in mind, let's consider a class which is trying to implement this protocol:
public class MySwiftClass: NSObject, NSCoding {
// some implementations
// including the two requirements of the NSCoding protocol
}
Now, consider trying to use this:
let mySwiftObject = MySwiftClass(coder: aCoder)
We should be able to do this without problem, right? I mean, after all, MySwiftClass conforms to NSCoding protocol, and NSCoding protocol guarantees there will be an init(coder:) initializer.
But if you were allowed to mark init(coder:) as a lower access level than the class had, there would be a scope within which the class can be seen, but its required initializer could not be accessed... so despite knowing that this class conforms to a protocol with a required initializer or is inherited from a parent class with a required initializer, we'd somehow not be able to call that required initializer because for the scope we are in, it would appear to not exist.
The second reason is for subclassing itself.
Let's take this example parent class:
public class ParentClass {
required init() {}
}
We want the zero-argument initializer to be required. That means, if anything inherits from ParentClass, it must also be sure that the zero-argument initializer is implemented. But if we are allowed to let the required initializer to have a lesser scope than the class itself, then there is a scope within which we can see the class, but we cannot see the required initializer, so how can subclasses created in that scope manage to even know there is a required initializer that they must implement?
Related
Assume we have the following example code:
protocol MyProtocol {
func someFunction()
}
public class MyClass {
}
public extension MyClass: MyProtocol {
func someFunction() {
print("hello")
}
}
Compiling the code above gives the following error:
Error: 'public' modifier cannot be used with extensions that declare protocol conformances
The same thing occurs if I mark the extension as private. It seems as though you cannot set the access level of an extension that conforms to a protocol, regardless of what the access level is set to. Even setting the protocol declaration to public or private does not remove the error.
Question
What is the reasoning for Swift restricting an extension's access level in this way if it conforms to a protocol? If the protocol conformance is applied at the class level, there is no such restriction.
If I obey the compiler and remove the private/public designation, what is the access level of someFunction()?
extension MyClass: MyProtocol {
func someFunction() {
print("hello")
}
}
I imagine in this case it would follow the original MyClass implementation and be public but I am not sure.
Is this behavior there because a protocol conformance in an extension means the entire class conforms to the protocol, and therefore it is redundant to re-specify the access level in the extension?
It's because it's impossible to conform to a protocol at any access level other than the access level of the protocol itself. In other words, if you have a public protocol, you cannot have private conformance to it. This is partially because protocol conformance is something that can be queried for at runtime (and therefore cannot differ between what module you're in, or be implemented twice in different files/modules), and partially because it would just plain be weird if a type conformed to a protocol in one file and didn't conform to that protocol when used in other files.
As for your question of the access level of someFunction, it follows the same rules as any other function. Which is to say, it defaults to internal, unless the type itself has a lower access level. So in your case, if MyClass and MyProtocol are both public, you can expect to get a compiler error telling you that someFunction() needs to be marked public as well. But since it looks like MyProtocol is in fact internal, omitting any access modifier works as someFunction() defaults to internal.
Private conformance might violate Liskov Substitution Principle
Quoting an abstract from apple devloper forum reply to a similar question:
"The biggest thing I've noted about private conformance, especially amonst classes that are meant to be subclassed further, is that you often end up with conflicting implementations."
For example, you have a class that privately conforms to a protocol and implements all of its methods. Later a subclass comes along and wants to do the same, but only wants to implement the required methods (because the optional ones not being implemented might provide some default behavior that subclass wants). But now you have 2 problems:
1) The object expecting this protocol implementation now has possibly 2 consumers of the protocol on the same object. This leads to both objects having to guard against unexpected calls. Or none, as due to the private conformance, the subclass can't call super to resolve the unexpected calls.
2) There is no way for the subclass to get the behavior it wants without modifying the protocol, as the superclass's implementation can't be removed without affecting its behavior either.
Source: Link to Apple Developer forum thread
If I obey the compiler and remove the private/public designation, what is the access level of someFunction()?
Whatever you say it is. Nothing stops you from marking the access level of someFunction(). But in this case you cannot mark it as private, because the access level of MyProtocol is internal.
The default, therefore, is internal in your code. Nothing is ever public by default; public is always an explicitly opt-in designation.
Quote from The Swift Programming Language (Swift 3.1):
Custom initializers can be assigned an access level less than or equal to the type that they initialize. The only exception is for required initializers (as defined in Required Initializers). A required initializer must have the same access level as the class it belongs to.
If so, why does this code compile and work?
private class GoofyClass {
public init(mood: String) {}
public required init(isCrazy: Bool) {}
}
private let shock = GoofyClass(mood: "shocked")
private let crazy = GoofyClass(isCrazy: true)
In Swift, members of a class or struct with a less restrictive access level than the class/struct itself are automatically downgraded to the same level as the class/struct. I believe this is a deliberate design decision on the part of the language designers.
In your case, assuming the class is declared at the top level in the file (i.e. it is not nested inside another type), the inits you have declared public are, in fact, fileprivate.
The only exception is for required initializers (as defined in Required Initializers). A required initializer must have the same access level as the class it belongs to.
This is referring to the fact that you cannot make the access level of a required initialiser more restrictive than its class e.g.
open class Foo
{
internal required init() // error
}
I thought both these classes conformed to the rules of inherited initializers:
class Butt1 : UIButton {}
class Butt2<T> : UIButton {}
let butt1 = Butt1() // ok
let butt2 = Butt2<Void>() // error: no accessible initializer
The docs say:
As mentioned above, subclasses do not inherit their superclass
initializers by default. However, superclass initializers are
automatically inherited if certain conditions are met. In practice,
this means that you do not need to write initializer overrides in many
common scenarios, and can inherit your superclass initializers with
minimal effort whenever it is safe to do so.
Assuming that you provide default values for any new properties you
introduce in a subclass, the following two rules apply:
Rule 1 If your subclass doesn’t define any designated initializers, it
automatically inherits all of its superclass designated initializers.
Rule 2 If your subclass provides an implementation of all of its
superclass designated initializers—either by inheriting them as per
rule 1, or by providing a custom implementation as part of its
definition—then it automatically inherits all of the superclass
convenience initializers.
In the case of Butt2 the compiler doesn't think it's "safe to do so", but why isn't it safe?
Consider the following class hierarchy in Swift:
GMSMarker is a class provided by the GoogleMaps library. It has two public initializers (one designated, one convenience).
MapItem, ClusterItem and Cluster are part of my model. I don't want to allow construction of MapItem objects; only ClusterItems and Clusters. Since Swift doesn't have abstract classes, having a private initializer would be sufficient for my purposes. However, given that MapItem inherits from GMSMarker with a convenience constructor, I can't simply override the designated initializer as private.
Given the rules of initializer inheritance in Swift, the only way that I've been able to prevent construction of the MapItem class is by declaring a new private initializer with a different signature so that the base class initializers aren't inherited.
class MapItem : GMSMarker {
private init(dummyParam: Int) {
super.init()
}
}
So far so good. Now, when trying to create my initializer for the ClusterItem class, I run into a problem.
class ClusterItem : MapItem {
weak var post: Post?
init(post: Post) {
self.post = post
super.init(dummyParam: 0)
}
}
I receive the following compiler error on the line that calls the super.init(): 'MapItem' does not have a member named 'init'.
If I omit the call to super.init, I receive the following error: Super.init isn't called before returning from initializer.
Is there any way of accomplishing what I want in swift. I would like to keep the inheritance hierarchy as is if possible because MapItem contains some common implementation code for ClusterItem and Cluster. Furthermore, I would like to be able to reference both ClusterItems and Clusters using a collection of MapItems.
There are various ways to solve this issue:
Declare the initializer as internal, that will give you module access and your subclass will be able to call it.
Declare the classes in the same file, then private won't be an issue anymore (private enables access from the same file).
Instead of abstract classes, you can make MapItem a protocol and ClusterItem and Cluster could inherit from GMSMarker directly. However, this solution may not be good depending on your exact needs.
For example, these are valid and will compile without (all) protocol stubs
public class ViewController: UIViewController, SFSpeechRecognizerDelegate {
}
class BLEController: CBCentralManager, CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
}
}
Edit: Solved! Creating a class without inheriting from UIViewController or CBCentralManager still does comply with the delegate protocols, but it does not comply with NSObjectProtocol. It just seems to be that I'm attempting to use the frameworks in an unintended way.
Why does my code compile without fulfilling all the protocol requirements?
What you are seeing here are optional protocol requirements. They are here because Swift code needs to interact with Objective-C, and Objective-C has them.
All methods except centralManagerDidUpdateState declared in CBCentralManagerDelegate are optional, from here:
The only required method is centralManagerDidUpdateState(_:); the central manager calls this when its state updates, thereby indicating the availability of the central manager.
And SFSpeechRecognizerDelegate, only contains one optional method, from here:
Use this protocol's optional method to track those changes and provide an appropriate response.
why does the code not compile if you remove the superclasses then?
This is because both of those protocols in your question also inherit from NSObjectProtocol, so they actually also have the additional requirements of NSObjectProtocol. UIViewController and CBCentralManager both inherits from NSObject, so they satisfy the NSObjectProtocol requirements, but your Swift class without a superclass doesn't.
Protocols with optional requirements don't have to inherit from NSObjectProtocol though. It's just that most of them are that way in the framework. You can for example do:
#objc protocol Foo {
#objc optional func foo()
}
class FooClass : Foo {
// compiles fine!
}
It compiles without needing the protocol stubs because all the requirement is optional. Check within the declaration of SFSpeechRecognizerDelegate it has only one requirement for a method called availabilityDidChange and you can see that it's optional from the keyword given at the beginning of the function.
In the second case, the class you have created doesn't inherit from NSObject but the first one does because it's a sub-class of UIViewController which in-turn is a sub-class of NSObject.