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
Related
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.
For a project I am currently working on, it would be very useful to get the KVC-String from a KeyPath instance my method is receiving. Short example:
struct Person {
var name: String
}
let propertyCache = ["name": "something"]
func method<T>(_ keypath: KeyPath<Person, T>) -> T? {
let kvcName = keypath.kvc
return propertyCache[kvcName]
}
This might seem not very useful, but in my project it is :) I found a property on KeyPath called _kvcKeyPathString which is also public, but it returns nil every time I tried.
Or is their maybe a possibility to use reflection there? Thanks in advance for ideas/solutions!
I don't know of a pure Swift way to get the name of the property as a string yet.
But, if you add the #objc attribute to the property then _kvcKeyPathString will actually have a value instead of always being nil. Also, since Swift structs can't be represented in Objective-C, this method only works for classes.
A minimal working example usage:
class SomeClass {
#objc var someProperty = 5
}
let keyPath = \SomeClass.someProperty
print(keyPath._kvcKeyPathString)
I ran into what I think is a strange error in may app. At the bottom of this question is complete code the reproduces what I am seeing in my app, but here is a quick demonstration.
I create two instances of the same class, one is declared as an optional conforming to a protocol the other as an optional of a concrete class
For both I can set the computed property via option chaining ie:
anOptionalInstance?.someComputedProperty = ....
for the concrete version I can set the property by unwrapping the optional
if let anInstance = anOptionalInstance {
anInstance.someComputedProperty = ....
}
For the polymorphic version, I get an error message that says I can't set the property on the instance.
Below is a complete file that reproduces the issue I am seeing.
Can anyone explain what is happening here?
struct MyStruct {
var someMember: String
}
protocol MyProtocol {
var myVar: MyStruct { get set }
}
class MyType: MyProtocol {
var myVar: MyStruct {
get {
return MyStruct(someMember: "some string")
}
set {
println(newValue)
}
}
}
class UsingClass {
var anInstanceOfMyType: MyProtocol?
var anOtherInstanceOfMyType: MyType?
func someMethod() {
anInstanceOfMyType = MyType()
anInstanceOfMyType?.myVar = MyStruct(someMember: "blah")
if let anInstanceOfMyType = anInstanceOfMyType {
// The following line produces this error :Cannot assign to 'myVar' in 'anInstanceOfMyType'
anInstanceOfMyType.myVar = MyStruct(someMember: "blah blah")
}
anOtherInstanceOfMyType = MyType()
anOtherInstanceOfMyType?.myVar = MyStruct(someMember: "blah")
if let anOtherInstanceOfMyType = anOtherInstanceOfMyType {
anOtherInstanceOfMyType.myVar = MyStruct(someMember: "blah blah")
}
}
}
The problem does happen because you are trying to change the property of the constant anInstanceOfMyType which type is MyProtocol.
1. Why anInstanceOfMyType is a constant?
At the first line of UsingClass, anInstanceOfMyType is actually declared as var. However with the Conditional Unwrapping a constant with name anInstanceOfMyType is created, and you are trying to change a property of that constant
2. Ok but anInstanceOfMyType references an instance of a class, so I should be able to change its properties even if it's a constant
Since anInstanceOfMyType has MyProtocol as type, it could contain a struct or a reference an instance of a class.
So the compiler does apply the safer approach and avoid you to change its properties.
Solution
Limit protocol adoption to class types (and not structures or enumerations) by adding the class keyword to a protocol’s inheritance list. The class keyword must always appear first in a protocol’s inheritance list, before any inherited protocols:
protocol MyProtocol: class {
var myVar: MyStruct { get set }
}
or
If MyProtocol is updated to extend AnyObject
protocol MyProtocol : AnyObject {
var myVar: MyStruct { get set }
}
then becomes clear that anInstanceOfMyType must refer an instance of a class, in this case your code does compile.
I couldn't find any good explanation to my questions so I'd like to ask you directly. First of all I'd like to refine my code in this post.
My problem is the protocol AnyObject and the Self type. I didn't implement AnyObject into my code because it is marked with #objc and I don't want any Objective-C stuff involved in my code (don't judge me for that). I also couldn't find any explanation about the Self type. It just worked as expected, but Xcode does not replace Self with the type the static function is called at.
Here is some example:
extension Int : Instance {}
Int.singleton { (customInstanceName) -> Self in 0 } // Self shall be replaced with Int
As you can see Xcode produces a Self instead an Int. Is there any chance I could fix this? Am I right that Self does return the dynamicType and my implementation is fine as it is in my post above? I would really appreciate any good explanation about the Self type.
As you have seen in my code. I am using a custom protocol to check whether my instance is a class or not. Is there any other shiny implementation to check my instances if they are classes or structure types, or am I forced to use AnyObject if I want to get rid of my ClassInstance protocol?
Thank you for your time.
UPDATE:
protocol Test {}
class A : Test {}
struct B : Test {}
let aClass : Test = A()
let aStruct : Test = B()
if let someClass = aClass as? AnyObject {
print(someClass) // only this will print
}
if let someStruct = aStruct as? AnyObject {
print(someStruct)
}
This will work, but AnyObject is still marked as an #objc protocol.
The Self type can be only used in protocols where it is a implicit typealias of the type which conforms to it:
protocol Testable {
func test() -> Self
}
If you want to conform to this protocol you than have to replace Self with the name of the type. For instance:
struct Product: Testable {
func test() -> Product {
return Product()
}
}
Important Edit:
As DevAndArtist pointed out in the comments there is a working class check in Swift 1.2 (without automatic bridging to Objective C) but not Swift 2 (Xcode 7 beta 3; probably a bug):
if instance.dynamicType is AnyClass {
// instance is a class
} else {
// instance is not a class
}
You can see workaround (mainly) for Swift 2 below.
End Edit
With respect to classes you should use AnyObject if you want to keep it simple but you can also use reflection which would be much more effort.
Below you can see some reflection results of string interpolations (only the first few characters):
"\(reflect(classType))" // Swift._ClassMirror
"\(reflect(0))" // Swift._LeafMirror
"\(reflect(enumType))" // Swift._EnumMirror
"\(reflect(structure))" // Swift._StructMirror
"\(reflect([0, 4]))" // Swift._ArrayTypeMirror
"\(reflect(NSDate()))" // Foundation._NSDateMirror
"\(reflect(NSURLRelationship.Contains))" // Swift._EnumMirror
"\(reflect(Int?(2)))" // Swift._OptionalMirror
As you can see enums are consistent if they are not defined in the Swift standard library (unfortunately also Optional...). So you can distinguish also structs and enums:
public enum Type {
case Enum, Class, Struct
}
public func getType<T>(anything: T) -> Type {
if anything is AnyObject {
return .Class
}
if "\(reflect(anything))".hasPrefix("Swift._EnumMirror") {
return .Enum
}
return .Struct
}
So for a better result you have to put some effort into it to differentiate between all the different cases.
But the easiest way to distinguish only between reference types and value types (aka classes and structs/enums) is still (unfortunately only works for own declared structs and not built in types because they can be bridged to Objective C; I'm working on it...):
if instance is AnyObject {}
// or: if instance is of type Any
if let classInstance = instance as? AnyObject {}
I would like my protocol to declare that there is a read/write property available. I have attempted it, but this does not work:
protocol EdibleThing {
var eaten: Bool { get set }
}
class Pickle: EdibleThing { var eaten = false }
class RusticGrapefruit: EdibleThing { var eaten = false }
class Jar {
let contents: [EdibleThing] = [Pickle(), RusticGrapefruit()]
var nextItem: EdibleThing {
return contents.last ?? Pickle() // Lazy pickle generation technology
}
func eat() {
let food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
}
What am I doing wrong? I think I've declared that the protocol has a get/set var called eaten, so why can't I set it?
The protocol might be implemented by either classes and structs - that prevents you from changing the internal status of an instance of a class or struct implementing that protocol using an immutable variable.
To fix the problem you have to either declare the food variable as mutable:
func eat() {
var food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
or declare the EdibleThing protocol to be implementable by classes only:
protocol EdibleThing : class {
var eaten: Bool { get set }
}
Note that this happens because food is a variable of EdibleThing type - the compiler doesn't know if the actual instance is a value or reference type, so it raises an error. If you make it a variable of a class type, like this:
let food: Pickle = nextItem as! Pickle
the compiler knows without any ambiguity that it's a reference type, and in that case it allows the assignment. But I guess that breaks your app logic... so consider it just as an example
You're mutating food.
Replace let food = nextItem with var food = nextItem
The problem is that you can't mutate a property on a value type defined by let.
Even though both of RusticGrapefruit and Pickle are class implementations (reference types), the protocol could be assigned to a value type like a struct. The compiler detects a potential problem and stops us.
Two solutions:
Change let to var (in my case, this would mean changing a lot of code that refers to objects of this type. Also, I like the semantic value and possible compiler optimizations from let)
Declare the protocol as only valid for classes: protocol EdibleThing: class { }