【问题标题】:Can the non-string "property name" passed to #keyPath() be saved independently?传递给#keyPath() 的非字符串“属性名称”可以独立保存吗?
【发布时间】:2017-06-26 15:23:36
【问题描述】:

我很高兴找到 Swift 3 的 #keyPath() 实现,它将消除拼写错误并在编译时强制密钥路径确实存在。比手动输入字符串要好得多。

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 list the type 作为“属性名称”传递到 #keyPath()

属性名称必须是对在 Objective-C 运行时可用的属性的引用。在编译时,key-path 表达式被字符串文字替换。

是否可以独立保存这个“属性名”,然后传递给#keyPath()创建一个String?

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 进行交互。我想编写将有效属性名称作为参数的便捷方法,而不是随机键路径字符串。理想情况下,由特定类型实现的属性名称。

func doSomethingForSpecificTypeProperty(_ propertyName: MyObject.PropertyName) {

    let keyPathString = #keyPath(propertyName)

    let expression = NSExpression(forKeyPath: keyPathString)

    // ...
}

【问题讨论】:

标签: swift key-value-observing kvc keypaths


【解决方案1】:

刚想出了类似的问题,发现this article。您可以将 KeyPath 泛型用于这些目的

swift 4 中的短代码如下所示:

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

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

【讨论】:

    【解决方案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));
    }
    

    此代码首先在括号内创建一个以句点分隔的名称列表,然后尝试将它们解析为表达式。它接受一个表达式而不是任何 Swift 类型的数据;它接受 code,而不是 data

    【讨论】:

    • 希望这将在未来的 Swift 版本中引入!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-28
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 2021-11-30
    • 1970-01-01
    相关资源
    最近更新 更多