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


137

특정 프로토콜에 맞는 객체를 저장할 수있는 클래스를 만들고 싶습니다. 개체는 형식화 된 배열에 저장해야합니다. 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)
        }
    }
}

2
Swift에는이를 구현하는 유형에 대해 다형성을 제공하지 않는 특별한 프로토콜 클래스가 있습니다. 이러한 프로토콜은 자체 정의에서 자체 또는 관련 유형을 사용합니다 (적도는 그 중 하나임). 경우에 따라 유형 소거 래퍼를 사용하여 컬렉션을 동형으로 만들 수도 있습니다. 예를 들어 여기 를 보십시오 .
wasdiver

답변:


48

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

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


1
나는 이것이 현재의 정답이라고 생각합니다. 네이트의 솔루션이 작동하지만 내 문제를 완전히 해결하지는 못합니다.
snod

32

다음과 같이 클래스와 함께 사용되는 클래스를 요구하는 형식 제약 조건으로 일반 클래스를 만들려고합니다 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)
        }
    }
}

해당 클래스의 객체를 어떻게 인스턴스화합니까?
snod

하나의 유형을 사용하여에 흠 ...이 방법은 잠금을 그에 부합 SomeProtocol-let protocolGroup: SomeClass<MyMemberClass> = SomeClass()
네이트 쿡

이렇게하면 클래스의 객체 만 MyMemberClass배열에 추가 할 수 있습니까?
snod

또는let foo = SomeClass<MyMemberClass>()
DarkDust

@snod Yeah, 당신이 찾고있는 것이 아닙니다. 문제는 Equatable적합성입니다. 정확한 코드를 사용할 수 있습니다. 버그 / 기능 요청을 제출할 수 있습니까?
네이트 쿡

15

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

12

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

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
            }
        }
    }

}

동일한 객체로 두 번 이상 호출 된 protocols경우에 에서 중복 항목을 허용하지 addElement않습니까?
Tom Harrington

예. 신속한 배열에는 중복 항목이 포함될 수 있습니다. 코드에서 이런 일이 발생할 수 있다고 생각되면 배열 대신 Set을 사용하거나 배열에 해당 객체가 포함되어 있지 않은지 확인하십시오.
almas

removeElement()중복을 피하려면 새 요소를 추가하기 전에 호출 할 수 있습니다 .
Georgios

배열을 제어하는 ​​방법은 공중에 있습니다. 답변 주셔서 감사합니다
Hill

9

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

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
        }
    }
}

4
중요한 것은 놓쳤습니다. OP는 프로토콜이 프로토콜을 상속하기를 원합니다 Equatable. 큰 차이가 있습니다.
wasdiver

@werediver 그렇게 생각하지 않습니다. 그는 SomeProtocol형식이 지정된 배열에 맞는 객체를 저장하려고합니다 . Equatable배열에서 요소를 제거하는 경우에만 적합성이 필요합니다. 내 솔루션은 Equatable프로토콜 을 준수하는 모든 Swift 유형과 함께 사용할 수 있기 때문에 @almas 솔루션의 개선 된 버전입니다 .
bzz

2

나는 당신의 주된 목표가 어떤 프로토콜에 맞는 객체들의 컬렉션을 유지하고이 컬렉션에 추가하고 삭제하는 것입니다. 이것은 클라이언트 "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)
    }
}

0

내가 발견 하지 그 블로그 게시물에 순수 순수 스위프트 솔루션 : 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.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.