Need Clarification on Anyobject in Swift

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 NSObjects.

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’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).

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 structs and classes. 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 structs (structs 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



Leave a reply



Submit