2017-02-08 4 views
6

Swift 3의 구현 인 #keyPath()을 발견하게되어 매우 기뻤습니다. 오타가 제거되고 컴파일시에 키 경로가 실제로 존재하게됩니다. 수동으로 문자열을 입력하는 것보다 훨씬 낫습니다. #keyPath()에 전달 된 문자열이 아닌 "속성 이름"을 독립적으로 저장할 수 있습니까?

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

// Works great 
let keyPathString = #keyPath(MyObject.myString) 

Swift docs list the type

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

는 "속성 이름"으로 #keyPath()에 전달된다.

"property name"

속성 이름

대물-C 런타임에서 사용할 수있는 속성을 참조해야한다. 컴파일시 키 - 경로 표현식은 문자열 리터럴로 대체됩니다.

이 "속성 이름"을 독립적으로 저장 한 다음 나중에 #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와 상호 작용한다. 임의의 키 경로 문자열 대신 유효한 속성 이름을 매개 변수로 사용하는 편리한 메서드를 작성하고 싶습니다. 이상적으로는 특정 유형에 의해 구현 된 속성 이름입니다.

func doSomethingForSpecificTypeProperty(_ propertyName: MyObject.PropertyName) { 

    let keyPathString = #keyPath(propertyName) 

    let expression = NSExpression(forKeyPath: keyPathString) 

    // ... 
} 
+0

스위프트 4의 : 당신은 이러한 목적

빠른 4 이것에 대한 짧은 코드는 다음과 같습니다에 대한 에게 일반적인 키 패스을 사용할 수 있습니다 [스마트 KeyPaths : 더 나은 키 - 값은 스위프트에 대한 코딩] :/(HTTPS /github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md) 더 나은 옵션이 추가 된 것 같습니다. 누군가 그것에 도착하기 전에 그것을 쓰고 싶다면 그 대답을 받아 들일 것입니다. – pkamb

+0

https://bugs.swift.org/browse/SR-5220 - "KeyPath의 문자열 표현을 검색하기위한 API 공개" – pkamb

+0

https://github.com/kishikawakatsumi/Kuery - "다음을 사용하는 유형 안전 핵심 데이터 쿼리 API Swift 4의 Smart KeyPaths " – pkamb

답변

4

가능한 것처럼 보이지 않습니다. 여기


는 키 경로 식 파싱 컴파일러의 코드이다 :

/// 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)); 
} 

이 코드는 제 괄호 안의 기간 구분 이름 목록을 생성 한 다음 그 표현으로 파싱하려고 . 그것은 표현을 받아들이고 어떤 종류의 스위프트 유형도 허용하지 않습니다. 코드이 아니라 데이터이 아닙니다.

+0

이 기능이 Swift의 차기 버전에서 소개되기를 바랍니다. – pkamb

1

유사한 질문이있어서 this article이 발견되었습니다.

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

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