How, in Swift, do I access a property with dynamic type checking, like Obj-C's id? - swift

I have a function:
func logLocalisedDescription(_ error: Any) {
NSLog(error.localizedDescription)
}
I want this to work for NSError and SKError, both of which have a localizedDescription, but no common superclass or protocol that declares it.
Is there a way to tell the Swift compiler to do run-time rather than compile-time type checking, like Objective-C's id? I tried Any, AnyObject and AnyClass, but none of them worked.
I'm fine with the code failing at runtime if the property doesn't exist.
Please don't suggest retrofitting classes to adopt protocols or other solutions. I'm aware of those. The question is about how to do dynamic typing in Swift.

Use a protocol :)
protocol LocalizedDescribable {
var localizedDescription: String { get }
}
extension NSError: LocalizedDescribable {}
extension SKError: LocalizedDescribable {}
func logLocalizedDescription(_ error: LocalizedDescribable) {
NSLog(error.localizedDescription)
}

You're asking about sending arbitrary Objective-C messages to an object of unknown type. I would call that dynamic messaging rather than dynamic type checking. In Objective-C, there are ways to make any object handle any message, regardless of the object's type.
Swift (on Apple platforms) allows you to do Objective-C-style dynamic messaging to AnyObject, as documented under “Accessing Objective-C Methods and Properties” in the AnyObject reference. In this case, here's the syntax:
func logLocalizedDescription(_ error: Any) {
if let d = (error as AnyObject).localizedDescription as String? {
NSLog("%#", d)
}
}
I had to explicitly specify the type of the message here to avoid an “Ambigious use of 'localizedDescription'” error, because there are multiple definitions of localizedDescription with different types (most are String, but Progress.localizedDescription is String!).
However, you don't need to use dynamic messaging in this particular case. The preferred Swift way, in this case, is to use as? Error, like this:
func logLocalizedDescription(_ error: Any) {
if let error = error as? Error {
NSLog("%#", error.localizedDescription)
} else {
NSLog("unknown error: %#", String(describing: error))
}
}
As it happens, SKError already conforms to Error, although it's not documented to do so. The above function will print an SKError's localizedDescription.
If you don't want to rely on this undocumented conformance, you can explicitly make it conform:
extension SKError: Error { }
Since Error declares localizedDescription, and SKError already implements localizedDescription, this retroactive conformance requires no implementation of its own.
Note also that it's inappropriate to pass an unknown string (like an error's localizedDescription) as the first argument of NSLog, because the first argument of NSLog is a format string. If the string contains unexpected % characters, the runtime behavior is undefined.

Related

Can a protocol define subscript(keyPath:) without an explicit implementation in the adopting object?

Since Swift 4, objects have gained subscript(keyPath:) which can be used to retrieve values using AnyKeyPath and its subclasses. According to the Swift book, the subscript is available on all types. For example, an instance of a class TestClass may be subscripted with an AnyKeyPath like so:
class TestClass {
let property = true
}
let anyKeyPath = \TestClass.property as AnyKeyPath
_ = TestClass()[keyPath: anyKeyPath]
This compiles correctly as expected. Use of any other valid subclass would also compile including PartialKeyPath<TestClass>, KeyPath<TestClass, Bool>, etc. This functionality is unavailable in a protocol extension. For example, the following is invalid:
class TestClass {
let property = true
}
protocol KeyPathSubscriptable {
}
extension KeyPathSubscriptable {
func test() {
let anyKeyPath = \TestClass.property as AnyKeyPath
_ = self[keyPath: anyKeyPath] // Value of type 'Self' has no subscripts
}
}
If we want to use that keyPath subscript in the protocol, we can include it in the protocol definition. However, the compiler will not resolve it automatically:
protocol KeyPathSubscriptable {
subscript(keyPath: AnyKeyPath) -> Any? { get }
}
extension KeyPathSubscriptable {
func test() {
let anyKeyPath = \TestClass.property as AnyKeyPath // This can be any valid KeyPath
_ = self[keyPath: anyKeyPath]
}
}
class TestClass: KeyPathSubscriptable { // Type 'TestObject' does not conform to protocol 'KeyPathSubscriptable'
let property = true
}
With this, we get a compile error: Type 'TestObject' does not conform to protocol 'KeyPathSubscriptable'. In order to resolve this, we must include a redundant implementation of that subscript in TestClass:
class TestClass: KeyPathSubscriptable {
let property = true
subscript(keyPath: AnyKeyPath) -> Any? {
fatalError() // This is never executed
}
}
This resolves the conformance issue and produces the goal result although it is seemingly unnecessary and illogical. I'm not sure how, but the subscript implementation is never even used. It's finding the expected implementation of subscript(keyPath:) and using that instead, but how? Where is that and is there any way to use it in a protocol? Why is this required by the compiler even though it's never used?
The context of this use case is in a logging module. The goal is that an object should be able to adopt a particular protocol which, with no additional setup on the object, would provide a human readable description of the object, instead of the default for many objects which is a memory address. The protocol would use Mirror to fetch KeyPaths of an object, read the values, and print them to the console. It is intended for debugging purposes and would not run in any production environment.
Please let me know if I can make any clarifications. I may post this to the Swift team if others think that this could potentially be a bug of sorts. All help is appreciated. Thanks in advance.
Full gist located here.

Extension Error Swift 3 JSON

I'm trying to migrate my app to Swift 3 but I have troubles.
The code section that struggles me is:
extension JSON: Swift.BooleanType {
//Optional bool
public var bool: Bool? {
get {
switch self.type {
case .bool:
return self.object.boolValue
default:
return nil
}
}
set {
if newValue != nil {
self.object = NSNumber(value: newValue! as Bool)
} else {
self.object = NSNull()
}
}
}
On first line is where xCode throws an error:
extension JSON: Swift.BooleanType {
The error says: Inheritance from non-protocol type 'BooleanType' (aka 'Bool')
Does anybody know what's happening there?
Simplest solution is here,
Bad : extension JSON: Swift.BooleanType {
Good : extension JSON: {
Reason : Admittedly, I am using this to modify SwiftyJSON which is a framework for processing JSON data. In doing some research it seems that they didn't allow BooleanType to allow for inheritance. The suggested means of dealing with this is simply to remove the type.
link is given : https://forums.developer.apple.com/thread/53405
Protocols are Swift's equivalent of Java interfaces. If you've never worked with interfaces before, they're classes absent of any concrete implementation. They exist to describe the skeleton of a class (the attribute and method names it should have) without actually implementing them so that other classes that inherit from the interface can flesh them out later. In Swift they're particularly useful for implementing the Delegate pattern.
Boolean is not a protocol. It is very much a living, breathing concrete Type with an existing implementation. To do what you want to do you either need to override the existing get/set method for Boolean type or create your own Boolean type as described in the official Apple Swift Blog.

Swift type conformance to Any

Does every type in swift, apart from functions, conform to Any? I understand that AnyObject represents all class types and Any is at a higher level than that, representing all other types. However I came across a situation recently where I had a function that looked like:
func myFunFunction(someArgument: Any) {
...
}
And when calling it with a String argument: myFunFunction("This is a string") I get an error saying that type String can't be converted to type Any.
Edit:
The above example is just that, an example. I wanted to simplify the code just to get an answer but here's my actual code:
I have a class final class CollectionViewBinder: NSObject, UICollectionViewDataSource, UICollectionViewDelegate { }
that serves as a collection view binding helper for ReactiveCocoa.
The init method:
init(collectionView: UICollectionView, dataSignal: SignalProducer<[[Any]], NoError>, supplementarySignal: SignalProducer<[[Any]], NoError>?) { ... }
If the type of dataSignal is SignalProducer<[[String]], NoError>, that's when I get the error saying type SignalProducer<[[String]], NoError> can't be converted to type SignalProducer<[[Any]], NoError> which would lead me to think my error is something to do with ReactiveCocoa possibly?
You are right about Any, apple docs say:
Any can represent an instance of any type at all, including function types.
The example you gave works for me without any problems. Usually in this kind of situations the error is in some other place but the compiler gives a misleading message.
What you expect is called "generic covariance" and is currently not supported by Swift.
https://nomothetis.svbtle.com/type-variance-in-swift

Swift: Overriding Self-requirement is allowed, but causes runtime error. Why?

I just started to learn Swift (v. 2.x) because I'm curious how the new features play out, especially the protocols with Self-requirements.
The following example is going to compile just fine, but causes arbitrary runtime effects to happen:
// The protocol with Self requirement
protocol Narcissistic {
func getFriend() -> Self
}
// Base class that adopts the protocol
class Mario : Narcissistic {
func getFriend() -> Self {
print("Mario.getFriend()")
return self;
}
}
// Intermediate class that eliminates the
// Self requirement by specifying an explicit type
// (Why does the compiler allow this?)
class SuperMario : Mario {
override func getFriend() -> SuperMario {
print("SuperMario.getFriend()")
return SuperMario();
}
}
// Most specific class that defines a field whose
// (polymorphic) access will cause the world to explode
class FireFlowerMario : SuperMario {
let fireballCount = 42
func throwFireballs() {
print("Throwing " + String(fireballCount) + " fireballs!")
}
}
// Global generic function restricted to the protocol
func queryFriend<T : Narcissistic>(narcissistic: T) -> T {
return narcissistic.getFriend()
}
// Sample client code
// Instantiate the most specific class
let m = FireFlowerMario()
// The call to the generic function is verified to return
// the same type that went in -- 'FireFlowerMario' in this case.
// But in reality, the method returns a 'SuperMario' and the
// call to 'throwFireballs' will cause arbitrary
// things to happen at runtime.
queryFriend(m).throwFireballs()
You can see the example in action on the IBM Swift Sandbox here.
In my browser, the output is as follows:
SuperMario.getFriend()
Throwing 32 fireballs!
(instead of 42! Or rather, 'instead of a runtime exception', as this method is not even defined on the object it is called on.)
Is this a proof that Swift is currently not type-safe?
EDIT #1:
Unpredictable behavior like this has to be unacceptable.
The true question is, what exact meaning the keyword Self (capital first letter) has.
I couldn't find anything online, but there are at least these two possibilities:
Self is simply a syntactic shortcut for the full class name it appears in, and it could be substituted with the latter without any change in meaning. But then, it cannot have the same meaning as when it appears inside a protocol definition.
Self is a sort of generic/associated type (in both protocols and classes) that gets re-instantiated in deriving/adopting classes. If that is the case, the compiler should have refused the override of getFriend in SuperMario.
Maybe the true definition is neither of those. Would be great if someone with more experience with the language could shed some light on the topic.
Yes, there seems to be a contradiction. The Self keyword, when used as a return type, apparently means 'self as an instance of Self'. For example, given this protocol
protocol ReturnsReceived {
/// Returns other.
func doReturn(other: Self) -> Self
}
we can't implement it as follows
class Return: ReturnsReceived {
func doReturn(other: Return) -> Self {
return other // Error
}
}
because we get a compiler error ("Cannot convert return expression of type 'Return' to return type 'Self'"), which disappears if we violate doReturn()'s contract and return self instead of other. And we can't write
class Return: ReturnsReceived {
func doReturn(other: Return) -> Return { // Error
return other
}
}
because this is only allowed in a final class, even if Swift supports covariant return types. (The following actually compiles.)
final class Return: ReturnsReceived {
func doReturn(other: Return) -> Return {
return other
}
}
On the other hand, as you pointed out, a subclass of Return can 'override' the Self requirement and merrily honor the contract of ReturnsReceived, as if Self were a simple placeholder for the conforming class' name.
class SubReturn: Return {
override func doReturn(other: Return) -> SubReturn {
// Of course this crashes if other is not a
// SubReturn instance, but let's ignore this
// problem for now.
return other as! SubReturn
}
}
I could be wrong, but I think that:
if Self as a return type really means 'self as an instance of
Self', the compiler should not accept this kind of Self requirement
overriding, because it makes it possible to return instances which
are not self; otherwise,
if Self as a return type must be simply a placeholder with no further implications, then in our example the compiler should already allow overriding the Self requirement in the Return class.
That said, and here any choice about the precise semantics of Self is not bound to change things, your code illustrates one of those cases where the compiler can easily be fooled, and the best it can do is generate code to defer checks to run-time. In this case, the checks that should be delegated to the runtime have to do with casting, and in my opinion one interesting aspect revealed by your examples is that at a particular spot Swift seems not to delegate anything, hence the inevitable crash is more dramatic than it ought to be.
Swift is able to check casts at run-time. Let's consider the following code.
let sm = SuperMario()
let ffm = sm as! FireFlowerMario
ffm.throwFireballs()
Here we create a SuperMario and downcast it to FireFlowerMario. These two classes are not unrelated, and we are assuring the compiler (as!) that we know what we are doing, so the compiler leaves it as it is and compiles the second and third lines without a hitch. However, the program fails at run-time, complaining that it
Could not cast value of type
'SomeModule.SuperMario' (0x...) to
'SomeModule.FireFlowerMario' (0x...).
when trying the cast in the second line. This is not wrong or surprising behaviour. Java, for example, would do exactly the same: compile the code, and fail at run-time with a ClassCastException. The important thing is that the application reliably crashes at run-time.
Your code is a more elaborate way to fool the compiler, but it boils down to the same problem: there is a SuperMario instead of a FireFlowerMario. The difference is that in your case we don't get a gentle "could not cast" message but, in a real Xcode project, an abrupt and terrific error when calling throwFireballs().
In the same situation, Java fails (at run-time) with the same error we saw above (a ClassCastException), which means it attempts a cast (to FireFlowerMario) before calling throwFireballs() on the object returned by queryFriend(). The presence of an explicit checkcast instruction in the bytecode easily confirms this.
Swift on the contrary, as far as I can see at the moment, does not try any cast before the call (no casting routine is called in the compiled code), so a horrible, uncaught error is the only possible outcome. If, instead, your code produced a run-time "could not cast" error message, or something as gracious as that, I would be completely satisfied with the behaviour of the language.
The issue here seems to be a violation in contract:
You define getFriend() to return an instance of receiver (Self). The problem here is that SuperMario does not return self but it returns a new instance of type SuperMario.
Now, when FireFlowerMario inherits that method the contract says that the method should return a FireFlowerMario but instead, the inherited method returns a SuperMario! This instance is then treated as if it were a FireFlowerMario, specifically: Swift tries to access the instance variable fireballCount which does not exist on SuperMario and you get garbage instead.
You can fix it like this:
class SuperMario : Mario {
required override init() {
super.init()
}
override func getFriend() -> SuperMario {
print("SuperMario.getFriend()")
// Dynamically create new instance of the same type as the receiver.
let myClass = self.dynamicType
return myClass.init()
}
}
Why does the compiler allow it? It has a hard time catching something like this, I guess. For SuperMario, the contract is still valid: the method getFriend does return an instance of the same class. The contract breaks when you create the subclass FireFlowerMario: should the compiler notice that a superclass might violate the contract? This would be an expensive check and probably more suited for a static analyzer, IMHO (Also, what happens if the compiler doesn't have access to SuperMario's source? What happens if that class is from a library?)
So it's actually SuperMario's duty to ensure that the contract is still valid when subclassing.

The strange behaviour of Swift's AnyObject

In messing around with Swift today I came across a strange thing. Here's the unit test I developed which shows some unexpected behaviours when using Swift's AnyObject.
class SwiftLanguageTests: XCTestCase {
class TestClass {
var name:String?
var xyz:String?
}
func testAccessingPropertiesOfAnyObjectInstancesReturnsNils() {
let instance = TestClass()
instance.xyz = "xyz"
instance.name = "name"
let typeAnyObject = instance as AnyObject
// Correct: Won't compile because 'xyz' is an unknown property in any class.
XCTAssertEqual("xyz", typeAnyObject.xyz)
// Unexpected: Will compile because 'name' is a property of NSException
// Strange: But returns a nil when accessed.
XCTAssertEqual("name", typeAnyObject.name)
}
}
This code is a simplification of some other code where there is a Swift function that can only return a AnyObject.
As expected, after creating an instance of TestClass, casting it to AnyObject and setting another variable, accessing the property xyz won't compile because AnyObject does not have such a property.
But surprisingly a property called name is accepted by the compiler because there is a property by that name on NSException. It appears that Swift is quite happy to accept any property name as long as it exists somewhere in the runtime.
The next unexpected behaviour and the thing that got all this started is that attempting to access the name property returns a nil. Watching the various variables in the debugger, I can see that typeAnyObject is pointing at the original TestClass instance and it's name property has a value of "name".
Swift doesn't throw an error when accessing typeAnyObject.name so I would expect it to find and return "name". But instead I get nil.
I would be interested if anyone can shed some light on what is going on here?
My main concern is that I would expect Swift to either throw an error when accessing a property that does not exist on AnyObject, or find and return the correct value. Currently neither is happening.
Similar as in Objective-C, where you can send arbitrary messages to id,
arbitrary properties and methods can be called on an instance of AnyObject
in Swift. The details are different however, and it is documented in
Interacting with Objective-C APIs
in the "Using Swift with Cocoa and Objective-C" book.
Swift includes an AnyObject type that represents some kind of object. This is similar to Objective-C’s id type. Swift imports id as AnyObject, which allows you to write type-safe Swift code while maintaining the flexibility of an untyped object.
...
You can call any Objective-C method and access any property on an AnyObject value without casting to a more specific class type. This includes Objective-C compatible methods and properties marked with the #objc attribute.
...
When you call a method on a value of AnyObject type, that method call behaves like an implicitly unwrapped optional. You can use the same optional chaining syntax you would use for optional methods in protocols to optionally invoke a method on AnyObject.
Here is an example:
func tryToGetTimeInterval(obj : AnyObject) {
let ti = obj.timeIntervalSinceReferenceDate // NSTimeInterval!
if let theTi = ti {
print(theTi)
} else {
print("does not respond to `timeIntervalSinceReferenceDate`")
}
}
tryToGetTimeInterval(NSDate(timeIntervalSinceReferenceDate: 1234))
// 1234.0
tryToGetTimeInterval(NSString(string: "abc"))
// does not respond to `timeIntervalSinceReferenceDate`
obj.timeIntervalSinceReferenceDate is an implicitly unwrapped optional
and nil if the object does not have that property.
Here an example for checking and calling a method:
func tryToGetFirstCharacter(obj : AnyObject) {
let fc = obj.characterAtIndex // ((Int) -> unichar)!
if let theFc = fc {
print(theFc(0))
} else {
print("does not respond to `characterAtIndex`")
}
}
tryToGetFirstCharacter(NSDate(timeIntervalSinceReferenceDate: 1234))
// does not respond to `characterAtIndex`
tryToGetFirstCharacter(NSString(string: "abc"))
// 97
obj.characterAtIndex is an implicitly unwrapped optional closure. That code
can be simplified using optional chaining:
func tryToGetFirstCharacter(obj : AnyObject) {
if let c = obj.characterAtIndex?(0) {
print(c)
} else {
print("does not respond to `characterAtIndex`")
}
}
In your case, TestClass does not have any #objc properties.
let xyz = typeAnyObject.xyz // error: value of type 'AnyObject' has no member 'xyz'
does not compile because the xyz property is unknown to the compiler.
let name = typeAnyObject.name // String!
does compile because – as you noticed – NSException has a name property.
The value however is nil because TestClass does not have an
Objective-C compatible name method. As above, you should use optional
binding to safely unwrap the value (or test against nil).
If your class is derived from NSObject
class TestClass : NSObject {
var name : String?
var xyz : String?
}
then
let xyz = typeAnyObject.xyz // String?!
does compile. (Alternatively, mark the class or the properties with #objc.)
But now
let name = typeAnyObject.name // error: Ambigous use of `name`
does not compile anymore. The reason is that both TestClass and NSException
have a name property, but with different types (String? vs String),
so the type of that expression is ambiguous. This ambiguity can only be
resolved by (optionally) casting the AnyObject back to TestClass:
if let name = (typeAnyObject as? TestClass)?.name {
print(name)
}
Conclusion:
You can call any method/property on an instance of AnyObject if that
method/property is Objective-C compatible.
You have to test the implicitly unwrapped optional against nil or
use optional binding to check that the instance actually has that
method/property.
Ambiguities arise if more than one class has (Objective-C) compatible
methods with the same name but different types.
In particular because of the last point, I would try to avoid this
mechanism if possible, and optionally cast to a known class instead
(as in the last example).
it has nothing with NSException!
from Apple documentation:
protocol AnyObject { ... }
The protocol to which all classes implicitly conform.
When used as a concrete type, all known #objc methods and properties are available, as implicitly-unwrapped-optional methods and properties respectively, on each instance of AnyObject
name is #objc property, xyz is not.
try this :-)
let typeAnyObject = instance as Any
or
#objc class TestClass: NSObject {
var name:String?
var xyz:String? }
let instance = TestClass() instance.xyz = "xyz" instance.name = "name"
let typeAnyObject = instance as AnyObject
typeAnyObject.name // will not compile now