Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-5220] Expose API to retrieve string representation of KeyPath #4085

Open
swift-ci opened this issue Jun 14, 2017 · 19 comments
Open

[SR-5220] Expose API to retrieve string representation of KeyPath #4085

swift-ci opened this issue Jun 14, 2017 · 19 comments

Comments

@swift-ci
Copy link
Contributor

Previous ID SR-5220
Radar rdar://problem/35295965
Original Reporter kishikawakatsumi (JIRA User)
Type Improvement

Attachment: Download

Additional Detail from JIRA
Votes 48
Component/s Foundation, Standard Library
Labels Improvement
Assignee None
Priority Medium

md5: dcdfcfcfca5995073623243af75491d3

is duplicated by:

  • SR-6270 Accessing _kvcKeyPathString

Issue Description:

Expose API to retrieve string representation of KeyPath

We can build a type-safe NSPredicate using KeyPath if we have such an API. We can use `_kvcKeyPathString` currently, but it would be nice if it will be officially visible as a public API.

For example, I created this experimental project using KeyPath and `_kvcKeyPathString`.

Using this extension, you can type-safely write queries that were previously hard-coded with strings as shown below. In addition to type checking, a typo can be prevented, and code completion works.

Before

NSPredicate(format: "name == %@", "Katsumi")
NSPredicate(format: "age > %@", 20)

After

Query(Person.self).filter(\Person.name == "Katsumi")
Query(Person.self).filter(\Person.age > 20)
Query(Person.self).filter(\Person.name > 20) // Compile error
Query(Person.self).filter(\Dog.name == "John") // Compile error
@belkadan
Copy link

cc @jckarter, @Catfish-Man

@lilyball
Copy link
Mannequin

lilyball mannequin commented Aug 25, 2017

I need this. I want to extend PMKVObserver to support the new key paths, but it needs to be able to hand a String to Obj-C.

Suggestion: let me type something like #keyPath(\Person.age) to get the string value.

@bobergj
Copy link

bobergj commented Oct 6, 2017

Suggestion: when the KeyPath refers to an NSObject key path, and when sending the KeyPath as Any to Obj-C, bridge it to NSString.

The implementation for transforming the KeyPath to a string already seems to already exists in https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/NSObject.swift, although it's private to the observe implementation.

Eridius (JIRA User): In your case, PMKVObserver should be able to use the new Swift 4 NSObject.observe API (see linked file) rather than extracting the key path string.

@lilyball
Copy link
Mannequin

lilyball mannequin commented Oct 6, 2017

@bobergj PMKVObserver cannot use the new NSObject.observe API because all of the logic is implemented on Obj-C, and the new block-based observation API is Swift-only. Also, I'm retaining the older Selector-based API in addition to the newer KeyPath-based one and they need to share an implementation.

@belkadan
Copy link

belkadan commented Nov 1, 2017

@swift-ci create

@swift-ci
Copy link
Contributor Author

swift-ci commented Nov 1, 2017

Comment by Krunoslav Zaher (JIRA)

Hi,

I've attached report.swift .

Is there some reason why the default `NSObject.observe` API doesn't return `nil` when one of the objects on observation path deallocates?

The example I've attached prints:
```
b = nil
b = default value
B deallocated
```

I would expect it to print:
```
b = nil
b = default value
b = nil
B deallocated
```

@lilyball
Copy link
Mannequin

lilyball mannequin commented Nov 1, 2017

kzaher (JIRA User) Please don't post unrelated questions in the comments of tickets. Your question has nothing to do with this ticket.

@swift-ci
Copy link
Contributor Author

swift-ci commented Nov 2, 2017

Comment by Krunoslav Zaher (JIRA)

Eridius (JIRA User) I've posted this issue https://bugs.swift.org/browse/SR-6270 and @belkadan marked it as a duplicate of this issue.

I would argue my issue is related with this one since I can resolve my issue by either:

  • figuring out what how to access `_kvcKeyPathString `

  • using `NSObject.observe` API -> but it has a bug that it does't fire in the case I've posted

I would really want to use `NSObject.observe` API but it seems to me that API is faulty in some cases.

I would like some clarification on that behavior, but yeah, I can also post another issue.

@swift-ci
Copy link
Contributor Author

Comment by Alex Lynch (JIRA)

The semantics unlocked by this feature request are profound in scope.

If swift could provide a type-checked predicate language like the one the OP [posted|https://github.com/kishikawakatsumi/Kuery,] then changes to CoreData object graphs would break predicates that would otherwise fail only at runtime (because the archaic string language isn't compile checked).

If the swift core team is concerned about exposing the "string value" of the KeyPath publicly, then perhaps a change to the NSPredicate interpretation process could help.

If we could write `NSPredicate(format: "%@ == %@ ", [keyPath, equatableThing])`.

The above form would maintain the privacy of the KeyPath's "string value" within the Foundation library.

@swift-ci
Copy link
Contributor Author

Comment by Peter Kamb (JIRA)

I heard back from the Swift team via email about this issue:

A number of people have raised the NSPredicate interop use case, so maybe Foundation would be interested in providing an overlay for it. That wouldn't require us to make the KVC interop interface public. I'm a bit concerned about providing the KVC string as public API, since people might misinterpret it as usable for Swift reflectiony things, which might be dangerous with ObjC importer renaming and other things.

I opened the following radar, which I'd encourage others to duplicate:

NSPredicate initialization via Swift KeyPath
rdar://problem/38439172

@swift-ci
Copy link
Contributor Author

Comment by Alex Lynch (JIRA)

Duplicated:

NSPredicate initialization via Swift KeyPath
rdar://problem/38576511

@swift-ci
Copy link
Contributor Author

Comment by Alexey Kravchenko (JIRA)

Duplicated:

Add Swift KeyPath compatibility with %K in NSPredicate

rdar://problem/38862195

@swift-ci
Copy link
Contributor Author

Comment by Daniel Asher (JIRA)

I'd like to duplicate but get:

Open Radar

Community bug reports

Search Results

Query: rdar://problem/38862195

No matching results found.

@swift-ci
Copy link
Contributor Author

Comment by Alex Lynch (JIRA)

This project is capable of turning KeyPaths into NSPredicates that are compatible with CoreData.

@swift-ci
Copy link
Contributor Author

Comment by Peter Kamb (JIRA)

NSExpression can be initialized via a Swift KeyPath, then the keyPath String extracted via NSExpression.keyPath

let expresson = NSExpression(forKeyPath: \String.count)
let keyPathString = expresson.keyPath

// Thread 1: Fatal error: Could not extract a String from KeyPath Swift.KeyPath<Swift.String, Swift.Int>

This crashes in the same scenarios in which `_kvcKeyPathString` is nil, but is at least a non `_underscoreFunction` alternative in the API.

@swift-ci
Copy link
Contributor Author

Comment by Tom Harrington (JIRA)

I could really use this too. In my scenario, I'm working on updates to mogenerator. For convenience in constructing NSPredicates, the generated Core Data code includes per-class structs declared like this:

    public struct Attributes {
        static let capacity = "capacity"
        static let name = "name"
        static let timestamp = "timestamp"
    }

Then an NSPredicate can use %@-style substitution in its format string, avoiding the need to include bare property names.

In recent versions of Swift, it would seem that Swift key paths could take the place of the hard-coded strings above. The predicate would be constructed using something like \MyClass.capacity which would give a predicate that included capacity.

Although this scenario deals specifically with subclasses of NSManagedObject, using ObjC-style key paths doesn't quite work because the attribute in question might have a non-ObjC type like Int32?.

I'm thinking of this in terms of being able to convert Swift key paths to string representations so that NSPredicate would understand them. For example, given a key path like MyClass.name, get "name" as a string. It would of course also work if NSPredicate could understand Swift key paths somehow (possibly by making use of the undocumented magic internal conversion described above?).

@swift-ci
Copy link
Contributor Author

swift-ci commented Feb 4, 2019

Comment by Stuart Robertson (JIRA)

I, too, am working with Realm (in both Swift and ObjC, though Swift primarily) and have run into numerous issues due to refactoring and then having string-based Realm-query strings be incorrect. The ability to use keypaths with Realm (and CoreData) will be greatly appreciated.

@swift-ci
Copy link
Contributor Author

Comment by Frank (JIRA)

I also would love to be able to use keypaths in NSPredicates. My current workaround is to use Codable models and access the rawValue of a CodingKey. But this doesn't work where I need to have different CodingKey rawValues (e.g. for JSON decoding)...

So +1 for the keyPath way!

@lilyball
Copy link
Mannequin

lilyball mannequin commented Jul 23, 2020

I really wish we could just get a string representation of all key paths instead of just the internal one for KVC-compatible key paths. But I suspect we might not be able to do this given ABI compatibility (since KeyPath operations are inlinable); just giving every key path a KVC-compatible string would presumably cause existing code to start believing that every key path is safe to use with KVC.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants