Is it possible to save the independent property name passed to #keyPath () independently?

I was very happy to find an implementation of Swift 3 #keyPath() that would eliminate typos and enforce at compile time that there is actually a key path. Much better than manually typing lines.

https://github.com/apple/swift-evolution/blob/master/proposals/0062-objc-keypaths.md

 class MyObject { @objc var myString: String = "default" } // Works great let keyPathString = #keyPath(MyObject.myString) 

Swift docs lists the type passed to #keyPath() as the "property name".

The property name must be a reference to a property available in the Objective-C runtime. At compile time, the key path expression is replaced with a string literal.

Is it possible to save this “property name” yourself and then go to #keyPath() to create a string?

 let propertyName = MyObject.myString // error. How do I save? let string = #keyPath(propertyName) 

Is there any support for querying the name of a property belonging to a particular type?

 // something like this let typedPropertyName: MyObject.PropertyName = myString // error let string = #keyPath(typedPropertyName) 

The ultimate goal will interact with APIs that require NSExpression for the key path. I would like to write convenient methods that take the correct property name as a parameter, rather than random strings of the key path. Ideally, a property name implemented by a specific type.

 func doSomethingForSpecificTypeProperty(_ propertyName: MyObject.PropertyName) { let keyPathString = #keyPath(propertyName) let expression = NSExpression(forKeyPath: keyPathString) // ... } 
+6
source share
2 answers

It doesn't seem like it's possible.


Here is the compiler code for parsing a key expression:

 /// expr-keypath: /// '#keyPath' '(' unqualified-name ('.' unqualified-name) * ')' /// ParserResult<Expr> Parser::parseExprKeyPath() { // Consume '#keyPath'. SourceLoc keywordLoc = consumeToken(tok::pound_keyPath); // Parse the leading '('. if (!Tok.is(tok::l_paren)) { diagnose(Tok, diag::expr_keypath_expected_lparen); return makeParserError(); } SourceLoc lParenLoc = consumeToken(tok::l_paren); // Handle code completion. SmallVector<Identifier, 4> names; SmallVector<SourceLoc, 4> nameLocs; auto handleCodeCompletion = [&](bool hasDot) -> ParserResult<Expr> { ObjCKeyPathExpr *expr = nullptr; if (!names.empty()) { expr = ObjCKeyPathExpr::create(Context, keywordLoc, lParenLoc, names, nameLocs, Tok.getLoc()); } if (CodeCompletion) CodeCompletion->completeExprKeyPath(expr, hasDot); // Eat the code completion token because we handled it. consumeToken(tok::code_complete); return makeParserCodeCompletionResult(expr); }; // Parse the sequence of unqualified-names. ParserStatus status; while (true) { // Handle code completion. if (Tok.is(tok::code_complete)) return handleCodeCompletion(!names.empty()); // Parse the next name. DeclNameLoc nameLoc; bool afterDot = !names.empty(); auto name = parseUnqualifiedDeclName( afterDot, nameLoc, diag::expr_keypath_expected_property_or_type); if (!name) { status.setIsParseError(); break; } // Cannot use compound names here. if (name.isCompoundName()) { diagnose(nameLoc.getBaseNameLoc(), diag::expr_keypath_compound_name, name) .fixItReplace(nameLoc.getSourceRange(), name.getBaseName().str()); } // Record the name we parsed. names.push_back(name.getBaseName()); nameLocs.push_back(nameLoc.getBaseNameLoc()); // Handle code completion. if (Tok.is(tok::code_complete)) return handleCodeCompletion(false); // Parse the next period to continue the path. if (consumeIf(tok::period)) continue; break; } // Parse the closing ')'. SourceLoc rParenLoc; if (status.isError()) { skipUntilDeclStmtRBrace(tok::r_paren); if (Tok.is(tok::r_paren)) rParenLoc = consumeToken(); else rParenLoc = PreviousLoc; } else { parseMatchingToken(tok::r_paren, rParenLoc, diag::expr_keypath_expected_rparen, lParenLoc); } // If we cannot build a useful expression, just return an error // expression. if (names.empty() || status.isError()) { return makeParserResult<Expr>( new (Context) ErrorExpr(SourceRange(keywordLoc, rParenLoc))); } // We're done: create the key-path expression. return makeParserResult<Expr>( ObjCKeyPathExpr::create(Context, keywordLoc, lParenLoc, names, nameLocs, rParenLoc)); } 

This code first creates a list of period-separated names inside parentheses, and then tries to parse them as an expression. It takes an expression, not data of any Swift type; It takes a code, not data.

+4
source

Just a similar question arose and found in this article . You can use a common keypath for these purposes.

The short code for this in swift 4 is as follows:

 let getName = \Person.name print(p[keyPath: getName]) // or just this: print(p[keyPath: \Person.name]) 
+1
source

Source: https://habr.com/ru/post/1014816/


All Articles