การใช้โปรโตคอลเป็นประเภทอาร์เรย์และพารามิเตอร์ฟังก์ชันอย่างรวดเร็ว


139

ฉันต้องการสร้างคลาสที่สามารถจัดเก็บอ็อบเจ็กต์ที่สอดคล้องกับโปรโตคอลบางอย่าง วัตถุควรถูกเก็บไว้ในอาร์เรย์ที่พิมพ์ ตามโปรโตคอลเอกสาร 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 มีโปรโตคอลระดับพิเศษซึ่งไม่ได้ให้ความหลากหลายมากกว่าประเภทที่นำไปใช้ โปรโตคอลดังกล่าวใช้ Self หรือ Associatedtype ในคำจำกัดความ (และ Equatable เป็นหนึ่งในนั้น) ในบางกรณีคุณสามารถใช้กระดาษห่อหุ้มชนิดลบเพื่อทำให้คอลเลกชันของคุณเป็นโฮโมมอร์ฟิก ดูตัวอย่างที่นี่
werediver

คำตอบ:


51

คุณประสบปัญหากับโปรโตคอลใน Swift ซึ่งยังไม่มีวิธีแก้ปัญหาที่ดี

ดูเพิ่มเติมที่Extending Array เพื่อตรวจสอบว่าเรียงลำดับใน Swift หรือไม่ ซึ่งมีคำแนะนำเกี่ยวกับวิธีการแก้ไขปัญหาที่อาจเหมาะกับปัญหาเฉพาะของคุณ (คำถามของคุณเป็นเรื่องธรรมดามากบางทีคุณอาจหาวิธีแก้ปัญหาโดยใช้คำตอบเหล่านี้)


1
ฉันคิดว่านี่เป็นคำตอบที่ถูกต้องสำหรับตอนนี้ โซลูชันของ Nate ใช้งานได้ แต่ไม่สามารถแก้ปัญหาของฉันได้ทั้งหมด
snod

33

คุณต้องการสร้างคลาสทั่วไปโดยมีข้อ จำกัด ประเภทที่ต้องการให้คลาสที่ใช้เป็นไปตาม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 ใช่ซึ่งไม่ใช่สิ่งที่คุณกำลังมองหา ปัญหาคือความEquatableสอดคล้อง - โดยที่คุณไม่สามารถใช้รหัสที่แน่นอนได้ อาจจะยื่นคำร้องขอบั๊ก / คุณสมบัติ?
Nate Cook

17

ใน 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

13

วิธีแก้ไขที่ จำกัด ที่ฉันพบคือการทำเครื่องหมายโปรโตคอลเป็นโปรโตคอลคลาสเท่านั้น ซึ่งจะช่วยให้คุณสามารถเปรียบเทียบวัตถุโดยใช้ตัวดำเนินการ '===' ฉันเข้าใจว่าสิ่งนี้ใช้ไม่ได้กับโครงสร้าง ฯลฯ แต่ก็ดีพอในกรณีของฉัน

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 แทนอาร์เรย์หรือตรวจสอบให้แน่ใจว่าอาร์เรย์ไม่มีวัตถุนั้นอยู่แล้ว
อัลมาส

คุณสามารถโทรremoveElement()ก่อนที่จะต่อท้ายองค์ประกอบใหม่ได้หากคุณต้องการหลีกเลี่ยงรายการที่ซ้ำกัน
Georgios

ฉันหมายถึงวิธีที่คุณควบคุมอาร์เรย์ของคุณอยู่ในอากาศใช่ไหม? ขอบคุณสำหรับคำตอบ
Reimond Hill

8

วิธีแก้ปัญหาค่อนข้างง่าย:

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โปรโตคอล มันสร้างความแตกต่างอย่างมาก
werediver

ฉันไม่คิดอย่างนั้น เขาต้องการจัดเก็บวัตถุที่สอดคล้องกับSomeProtocolอาร์เรย์ที่พิมพ์ Equatableความสอดคล้องจำเป็นสำหรับการลบองค์ประกอบออกจากอาร์เรย์เท่านั้น โซลูชันของฉันเป็นโซลูชัน @almas เวอร์ชันปรับปรุงเนื่องจากสามารถใช้กับ Swift ประเภทใดก็ได้ที่สอดคล้องกับEquatableโปรโตคอล
bzz

2

ฉันคิดว่าเป้าหมายหลักของคุณคือการเก็บรวบรวมวัตถุที่สอดคล้องกับโปรโตคอลบางอย่างเพิ่มลงในคอลเล็กชันนี้และลบออกจากมัน นี่คือฟังก์ชันการทำงานตามที่ระบุไว้ในไคลเอนต์ของคุณ "SomeClass" การสืบทอดที่เท่าเทียมกันนั้นต้องการตัวเองและไม่จำเป็นสำหรับฟังก์ชันนี้ เราสามารถทำงานนี้ในอาร์เรย์ใน Obj-C โดยใช้ฟังก์ชัน "ดัชนี" ที่สามารถใช้ตัวเปรียบเทียบแบบกำหนดเองได้ แต่ Swift ไม่รองรับ ดังนั้นทางออกที่ง่ายที่สุดคือใช้พจนานุกรมแทนอาร์เรย์ดังที่แสดงในโค้ดด้านล่าง ฉันได้ให้ getElements () ซึ่งจะคืนอาร์เรย์โปรโตคอลที่คุณต้องการ ดังนั้นใครก็ตามที่ใช้ SomeClass จะไม่รู้ด้วยซ้ำว่ามีการใช้พจนานุกรมสำหรับการนำไปใช้งาน

ไม่ว่าในกรณีใดคุณจะต้องมีคุณสมบัติที่แตกต่างเพื่อแยก objets ของคุณฉันคิดว่ามันคือ "ชื่อ" โปรดตรวจสอบให้แน่ใจว่า do element.name = "foo" ของคุณเมื่อคุณสร้างอินสแตนซ์ SomeProtocol ใหม่ หากไม่ได้ตั้งชื่อคุณยังคงสามารถสร้างอินสแตนซ์ได้ แต่จะไม่ถูกเพิ่มลงในคอลเล็กชันและ addElement () จะส่งคืน "เท็จ"

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

ฉันพบโซลูชัน Swift ที่ไม่บริสุทธิ์ในบล็อกโพสต์นั้น: 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
}

หมายเหตุ: ในกรณีนี้วัตถุของคุณไปตามกลไกการสืบทอดต้องจากSomeProtocolNSObject

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.