I'm having a bit of trouble understanding why this won't compile. The compiler throws the error the error: All stored properties of a class instance must be initialized before returning nil from an initializer.
The error message itself doesn't quite make sense, as a failed initialization is just that, failed. The stored properties should only need to be initialized if the object is actually initialized, no?
When I omit the else { return nil }, the compiler throws a different error: Use of 'self' in delegating initializer before 'self.init' is called.
extension NSURL {
convenience init?(optionalString: String?) {
if let string = optionalString {
self.init(string: string)
} else {
return nil
}
}
}
The init that you are calling self.init(string: string) is itself a convenience initialiser - you should call the full designated initialiser init?(string URLString: String, relativeToURL baseURL: NSURL?).
Related
In the past we've used Objective-C to anonymously get the sharedInstance of a class this way:
+ (nullable NSObject *)sharedInstanceForClass:(nonnull Class)aClass
{
// sharedPropertyProvider
NSObject<KVASharedPropertyProvider> *sharedPropertyProvider = [aClass conformsToProtocol:#protocol(KVASharedPropertyProvider)]
? (NSObject<KVASharedPropertyProvider> *)aClass
: nil;
if (sharedPropertyProvider == nil)
{
return nil;
}
// return
return [sharedPropertyProvider.class sharedInstance];
}
It's protocol based. We put this protocol on every class we have with a shared instance where we need to do this.
#objc (KVASharedPropertyProvider)
public protocol KVASharedPropertyProvider: AnyObject
{
#objc (sharedInstance)
static var sharedInstance: AnyObject { get }
}
The above works fine in Objective-C (and when called from Swift). When attempting to write the same equivalent code in Swift, however, there appears to be no way to do it. If you take this specific line(s) of Objective-C code:
NSObject<KVASharedPropertyProvider> *sharedPropertyProvider = [aClass conformsToProtocol:#protocol(KVASharedPropertyProvider)]
? (NSObject<KVASharedPropertyProvider> *)aClass
: nil;
And attempt to convert it to what should be this line of Swift:
let sharedPropertyProvider = aClass as? KVASharedPropertyProvider
... initially it appears to succeed. The compiler just warns you that sharedPropertyProvider isn't be used. But as soon as you attempt to use it like so:
let sharedInstance = sharedPropertyProvider?.sharedInstance
It gives you the compiler warning back on the previous line where you did the cast:
Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type
'KVASharedPropertyProvider' always fails
Any ideas? Is Swift simply not capable of casting AnyClass to a protocol in the same way that it could be in Objective-C?
In case you're wondering why we need to do this, it's because we have multiple xcframeworks that need to operate independently, and one xcframework (a core module) needs to optionally get the shared instance of a higher level framework to provide special processing if present (i.e. if installed) but that processing must be initiated from the lower level.
Edit:
It was asked what this code looked like in Swift (which does not work). It looks like this:
static func shared(forClass aClass: AnyClass) -> AnyObject?
{
guard let sharedPropertyProvider = aClass as? KVASharedPropertyProvider else
{
return nil
}
return type(of: sharedPropertyProvider).sharedInstance
}
The above generates the warning:
Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type
'KVASharedPropertyProvider' always fails
It was suggested I may need to use KVASharedPropertyProvider.Protocol. That looks like this:
static func shared(forClass aClass: AnyClass) -> AnyObject?
{
guard let sharedPropertyProvider = aClass as? KVASharedPropertyProvider.Protocol else
{
return nil
}
return type(of: sharedPropertyProvider).sharedInstance
}
And that generates the warning:
Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type
'KVASharedPropertyProvider.Protocol' always fails
So, I assume you have something like this
protocol SharedProvider {
static var shared: AnyObject { get }
}
class MySharedProvider: SharedProvider {
static var shared: AnyObject = MySharedProvider()
}
If you want to use AnyObject/AnyClass
func sharedInstanceForClass(_ aClass: AnyClass) -> AnyObject? {
return (aClass as? SharedProvider.Type)?.shared
}
Better approach
func sharedInstanceForClass<T: SharedProvider>(_ aClass: T.Type) -> AnyObject {
return T.shared
}
I have a convenience initializer that calls onto an initializer that calls onto a super class initializer with header init!(data: Data!). What I don't understand is why I am getting the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
I have tried initializing a random Data object with no data but I still obtain this error any ideas?
Code:
Convenience init(itemId: String) {
let d: Data = Data.init()
self.init(data: d) // error here!
}
required init!(data: Data?) {
super.init(data: data)
}
super class from obj c library:
-(id)initWithData:(NSData*)data {
if ([self init]) {
//some code
}
return nil
}
You're using implicitly unwrapped optional parameters and initializers. If you'd like to have a failable initializer, you should use init? instead. As for passing in an argument that is implicitly unwrapped. That is really dangerous, too. Marking things as implicitly unwrapped is very convenient for avoiding the Swift compiler complaining, but it will often lead to this problem.
The problem is your call to super.init is returning nil.
This example shows you a safe way to do failable initialization.
// This is a stand in for what your super class is doing
// Though I can't tell you why it is returning nil
class ClassB {
required init?(data: Data?) {
return nil
}
}
class ClassA: ClassB {
convenience init?(itemId: String) {
let d: Data = Data.init()
self.init(data: d) // error here!
}
required init?(data: Data?) {
super.init(data: data)
}
}
if let myObject = ClassA.init(itemId: "ABC") {
// non-nil
} else {
// failable initializer failed
}
I have the following class:
class Foo {
let a : Int?
let b : Int?
init?(){
}
}
I get the error "constant self.a used before being initialised" in the failable initialiser. What on earth is the compiler talking about? I haven't used a at all yet!
The problem is that each property declared with let in a class must be populated before the init does return.
In your case the init is not populating the 2 constant properties.
In Swift 2.1 each constant property of a class must be populated even when a failable initializer does fail.
class Foo {
let a: Int?
let b: Int?
init?() {
return nil // compile error
}
}
More details here.
Struct
On the other hand you can use a struct where a failable initializer can return nil without populating all the let properties.
struct Person {
let name: String
init?(name:String?) {
guard let name = name else { return nil }
self.name = name
}
}
I have a simple Result object which should contain an object (class, struct or enum) and a Bool to say whether it was cancelled or not. I need to interrogate this object along its path (before it gets to its destination, where the destination knows what kind of object to expect) to determine whether it was cancelled or not (without worrying about the accompanying object at that moment). My object looks like:
import Foundation
#objc protocol Resultable {
var didCancel: Bool { get }
}
class Result<T>: Resultable {
let didCancel: Bool
let object: T?
init(didCancel: Bool, object: T?) {
self.didCancel = didCancel
self.object = object
}
}
The idea being that my Result object can wrap the didCancel flag and the actual object (which can be of any type), and the fact that it implements the Resultable protocol means that I can interrogate it at any point to see whether it was cancelled by casting it to Resultable.
I understand (while not liking it) that the protocol has to be prefixed with #objc so that we can cast to it (according to the Apple docs). Unfortunately, when I run the code below (in a playground or in a project), I get a nasty "does not implement methodSignatureForSelector:" error message:
let test = Result(didCancel: false, object: NSArray())
println(test.didCancel)
// transform the object into the form it will be received in
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel)
}
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject (and this is not a requirement that Apple makes explicit). This is obviously a problem for a generic class.
Is there anything I am missing here? Any way to get this to work? Failing that, are there any workarounds (although I strongly believe that this kind of thing should be possible - perhaps we can hope that Apple will do away with the #objc protocol casting requirement at some stage)?
UPDATE
It looks like this is solved with Swift 1.2
You can now cast to a non-ObjC protocol, and without the object having to inherit from NSObject.
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject
This is not true. For example, the following code works as expected:
#objc protocol MyProtocol {
var flag: Bool { get }
}
class MyClass: MyProtocol {
let flag = true
}
let foo:AnyObject = MyClass()
if let p = foo as? MyProtocol {
println(p.flag)
}
The problems is that: Any methods/properties declared in Swift Generic classes are invisible from Objective-C. Hence, from the perspective of #objc protocol Resultable, didCancel property declared in Result<T> is invisible. That's why methodSignatureForSelector: is called.
The workaround here is very annoying: You have to have non Generic base class for Result that implements didCancel.
#objc protocol Resultable {
var didCancel: Bool { get }
}
class ResultBase: Resultable {
let didCancel: Bool
init(didCancel: Bool) { self.didCancel = didCancel }
}
class Result<T>: ResultBase, Resultable {
// ^^^^^^^^^^^^
// You have to explicitly conforms `Resultable` here as well for some reason :/
let object: T?
init(didCancel: Bool, object: T?) {
self.object = object
super.init(didCancel: didCancel)
}
}
let test = Result(didCancel: false, object: NSArray())
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel) // -> outputs "false"
}
Here is an older question on the same topic, but it is for Swfit 1.0. In Swift 1.1, the StringLiteralConvertible protocol has changed to use initializers instead of class methods. Also, [NSURL init(string: String)] become a failable initializer.
This is what I've tried but it doesn't compile in Xcode 6.1.
extension NSURL: StringLiteralConvertible {
convenience public init?(stringLiteral value: String) {
self.init(string: value)
}
convenience public init?(extendedGraphemeClusterLiteral value: String) {
self.init(string: value)
}
convenience public init?(unicodeScalarLiteral value: String) {
self.init(string: value)
}
}
The initializers required by the protocol "StringLiteralConvertible" do not return optionals, so putting a ? after init won't help (even though XCode itself suggests it). But all initializers for NSURL do return optionals, because the parameters may not result in a valid URL. And you have to call one of the super.init initializers in all custom NSURL initializers. So it is no longer possible to have NSURL implement "StringLiteralConvertible".