问题
Consider the following code, which adds a gesture recognizer to a view.
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleGesture(gesture:)))
let test1 = self
@objc func handleGesture(gesture: UITapGestureRecognizer) {
// some code
print("hello")
}
override func viewDidLoad() {
let test2 = self
super.viewDidLoad()
imageView.addGestureRecognizer(gesture)
}
}
As per this question, the above code does not work because I'm trying to use self
(in the gesture recognizer's initializer) when not fully initialized, and this is so because of Swift's two-phase initialization.
I'm not interested in the easy fix to make this work, but this triggers a couple of questions:
1) Why does the compiler allow us to use self
here if self
is not ready to be used? Shouldn't I get a compiler error if I'm trying to use self
too soon?
2) We can't directly inspect the type of self
with alt+click in XCode. However, we can inspect the types of my ad hoc variables test1
and test2
. While test2
's type is ViewController
, as expected, test1
's type is (ViewController) -> () -> ViewController
(i.e., a closure that takes a ViewController
and returns a closure that takes nothing and returns a ViewController
). What is that and why does self
have two different types within the same class?
回答1:
1)
Shouldn't I get a compiler error if I'm trying to use self
too soon?
I do agree. You may send a bug report to swift.org.
Why does the compiler allow us to use self
here if self
is not ready to be used?
Unfortunately, there's another self
in the descendants of NSObject
, the method self() of NSObject.
2)
What is that and why does self have two different types within the same class?
The current Swift interprets the initial value expression in the class
context, not in the instance context.
You know method names can be used as closures in Swift:
class ViewController: UIViewController {
//..
func aMethod() {
//...
}
func anInstanceMethod() {
let meth = aMethod // () -> ()
}
}
Swift can also refer to an instance method in the class
context, which generates a so-called unapplied method reference (see SE-0042), which currently returns a curried function:
class ViewController: UIViewController {
//...
func aMethod() {
//...
}
class func aClassMethod() {
let meth = aMethod // (ViewController) -> () -> ()
}
}
The method self()
as well.
Generally we do not need self()
method and this behavior should be changed, I think.
回答2:
This is interesting behaviour that works for Objective-C objects. Let's take these three examples:
class Object: NSObject {
let test = self // compiles
}
class NonNSObject {
// let test = self // errors
lazy var lazyTest = self // compiles
}
struct NonClass {
// let test = self // errors
lazy var lazyTest = self // errors
}
NonNSObject
exhibits what you'd escape:
The object cannot reference itself until it is fully initialized, and let
bindings must all be initialized before full initialization, so this failed.
However, NSObject happens to have an Objective-C method - (instancetype)self;
which returns self. We can model this on NonNSObject as so:
func returnSelf() -> NonNSObject {
return self
}
This is where we start to see the answer to 2).
If we reference this method returnSelf
on the Class we get the signature (NonNSObject) -> () -> NonNSObject
. You can do this with any instance method as so:
let test = NonNSObject.returnSelf
The signature makes sense in this context:
- The argument is the object we actually want to call the method on
- Then we "apply" the function (with no arguments, in this case)
- And we finally get our return value
let curriedFunction = NonNSObject.returnSelf // (Self) -> () -> Self
let readyToCall = curriedFunction(NonNSObject()) // () -> Self
let finallyApplied = readyToCall() // Self
Putting all the pieces together, we can see that in the case of ViewController (which inherits from UIViewController which way up the chain inherits from NSObject) there is an instance method self
which the compiler is assuming you meant, so it uses that instead of the instance itself (as that would be an error). Its signature is thus a natural consequence of using an instance method on the class itself—it needs an instance, which is the first argument.
In summary:
1) Instead of assuming you made an error, the Swift compiler finds a function self
on NSObject and returns the curried form.
2) This is the curried form of a function, in particular, an instance method which returns its own type.
2.5) It's still highlighted in pink because Swift-ObjC interop is mildly hacky, and self
is both a method and, well, self
.
As a bonus, the struct cannot reference itself at all, even lazily.
来源:https://stackoverflow.com/questions/46797017/the-type-of-self-in-swift-and-its-use-with-respect-to-two-phase-initialization