[xcode] 선택적 바인딩을 통해 Swift에서 안전 (경계 검사) 배열 조회?

Swift에 배열이 있고 범위를 벗어난 인덱스에 액세스하려고하면 예기치 않은 런타임 오류가 있습니다.

var str = ["Apple", "Banana", "Coconut"]

str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION

그러나 Swift가 제공하는 모든 옵션 체인과 안전 을 생각 했을 것입니다.

let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
    print(nonexistent)
    ...do other things with nonexistent...
}

대신에:

let theIndex = 3
if (theIndex < str.count) {         // Bounds check
    let nonexistent = str[theIndex] // Lookup
    print(nonexistent)   
    ...do other things with nonexistent... 
}

그러나 이것은 사실이 아닙니다. 저는 ol ‘ if문을 사용하여 색인이보다 작거나 같은지 확인해야합니다 str.count.

subscript()구현을 추가하려고 시도했지만 호출을 원래 구현으로 전달하거나 첨자 표기법을 사용하지 않고 항목 (인덱스 기반)에 액세스하는 방법을 모르겠습니다.

extension Array {
    subscript(var index: Int) -> AnyObject? {
        if index >= self.count {
            NSLog("Womp!")
            return nil
        }
        return ... // What?
    }
}



답변

Alex의 답변 에는 질문에 대한 좋은 조언과 해결책이 있지만이 기능을 구현하는 더 좋은 방법을 발견했습니다.

스위프트 3.2 이상

extension Collection {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

스위프트 3.0 및 3.1

extension Collection where Indices.Iterator.Element == Index {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Swift 3 용 솔루션을 개발 한 Hamish에게 감사의 뜻을 전 합니다.

스위프트 2

extension CollectionType {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

let array = [1, 2, 3]

for index in -20...20 {
    if let item = array[safe: index] {
        print(item)
    }
}


답변

이 동작을 정말로 원한다면 배열 대신 사전을 원하는 것처럼 냄새가납니다. 사전 반환 nil에 접근하는 열쇠가 어디에 배열의 키, 무엇이든 될 수 있기 때문에이 키가 사전에 존재 알고 더 힘들어 때문에 의미가있는 키가없는 경우 필수 :의 범위 0count. 그리고 그것은 당신이 할 수있는 범위, 반복하는 매우 일반적입니다 확신 루프의 각 반복에 실제 값을 가지고 있습니다.

이 방법으로 작동하지 않는 이유는 Swift 개발자가 선택한 디자인 선택이라고 생각합니다. 예를 들어 보자.

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"

배열을 사용하는 대부분의 경우처럼 인덱스가 존재한다는 것을 이미 알고 있다면이 코드가 좋습니다. 첨자에 액세스하는 가능성이 반환 할 경우, nil당신은 반환 형식 변경Arraysubscript옵션으로 방법을. 코드가 다음과 같이 변경됩니다.

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
//                                     ^ Added

즉, 범위를 벗어난 인덱스에 거의 액세스 할 수 없기 때문에 배열을 반복하거나 알려진 인덱스로 다른 작업을 수행 할 때마다 선택 사항을 래핑 해제해야합니다. Swift 설계자는 범위를 벗어난 인덱스에 액세스 할 때 런타임 예외를 희생하면서 옵션의 언 래핑을 줄 이도록 선택했습니다. 그리고 nil데이터 어딘가에서 예상하지 못한 논리 오류보다 충돌이 바람직합니다 .

그리고 나는 그들에 동의합니다. 따라서 Array선택 사항이 아닌 값을 기대하는 모든 코드를 배열에서 분리하므로 기본 구현을 변경하지 않습니다 .

대신에 서브 클래스 Array를 작성하고 subscript옵션을 리턴하도록 대체 할 수 있습니다 . 또는 더 실제로 Array는이를 수행하는 비 첨자 방법으로 확장 할 수 있습니다 .

extension Array {

    // Safely lookup an index that might be out of bounds,
    // returning nil if it does not exist
    func get(index: Int) -> T? {
        if 0 <= index && index < count {
            return self[index]
        } else {
            return nil
        }
    }
}

var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
    print("I ate a \( fruit )")
    // I ate a Banana
}

if let fruit = fruits.get(3) {
    print("I ate a \( fruit )")
    // never runs, get returned nil
}

스위프트 3 업데이트

func get(index: Int) ->T? 로 교체해야합니다 func get(index: Int) ->Element?


답변

Nikita Kukushkin의 답변을 바탕으로 때로는 때로는 색인뿐만 아니라 배열 색인에 안전하게 할당해야합니다.

myArray[safe: badIndex] = newValue

다음은 safe : 매개 변수 이름을 추가하여 가변 배열 인덱스에 안전하게 쓸 수있는 Nikita의 답변 (Swift 3.2)에 대한 업데이트입니다.

extension Collection {
    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[ index] : nil
    }
}

extension MutableCollection {
    subscript(safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[ index] : nil
        }

        set(newValue) {
            if let newValue = newValue, indices.contains(index) {
                self[ index] = newValue
            }
        }
    }
}


답변

스위프트 2에서 유효

이것은 이미 시간을 많이 대답되었지만, 나는 피 각질의 words¹에있는 스위프트 프로그래밍의 패션이 어디로 가는지에 라인에서 더 많은 답을 제시하고 싶습니다 : “생각해 protocol최초의”

• 우리는 무엇을하고 싶은가?
안전 할 때만 주어진 인덱스 의 요소를 가져 Array옵니다. nil그렇지 않으면
•이 기능은 무엇을 구현해야합니까?
Array subscript보내고
그것은 어디에서이 기능을 얻을 않습니다 •?
모듈 의 정의는 다음 struct Array과 같습니다.Swift
• 더 일반적인 / 추상적 인 것은 없습니까?
그것은 또한 protocol CollectionType그것을 보장합니다 채택
• 더 일반적인 / 추상적 인 것은 없습니까?
그것은 또한 채택 protocol Indexable한다 …
그렇습니다, 우리가 할 수있는 최선의 소리. 그런 다음 원하는 기능을 갖도록 확장 할 수 있습니까?
그러나 우리는 매우 유형 (없음 제한 Int없음 ()와 속성을count)와 함께 작동합니다!
• 충분할 것입니다. Swift의 stdlib는 꽤 잘 수행됩니다.)

extension Indexable {
    public subscript(safe safeIndex: Index) -> _Element? {
        return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
    }
}

¹ : 사실이 아니지만 아이디어를 제공합니다.


답변

extension Array {
    subscript (safe index: Index) -> Element? {
        return 0 <= index && index < count ? self[index] : nil
    }
}
  • O (1) 성능
  • 안전한 타입
  • [MyType?]에 대한 Optionals를 올바르게 처리합니다 (MyType ??을 반환합니다. 두 수준에서 모두 줄 바꿈 할 수 없음).
  • 세트에 문제를 일으키지 않습니다
  • 간결한 코드

다음은 내가 당신을 위해 실행 한 테스트입니다.

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int?
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?


답변

  • 배열은 nil 값을 저장할 수 있기 때문에 array [index] 호출이 범위를 벗어난 경우 nil을 반환하는 것은 의미가 없습니다.
  • 사용자가 범위를 벗어난 문제를 처리하는 방법을 모르기 때문에 사용자 지정 연산자를 사용하는 것은 적합하지 않습니다.
  • 반대로, 포장 풀기 대상물에는 기존의 제어 흐름을 사용하고 유형 안전을 보장하십시오.

index = array.checkIndexForSafety (index : Int) 인 경우

  let item = array[safeIndex: index] 

index = array.checkIndexForSafety (index : Int) 인 경우

  array[safeIndex: safeIndex] = myObject
extension Array {

    @warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {

        if indices.contains(index) {

            // wrap index number in object, so can ensure type safety
            return SafeIndex(indexNumber: index)

        } else {
            return nil
        }
    }

    subscript(index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

    // second version of same subscript, but with different method signature, allowing user to highlight using safe index
    subscript(safeIndex index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

}

public class SafeIndex {

    var indexNumber:Int

    init(indexNumber:Int){
        self.indexNumber = indexNumber
    }
}


답변

스위프트 4

보다 전통적인 구문을 선호하는 사람들을위한 확장 :

extension Array {

    func item(at index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}