Need clarification on AnyObject in Swift
AnyObject
is a protocol. If you type it in a Playground and command click on it the following pops up:
/// 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`. For
/// example:
///
/// .. parsed-literal:
///
/// 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.
/// func getCValue1(x: AnyObject) -> Int? {
/// if let f: ()->Int = **x.getCValue** {
/// return f()
/// }
/// return nil
/// }
///
/// // A more idiomatic implementation using "optional chaining"
/// func getCValue2(x: AnyObject) -> Int? {
/// return **x.getCValue?()**
/// }
///
/// // An implementation that assumes the required method is present
/// func getCValue3(x: AnyObject) -> **Int** {
/// return **x.getCValue()** // x.getCValue is implicitly unwrapped.
/// }
///
/// See also: `AnyClass`
@objc protocol AnyObject {
}
When is a variable a AnyObject but not a NSObject
On platforms with Objective-C compatibility (which means all of Apple's platforms and no others), every class type is (secretly) a subclass of the SwiftObject
class, which provides NSObject
protocol conformance.
On other platforms, NSObject
is “just another class”, implemented in Swift, so only a class that explicitly has NSObject
as a superclass has instances that are NSObject
s.
How can this [AnyObject] return as AnyObject?
So why no any warning or error issue?
There would have been if you hadn't of said as AnyObject
:
class Brain {
var internalProgram = [AnyObject]()
var program: AnyObject {
get {
// compiler error:
// Return expression of type '[AnyObject]' does not conform to 'AnyObject'
return internalProgram
}
}
}
The compiler is telling us that [AnyObject]
doesn't conform to AnyObject
– which is perfectly true. A Swift Array
is a struct
, not a class
, therefore cannot directly be typed as an AnyObject
.
However, you then say as AnyObject
. By doing so, you're bridging the Swift Array
to NSArray
(when Foundation is imported) – which is a class
. Therefore it can be typed as an AnyObject
. You can see the full list of Foundation types which can be bridged to here.
Furthermore it's worth noting that in Swift 3, everything can be bridged to AnyObject
due to the introduction of the opaque _SwiftValue
type, which can wrap an arbitrary Swift value in an Obj-C compatible box (including Array
when Foundation isn't imported).
Because anything can now be an AnyObject
, it's pretty much as weak a type as Any
. On top of this, it also lets you call any known @objc
method on it, completely disregarding type safety and is almost guaranteed to cause you problems with _SwiftValue
boxing. For those reasons, you should avoid using AnyObject
wherever you can. There is nearly always a stronger type available for you to use.
Do all class types in Swift implicitly implement AnyObject?
From the documentation:
AnyObject
The protocol to which all classes implicitly conform.
The strange behaviour of Swift's AnyObject
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’sid
type. Swift importsid
asAnyObject
, 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 ofAnyObject
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 onAnyObject
.
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).
Swift, Self from AnyObject
You can simply return User?
from your class function, if this is an option:
public class func returnFirstSelf() -> User? {
if let found = findByID("1") as? User {
return found
}
return nil
}
There's currently no way (I'm aware of) to return Self?
with Swift as it stands. The problem is that Self
has a somewhat... "dynamic" meaning, separate from concrete types, protocols, and even generics. A particular example that demonstrates this is: What if you have a class StudentUser
that extends User
? If you tried to implement it like this:
class func returnFirstSelf() -> Self? {
if let found = findById("1") as? Self { // Note the check that 'found' is a 'Self'
return found
}
return nil
}
Then you encounter a compiler error because you cannot use Self
outside the result of a protocol or class method. And if you try to implement it like this:
class func returnFirstSelf() -> Self? {
if let found = findById("1") as? User { // Note 'User' instead of 'Self'
return found
}
return nil
}
Then you run the risk of Self
actually meaning StudentUser
, and even if you pass the check that requires found
to be a User
, it doesn't guarantee that found
will be a StudentUser
. This will occur in the case that StudentUser
does not override the method to ensure that the as?
checks against StudentUser
.
The critical flaw here in my opinion is that you cannot use the required
keyword on class methods, requiring subclasses to override them. This would allow the compiler to ensure that any subclasses have overridden the method and provided an implementation that can guarantee type safety.
Why do Swift Class-Only Protocols need AnyObject Inheritance?
When you write a protocol, you define an interface for the types which adopt, or conform to the protocol:
// Every Greeter has a greeting message
// and a way for it to present that message.
protocol Greeter {
var greeting: String { get }
func greet()
}
extension Greeter {
// Default implementation for the `greet` method.
func greet() {
print(self.greeting)
}
}
This gives consistent behavior to every type which conforms and lets you treat them all in the same way regardless of what they are. For instance, a type can store a value bound to a protocol type:
struct Store {
// The name of the store.
var name: String
// The address of the store.
var address: String
// An entity that greets customers when they walk in.
var greeter: Greeter
}
In this case, Store.greeter
can be set to any value or object which conforms to the Greeter
protocol:
struct Bell: Greeter {
let greeting = "Ding!"
}
class Person: Greeter {
let greeting: String
init(name: String) {
greeting = "Welcome! My name is \(name)."
}
}
var store = Store(name: "My Shop", address: "123 Main St.", greeter: Bell())
store.greeter.greet() // => "Ding!"
store.greeter = Person(name: "Itai")
store.greeter.greet() // => "Welcome! My name is Itai."
By default, a protocol can be adopted by both value types and object types, i.e., both struct
s and class
es. This is a good default because in many cases, there's no reason to restrict who can conform to a protocol.
However, there is one case where this matters. By default, Store
owns its greeter
property, i.e., when you assign an object to store.greeter
, it is retained. This is normally fine, except when you have a potentially circular relationship (for example, if Person
has a reference to the Store
they work at).
Normally, to break a potential circular chain like this up, you would make the variable weak
:
struct Store {
// ...
weak var greeter: Greeter?
// ^ error: 'weak' must not be applied to non-class-bound 'Greeter'; consider adding a protocol conformance that has a class bound
}
If you try this, though, you'll get the above error. This is because weak
only makes sense for objects, not for struct
s (struct
s can only be owned because they are value types — every owner just makes a copy of a struct
, so there is no one central value to retain or release). In this case, to ensure a weak
reference, you must ensure that Greeter
an only be an object type.
The way to do this is to constrain the protocol to only allow classes to conform by using AnyObject
as a constraint:
protocol Greeter: AnyObject {
// ...
}
Now, Bell
can't conform to Greeter
any more (error: non-class type 'Bell' cannot conform to class protocol 'Greeter'
) but you are allowed to have weak var greeter: Greeter?
as a variable.
So, when should you restrict your protocol to be "class-bound" (using AnyObject
)? Well, only when you really have to. The one benefit for class-bound protocols is to allow weak
references to them, and the main use-case for that is with delegate references to prevent circular retains.
For instance, UITableView
has a weak
delegate
property because the delegate
object is usually the view controller which owns the UITableView
object itself. If the view controller retains the table view, and the table view retains the controller, neither can be released automatically.
So, in the general case, you don't need to worry about the AnyObject
constraint unless you really do only want classes to conform to the protocol.
Related Topics
Cleanly Handling /Usr/Local/ with Swift Package Manager and Libevent
Sandbox Entitlement to Script Itunes via Nsapplescript
How to Set Window Level in Swift
Can't Update Label from Other Class in Swift
Protocol with Associatedtype Protocol for Generic Functions
Create Tab Bar Controller and Navigation Controller
Swift Full Text Search in Tableview
How to Make Text Appear in Centre of The Image
Arkit: How to Tell If User's Face Is Parallel to Camera
Why My Arguments Are Being Blocked When Running a Shell Command
How to Center Nspopover When Using Swiftui
Could Not Find an Overload for '-' That Accepts The Supplied Arguments