Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift AnyObject's subscript, where did it come from?

In Swift, how is it that AnyObject supports subscripts, even for types that are't subscriptable? Example:

let numbers: AnyObject = [11, 22, 33]
numbers[0]       // returns 11

let prices: AnyObject = ["Bread": 3.49, "Pencil": 0.5]
prices["Bread"]  // returns 3.49

let number: AnyObject = 5
number[0]        // return nil

let number: AnyObject = Int(5)
number[0]        // return nil

Yet if my number is declared as Int then it's a syntax error:

let number: Int = 5
number[0]        // won't compile

Interestingly, Any doesn't have subscript support.

like image 463
Steve Kuo Avatar asked Jan 21 '16 22:01

Steve Kuo


2 Answers

This works only if you import Foundation, as Swift is doing in that case some bridging to Objective-C types - a NSArray-like object in this case.

import Foundation

let numbers: AnyObject = [11, 22, 33] as AnyObject
type(of: numbers) //_SwiftDeferredNSArray.Type

If you don't import Foundation, then you are not even allowed to make the assignment (because a Swift array is a struct and not an object).

let numbers: AnyObject = [11, 22, 33] // error: contextual type 'AnyObject' cannot be used with array literal

You can cast to Any, though:

let numbers: Any = [11, 22, 33]    
type(of: numbers) // Array<Int>.Type

Why does the import Foundation does the trick? This is documented in the AnyObject type description:

/// 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`.  For  
/// example:  
///  
///     class C {  
///       @objc func getCValue() -> Int { return 42 }  
///     }  
///  
///     // If x has a method @objc getValue()->Int, call it and  
///     // return the result.  Otherwise, return nil.  

This means you can even call methods on your array that don't necessarily exist on NSArray, but exists in the Objective-C world, like for example:

numbers.lowercaseString       // nil

and Swift will gracefully return you a nil value instead of throwing you a nasty object does not recognises selector exception, like it would happen in Objective-C. If this is good or bad, remains to debate :)

Update The above seems to work only for properties, and property-like methods, if you try to use an Objective-C method, then you'll run into the unrecognized selector issue:

import Foundation

@objc class TestClass: NSObject {
    @objc var someProperty: Int = 20
    @objc func someMethod() {}
}

let numbers: AnyObject = [11, 22, 33] as AnyObject
numbers.lowercaseString                // nil
numbers.someMethod                     // nil
numbers.someMethod()                   // unrecognized selector
numbers.stringByAppendingString("abc") // unrecognized selector
like image 176
Cristik Avatar answered Nov 17 '22 05:11

Cristik


It has to do with the bridging of types when assigning a value to an object of type AnyObject:

let numbers: AnyObject = [11, 22, 33]
print(numbers.dynamicType) // _SwiftDeferredNSArray.Type

let prices: AnyObject = ["Bread": 3.49, "Pencil": 0.5]
print(prices.dynamicType) // _NativeDictionaryStorageOwner<String, Double>.Type

let number: AnyObject = 5
print(number.dynamicType) // __NSCFNumber.Type

let anotherNumber: Int = 5
print(anotherNumber.dynamicType)  // Int.Type

Behind the scenes, _SwiftDeferredNSArray.Type, _NativeDictionaryStorageOwner<String, Double>.Type, and __NSCFNumber.Type must support subscripts while Int.Type does not.

This is assuming you have imported Foundation. For an explanation with pure Swift types, see Cristik's answer.

like image 5
JAL Avatar answered Nov 17 '22 07:11

JAL