[ios] 신속하게 배열 유형 및 기능 매개 변수로 프로토콜 사용

특정 프로토콜에 맞는 객체를 저장할 수있는 클래스를 만들고 싶습니다. 개체는 형식화 된 배열에 저장해야합니다. Swift 문서에 따르면 프로토콜은 유형으로 사용할 수 있습니다. 

유형이기 때문에 다음을 포함하여 다른 유형이 허용되는 여러 위치에서 프로토콜을 사용할 수 있습니다.

  • 함수, 메소드 또는 이니셜 라이저의 매개 변수 유형 또는 리턴 유형
  • 상수, 변수 또는 속성의 유형으로
  • 배열, 사전 또는 다른 컨테이너의 항목 유형으로

그러나 다음은 컴파일러 오류를 생성합니다.

프로토콜 ‘SomeProtocol’은 자체 또는 연관된 유형 요구 사항이 있으므로 일반 제한 조건으로 만 사용할 수 있습니다.

이것을 어떻게 해결해야합니까?

protocol SomeProtocol: Equatable {
    func bla()
}

class SomeClass {

    var protocols = [SomeProtocol]()

    func addElement(element: SomeProtocol) {
        self.protocols.append(element)
    }

    func removeElement(element: SomeProtocol) {
        if let index = find(self.protocols, element) {
            self.protocols.removeAtIndex(index)
        }
    }
}



답변

아직 좋은 해결책이없는 Swift의 프로토콜 문제에 변형이 있습니다.

Swift에서 정렬되어 있는지 확인하려면 배열 확장을 참조하십시오 . , 특정 문제에 적합 할 수있는 해결 방법에 대한 제안이 포함되어 있습니다 (질문은 매우 일반적입니다. 이러한 답변을 사용하여 해결 방법을 찾을 수도 있습니다).


답변

다음과 같이 클래스와 함께 사용되는 클래스를 요구하는 형식 제약 조건으로 일반 클래스를 만들려고합니다 SomeProtocol.

class SomeClass<T: SomeProtocol> {
    typealias ElementType = T
    var protocols = [ElementType]()

    func addElement(element: ElementType) {
        self.protocols.append(element)
    }

    func removeElement(element: ElementType) {
        if let index = find(self.protocols, element) {
            self.protocols.removeAtIndex(index)
        }
    }
}


답변

Swift에는이를 구현하는 유형에 대해 다형성을 제공하지 않는 특별한 프로토콜 클래스가 있습니다. 이러한 프로토콜을 사용 Self또는 associatedtype키워드의 정의에 (그리고 Equatable그 중 하나입니다).

경우에 따라 유형 소거 래퍼를 사용하여 컬렉션을 동형으로 만들 수도 있습니다. 아래는 예입니다.

// This protocol doesn't provide polymorphism over the types which implement it.
protocol X: Equatable {
    var x: Int { get }
}

// We can't use such protocols as types, only as generic-constraints.
func ==<T: X>(a: T, b: T) -> Bool {
    return a.x == b.x
}

// A type-erased wrapper can help overcome this limitation in some cases.
struct AnyX {
    private let _x: () -> Int
    var x: Int { return _x() }

    init<T: X>(_ some: T) {
        _x = { some.x }
    }
}

// Usage Example

struct XY: X {
    var x: Int
    var y: Int
}

struct XZ: X {
    var x: Int
    var z: Int
}

let xy = XY(x: 1, y: 2)
let xz = XZ(x: 3, z: 4)

//let xs = [xy, xz] // error
let xs = [AnyX(xy), AnyX(xz)]
xs.forEach { print($0.x) } // 1 3


답변

내가 찾은 제한된 해결책은 프로토콜을 클래스 전용 프로토콜로 표시하는 것입니다. ‘===’연산자를 사용하여 객체를 비교할 수 있습니다. 나는 이것이 구조체 등으로는 작동하지 않는다는 것을 이해하지만 내 경우에는 충분했습니다.

protocol SomeProtocol: class {
    func bla()
}

class SomeClass {

    var protocols = [SomeProtocol]()

    func addElement(element: SomeProtocol) {
        self.protocols.append(element)
    }

    func removeElement(element: SomeProtocol) {
        for i in 0...protocols.count {
            if protocols[i] === element {
                protocols.removeAtIndex(i)
                return
            }
        }
    }

}


답변

해결책은 매우 간단합니다.

protocol SomeProtocol {
    func bla()
}

class SomeClass {
    init() {}

    var protocols = [SomeProtocol]()

    func addElement<T: SomeProtocol where T: Equatable>(element: T) {
        protocols.append(element)
    }

    func removeElement<T: SomeProtocol where T: Equatable>(element: T) {
        protocols = protocols.filter {
            if let e = $0 as? T where e == element {
                return false
            }
            return true
        }
    }
}


답변

나는 당신의 주된 목표가 어떤 프로토콜에 맞는 객체들의 컬렉션을 유지하고이 컬렉션에 추가하고 삭제하는 것입니다. 이것은 클라이언트 “SomeClass”에 명시된 기능입니다. 동일 상속은 자체를 필요로하며이 기능에는 필요하지 않습니다. 커스텀 비교기를 사용할 수있는 “index”함수를 사용하여 Obj-C의 배열에서이 작업을 수행 할 수 있었지만 Swift에서는 지원되지 않습니다. 따라서 가장 간단한 해결책은 아래 코드와 같이 배열 대신 사전을 사용하는 것입니다. 원하는 프로토콜 배열을 반환하는 getElements ()를 제공했습니다. 따라서 SomeClass를 사용하는 사람은 사전이 구현에 사용되었음을 알지 못합니다.

어쨌든, 당신은 당신의 오브제를 분리하기 위해 구별되는 속성이 필요하기 때문에, 나는 그것이 “이름”이라고 가정했습니다. 새 SomeProtocol 인스턴스를 작성할 때 do element.name = “foo”인지 확인하십시오. 이름을 설정하지 않으면 인스턴스를 만들 수는 있지만 컬렉션에 추가되지 않으며 addElement ()는 “false”를 반환합니다.

protocol SomeProtocol {
    var name:String? {get set} // Since elements need to distinguished,
    //we will assume it is by name in this example.
    func bla()
}

class SomeClass {

    //var protocols = [SomeProtocol]() //find is not supported in 2.0, indexOf if
     // There is an Obj-C function index, that find element using custom comparator such as the one below, not available in Swift
    /*
    static func compareProtocols(one:SomeProtocol, toTheOther:SomeProtocol)->Bool {
        if (one.name == nil) {return false}
        if(toTheOther.name == nil) {return false}
        if(one.name ==  toTheOther.name!) {return true}
        return false
    }
   */

    //The best choice here is to use dictionary
    var protocols = [String:SomeProtocol]()


    func addElement(element: SomeProtocol) -> Bool {
        //self.protocols.append(element)
        if let index = element.name {
            protocols[index] = element
            return true
        }
        return false
    }

    func removeElement(element: SomeProtocol) {
        //if let index = find(self.protocols, element) { // find not suported in Swift 2.0


        if let index = element.name {
            protocols.removeValueForKey(index)
        }
    }

    func getElements() -> [SomeProtocol] {
        return Array(protocols.values)
    }
}


답변

내가 발견 하지 그 블로그 게시물에 순수 순수 스위프트 솔루션 :
http://blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/

요령은 NSObjectProtocol도입 하면서 따라야한다 isEqual(). 따라서 Equatable프로토콜과 기본 사용법 을 사용하는 대신 ==고유 한 함수를 작성하여 요소를 찾아서 제거 할 수 있습니다.

find(array, element) -> Int?함수 구현은 다음과 같습니다 .

protocol SomeProtocol: NSObjectProtocol {

}

func find(protocols: [SomeProtocol], element: SomeProtocol) -> Int? {
    for (index, object) in protocols.enumerated() {
        if (object.isEqual(element)) {
            return index
        }
    }

    return nil
}

참고 :이 경우 준수하는 객체 SomeProtocol는에서 상속해야합니다 NSObject.