I'm having some trouble getting the new KVO syntax right.
According to the Apple documentation:
Create an observer for the key path and call the
observe(_:options:changeHandler) method. For more information on key
paths, see Keys and Key Paths.
class MyObserver: NSObject {
#objc var objectToObserve: MyObjectToObserve
var observation: NSKeyValueObservation?
init(object: MyObjectToObserve) {
objectToObserve = object
super.init()
observation = observe(\.objectToObserve.myDate) { object, change in
print("Observed a change to \(object.objectToObserve).myDate, updated to: \(object.objectToObserve.myDate)")
}
}
}
let observed = MyObjectToObserve()
let observer = MyObserver(object: observed)
observed.updateDate()
I'm initializing my observation like so:
self.observation = self.webView!.observe(\.webView.isLoading, changeHandler: { (webView, observedChange) in
//code
})
but am getting this error:
Turns out the syntax needs to be like this, using the object Type rather than the object instance name:
self.observation = self.webView!.observe(\WKWebView.isLoading, changeHandler: { (webView, observedChange) in
//code
})
Misread the documentation ¯\_(ツ)_/¯
If you use the \. syntax the root element is the observed object so it's simply
self.observation = self.webView!.observe(\.isLoading, ...
The compiler treats your syntax as webView.webView.isLoading which is obviously not intended.
Related
I'm trying to write a wrapper around URLSessionTask in Swift. According to the documentation
All task properties support key-value observing.
So I want to keep this behavior and make all the properties on my wrapper also KVO-compliant (usually delegating to the wrapped task) and fully accessible to Objective-C. I'll describe what I'm doing with one property, but I basically want to do the same thing for all properties.
Let's take the property state of URLSessionTask. I create my wrapper like this:
#objc(MyURLSessionTask)
public class TaskWrapper: NSObject {
#objc public internal(set) var underlyingTask: URLSessionTask?
#objc dynamic public var state: URLSessionTask.State {
return underlyingTask?.state ?? backupState
}
// the state to be used when we don't have an underlyingTask
#objc dynamic private var backupState: URLSessionTask.State = .suspended
#objc public func resume() {
if let task = underlyingTask {
task.resume()
return
}
dispatchOnBackgroundQueue {
let task:URLSessionTask = constructTask()
task.resume()
self.underlyingTask = task
}
}
}
I added #objc to the properties so they are available to be called from Objective-C. And I added dynamic to the properties so they will be called via message-passing/the runtime even from Swift, to make sure the correct KVO-Notifications can be generated by NSObject. This is supposed to be enough according to Apple's KVO chapter in the "Using Swift with Cocoa and Objective-C" book.
I then implemented the static class methods necessary to tell KVO about dependent key paths:
// MARK: KVO Support
extension TaskWrapper {
#objc static var keyPathsForValuesAffectingState:Set<String> {
let keypaths:Set<String> = [
#keyPath(TaskWrapper.backupState),
#keyPath(TaskWrapper.underlyingTask.state)
]
return keypaths
}
}
Then I wrote a unit test to check whether the notifications are called correctly:
var swiftKVOObserver:NSKeyValueObservation?
func testStateObservation() {
let taskWrapper = TaskWrapper()
let objcKVOExpectation = keyValueObservingExpectation(for: taskWrapper, keyPath: #keyPath(TaskWrapper.state), handler: nil)
let swiftKVOExpectation = expectation(description: "Expect Swift KVO call for `state`-change")
swiftKVOObserver = taskWrapper.observe(\.state) { (_, _) in
swiftKVOExpectation.fulfill()
}
// this should trigger both KVO versions
taskWrapper.underlyingTask = URLSession(configuration: .default).dataTask(with: url)
self.wait(for: [swiftKVOExpectation, objcKVOExpectation], timeout: 0.1)
}
When I run it, the test crashes with an NSInternalInconsistencyException:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot remove an observer <_XCKVOExpectationImplementation 0x60000009d6a0> for the key path "underlyingTask.state" from < MyURLSessionTask 0x6000002a1440>, most likely because the value for the key "underlyingTask" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the MyURLSessionTask class.'
But by making the underlyingTask-property #objc and dynamic, the Objective-C runtime should ensure that this notification is sent, even when the task is changed from Swift, right?
I can make the test work correctly by sending the KVO-notifications for the underlyingTask manually like this:
#objc public internal(set) var underlyingTask: URLSessionTask? {
willSet {
willChangeValue(for: \.underlyingTask)
}
didSet {
didChangeValue(for: \.underlyingTask)
}
}
But I'd much rather avoid having to implement this for every property and would prefer to use the existing keyPathsForValuesAffecting<Key> methods. Am I missing something to make this work? Or should it work and this is a bug?
Property underlyingTask isn't dynamic.
As I understand reflection in Swift is poorly available as of yet. I am currently in the process of converting objective-c code to swift for the sake of performance (I have noticed a considerable difference).
Now what I need is a way to call a Method using reflection. The object the method needs to be called upon extends NSObject to enable the class to be resolved using the following code;
let clazz = NSClassFromString("MyProject.DynamicClass") as NSObject.Type;
let clazzInstance = clazz() as! NSObject;
I am able to retrieve a the number of argument and a reference to the method using the following code;
let selectorMethod = Selector("myCustomMethod:");
let numberOfArguments : UInt32 = method_getNumberOfArguments(selectorMethod);
let referenceToMethod : Method = class_getInstanceMethod(clazz, selector!);
But how do I use/call the referenceToMethod?
Additional
I have also tried calling performSelector but this has been completely removed Swift 2. I also would like to prevent the use of any #objc attributes/annotations.
If you are looking for a completely Swifty way of reflection, the object that has the method that needs to be called does not need to be a NSObject at all, instead all it need is a required initializer. Have a look at below example :
class A {
required init() {}
func printSomething(s: String) {
print(s)
}
}
// initializing object dynamically from class
let clazz = NSClassFromString("MyProject.A") as! A.Type
let clazzInstance = clazz()
// getting and calling its methods in Swifty way
let method = clazzInstance.printSomething
method("something")
The advantage of using this stands on fact that you wont need to use casting at all and also calling method with wrong arguments would trigger a compile time error
A crazy idea, and not perfect:
you can define a var as:
var myFunctionToBe : (() -> AnyObject)?
or even set AnyObject as parameter,
and then in the initiator:
init() {
myFunctionToBe = {
//do something
return whatsdone
}
}
then in reflection you can see the var myFunctionToBe, get it as:
let method = c.value as? (() -> AnyObject)
and invoke it:
method!()
or
let returnVal = method!() as? String
I'm trying to create a protocol in Swift I can use for object construction. The problem I'm running into is that I need to store the type information so the type can be constructed later and returned in a callback. I can't seem to find a way to store it without either crashing the compiler or creating build errors. Here's the basics (a contrived, but working example):
protocol Model {
init(values: [String])
func printValues()
}
struct Request<T:Model> {
let returnType:T.Type
let callback:T -> ()
}
We have a simple protocol that declares a init (for construction) and another func printValues() (for testing). We also define a struct we can use to store the type information and a callback to return the new type when its constructed.
Next we create a constructor:
class Constructor {
var callbacks: [Request<Model>] = []
func construct<T:Model>(type:T.Type, callback: T -> ()) {
callback(type(values: ["value1", "value2"]))
}
func queueRequest<T:Model>(request: Request<T>) {
callbacks.append(request)
}
func next() {
if let request = callbacks.first {
let model = request.returnType(values: ["value1", "value2"])
request.callback(model)
}
}
}
A couple things to note: This causes a compiler crash. It can't figure this out for some reason. The problem appears to be var callbacks: [Request<Model>] = []. If I comment out everything else, the compiler still crashes. Commenting out the var callbacks and the compiler stops crashing.
Also, the func construct works fine. But it doesn't store the type information so it's not so useful to me. I put in there for demonstration.
I found I could prevent the compiler from crashing if I remove the protocol requirement from the Request struct: struct Request<T>. In this case everything works and compiles but I still need to comment out let model = request.returnType(values: ["value1", "value2"]) in func next(). That is also causing a compiler crash.
Here's a usage example:
func construct() {
let constructor = Constructor()
let request = Request(returnType: TypeA.self) { req in req.printValues() }
//This works fine
constructor.construct(TypeA.self) { a in
a.printValues()
}
//This is what I want
constructor.queueRequest(request)
constructor.next() //The callback in the request object should be called and the values should print
}
Does anyone know how I can store type information restricted to a specific protocol to the type can later be constructed dynamically and returned in a callback?
If you want the exact same behavior of next I would suggest to do this:
class Constructor {
// store closures
var callbacks: [[String] -> ()] = []
func construct<T:Model>(type:T.Type, callback: T -> ()) {
callback(type(values: ["value1", "value2"]))
}
func queueRequest<T:Model>(request: Request<T>) {
// some code from the next function so you don't need to store the generic type itself
// **EDIT** changed closure to type [String] -> () in order to call it with different values
callbacks.append({ values in
let model = request.returnType(values: values)
request.callback(model)
})
}
func next(values: [String]) {
callbacks.first?(values)
}
}
Now you can call next with your values. Hopefully this works for you.
EDIT: Made some changes to the closure type and the next function
Unfortunately there is no way to save specific generic types in an array and dynamically call their methods because Swift is a static typed language (and Array has to have unambiguous types).
But hopefully we can express something like this in the future like so:
var callbacks: [Request<T: Model>] = []
Where T could be anything but has to conform to Model for example.
Your queueRequest method shouldn't have to know the generic type the Request it's being passed. Since callbacks is an array of Request<Model> types, the method just needs to know that the request being queued is of the type Request<Model>. It doesn't matter what the generic type is.
This code builds for me in a Playground:
class Constructor {
var callbacks: [Request<Model>] = []
func construct<T:Model>(type:T.Type, callback: T -> ()) {
callback(type(values: ["value1", "value2"]))
}
func queueRequest(request: Request<Model>) {
callbacks.append(request)
}
func next() {
if let request = callbacks.first {
let model = request.returnType(values: ["value1", "value2"])
request.callback(model)
}
}
}
So I found an answer that seems to do exactly what I want. I haven't confirmed this works yet in live code, but it does compile without any errors. Turns out, I needed to add one more level of redirection:
I create another protocol explicitly for object construction:
protocol ModelConstructor {
func constructWith(values:[String])
}
In my Request struct, I conform to this protocol:
struct Request<T:Model> : ModelConstructor {
let returnType:T.Type
let callback:T -> ()
func constructWith(values:[String]) {
let model = returnType(values: values)
callback(model)
}
}
Notice the actual construction is moved into the Request struct. Technically, the Constructor is no longer constructing, but for now I leave its name alone. I can now store the Request struct as ModelConstructor and correctly queue Requests:
class Constructor {
var callbacks: [ModelConstructor] = []
func queueRequest(request: Request<Model>) {
queueRequest(request)
}
func queueRequest(request: ModelConstructor) {
callbacks.append(request)
}
func next() {
if let request = callbacks.first {
request.constructWith(["value1", "value2"])
callbacks.removeAtIndex(0)
}
}
}
Note something special here: I can now successfully "queue" (or store in an array) Request<Model>, but I must do so indirectly by calling queueRequest(request: ModelConstructor). In this case, I'm overloading but that's not necessary. What matters here is that if I try to call callbacks.append(request) in the queueRequest(request: Request<Model>) function, the Swift compiler crashes. Apparently we need to hold the compiler's hand here a little so it can understand what exactly we want.
What I've found is that you cannot separate Type information from Type Construction. It needs to be all in the same place (in this case it's the Request struct). But so long as you keep construction coupled with the Type information, you're free to delay/store the construction until you have the information you need to actually construct the object.
I'm seeing some odd behaviour in a class I created a while ago, where it seems that a struct's properties are changing immediately after being passed (copied) to a method.
I've boiled it down to a simple test case that can be run in a playground:
struct StructToPass<T> {
let x: T
}
class MyClass<T> {
func createAndPassStructWithValue(value: T) {
let structToPass = StructToPass(x: value)
println("Before passing to method: \(structToPass.x)")
passStruct(structToPass)
}
func passStruct(_ theStruct: StructToPass<T>? = nil) {
println("Inside method: \(theStruct!.x)")
}
}
let myClass = MyClass<Int>()
myClass.createAndPassStructWithValue(42)
Looking at the relevant printed statements, it shows that the struct's x property has changed:
// Before passing to method: 42
// Inside method: 140734543799888
Creating the struct outside the class and calling passStruct(_:) directly causes the playground to crash, as does writing passStruct(_:) as a function:
// Causes playground to crash:
let aStruct = StructToPass(x: 42)
myClass.passStruct(aStruct)
// Also causes playground to crash:
func passStruct<T>(_ theStruct: StructToPass<T>? = nil) {}
passStruct(aStruct)
Changing the passStruct(_:) method/function to use the default external parameter name fixes the issue, as does introducing another parameter (before/after the default parameter):
// This works:
func passStruct<T>(theStruct: StructToPass<T>? = nil) {
println("Inside function: \(theStruct!.x)")
}
passStruct(theStruct: aStruct)
// This also works:
func passStruct<T>(_ theStruct: StructToPass<T>? = nil, someOtherParam: Int) {
println("Inside function: \(theStruct!.x)")
}
passStruct(aStruct, 42)
Is this a compiler bug? It seems the compiler doesn't like it when a generic function/method with a single argument with a default value doesn't use an external parameter name. It's a specific case, but I think it ought to work. If it shouldn't work, there ought to be a compiler warning.
110% compiler bug. I've even tried this out of Playground. It all happily compiles until you want to add a line which actually does something, like sending a passStruct. There's all kinds of things wrong with this. I even had this fail:
func passStruct<T>(_ theStruct: StructToPass<T>? = (nil as StructToPass<T>?)) {
println("Inside function: \(theStruct!.x)")
}
which I kinda thought might be the problem (even though it shouldn't be I've had that elsewhere).
Well found! Report it. They're clearly not finished with generics. In my experiments I found that generic class properties aren't allowed.
static let nilStruct: StructToPass<T>? = nil
does not compile, with one of the "not yet supported" error messages.
I am having an issue with calling an instance method within the class itself. If someone can provide me some insight it would be greatly appreciated.
My current code looks like this:
class Rect
{
func printthis() -> String {
return "this is working or what"
}
var toPrint:String = self.printthis()
}
The error I am getting in Xcode is: Use of unresolved identifier 'self'.
What am I missing here?
You can't call an instance method without an instance. The class is merely the template for instances. So i don't what you are trying to do here...
But the answer is no, you cannot call an instance method form the class definition because there is no instance yet.
Perhaps you want to delcare a class method and use that to set an instance variable on creation? If so, you might do that like this:
class Rect {
class func printthis() -> String {
return "this is working or what"
}
var toPrint:String
init() {
toPrint = Rect.printthis()
}
}
var r = Rect()
println(r.toPrint) //-> this is working or what
An instance of a class is not initialized and able to be referenced (even as 'self') until all of its variables have been assigned values.
An option that may work for you is to declare your variable as an implicitly-unwrapped optional, which is assigned nil by default. Then in the class's init method, since all of the variables have been assigned values, you are able to start calling methods on your instance.
class Rect {
var toPrint: String!
init() {
toPrint = printthis()
}
printthis() -> String {
return "this will work"
}
}
the problem is that swift is strict about initing all properties.
you may as a workaround
class Rect
{
func printthis() -> String {
return "this is working or what"
}
var toPrint:String = ""
init() {
toPrint = printthis()
}
}