The reasoning for this is explained in Objective-C id as Swift Any. In Swift 2, it was as you suggest. id
parameters were bridged as AnyObject. In Swift 3, they were changed to Any:
This change makes Objective-C APIs more flexible in Swift, because Swift-defined value types can be passed to Objective-C APIs and extracted as Swift types, eliminating the need for manual “box” types. These benefits also extend to collections: Objective-C collection types NSArray, NSDictionary, and NSSet, which previously only accepted elements of AnyObject, now can hold elements of Any type.
The change was detailed in SE-0116 Import Objective-C id
as Swift Any
type.
This made Swift/ObjC interop dramatically simpler than in Swift 2, but it did create some specific cases, like the targets of actions, where it was possibly a little easier to create code that would fail at runtime. In practice, though, the limitation on the selector
parameter is the more important protection there. Mismatching target and selector have always been a risk in ObjC, and allowing Any here doesn't actually make it much worse. You still need to use #selector
correctly, and it will throw errors if you reference non @objc methods.
Joakim also makes some good arguments, though I would typically prefer that Observer require NSObjectProtocol in this case. I suspect it was more a matter of not being worth the trouble to add NS_REFINED_FOR_SWIFT macros for these cases. If you find real-world errors occurring due to the lack of AnyObject enforcement, that would be worth bringing up on the Swift forums as a possible improvement.
But to your secondary question "is it even possible to correctly specify selector to something except @objc method of class," the answer is yes, if by "correctly" you mean "it will work."
As an example, you can send selectors to String, even though it is a struct, by referencing methods attached to NSString:
let string: String = "This is a Swift string"
// This is an extension on **NSString**, not String.
extension NSString {
@objc func strangeButTrue() { print("Yes, this will print") }
}
Timer.scheduledTimer(timeInterval: 1,
target: string,
selector: #selector(NSString.strangeButTrue),
userInfo: nil,
repeats: false)
Note also that #selector(NSString.strangeButTrue)
is a compile-time convenience. This can also be expressed this way:
let nameOfSelectorDecidedAtRunTime = "strangeButTrue"
Timer.scheduledTimer(timeInterval: 1,
target: string,
selector: Selector(nameOfSelectorDecidedAtRunTime),
userInfo: nil,
repeats: false)
Constructing selectors at runtime from strings is somewhat common in ObjC. Many large projects I've worked on have at least one place it's done. Being able to treat bridged objects this way does make it easier to port existing ObjC code, even if this isn't something you should really do much in Swift.