传递给#keyPath()的非字符串“属性名称”是否可以单独保存?

By simon at 2018-02-28 • 0人收藏 • 46人看过

我很高兴找到Swift 3的实现#keyPath(),这将会 消除拼写错误并在编译时执行t他的关键路径实际上存在。 比手动键入字符串好得多。

class MyObject {
    @objc var myString: String = "default"
}

// Works great
let keyPathString = #keyPath(MyObject.myString)
[Swift文档列出 类型](https://developer.apple.com/library/content/documentat离子/夫特/概念/ SwiftProgrammingLanguage / Expressions.html) 被传递到#keyPath()作为“属性名称”。 [![“propeRTY 名“](https://i.stack.imgur.com/k9MMX.png)](https://i.stack.imgur.com/k9MMX.png)

属性名称必须是对的引用一个可用的属性 Objective-C运行时。在编译时,键路径表达式被替换为a 弦升人。 是否可以独立保存这个“属性名称”,然后传递给 #keyPath()创建一个字符串?

let propertyName = MyObject.myString // error. How do I save?
let string = #keyPath(propertyName)
是否有任何要求属性名称属于特定的支持 类型?
// something like this
let typedPropertyName: MyObject.PropertyName = myString // error
let string = #keyPath(typedPropertyName)
最终目标是互动使用需要NSExpression的API 为关键路径。我想写一些有效的便利方法 正确ty名称作为参数,而不是随机键路径字符串。理想情况下,a 由特定类型实现的属性名称。
func doSomethingForSpecificTypeProperty(_ propertyName: MyObject.PropertyName) {

    let keyPathString = #keyPath(propertyName)

    let expression = NSExpression(forKeyPath: keyPathString)

    // ...
}

3 个回复 | 最后更新于 2018-02-28
2018-02-28   #1

它看起来不可能。


以下是编译器的代码来解析关键路径表达式:

///   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));
}
此代码冷杉t在内部创建一个以句点分隔的名称列表 括号,然后它试图将它们解析为表达式。它接受TS 表达式而不是任何Swift类型的数据;它接受code,而不是data

2018-02-28   #2

它看起来不可能。


以下是编译器的代码来解析关键路径表达式:

///   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));
}
此代码冷杉t在内部创建一个以句点分隔的名称列表 括号,然后它试图将它们解析为表达式。它接受TS 表达式而不是任何Swift类型的数据;它接受code,而不是data

2018-02-28   #3

刚刚提出类似的问题,发现[这个 文章(https://www.klundberg.com/blog/swift-4-keypaths-and-you/)。您可以 üse * KeyPath *通用于这些目的 这个在swift 4中的短代码如下所示:

let getName = \Person.name
print(p[keyPath: getName])

// or just this:
print(p[keyPath: \Person.name])

登录后方可回帖

Loading...