การลบล้างคุณสมบัติระดับซุปเปอร์คลาสด้วยประเภทต่างๆใน Swift


129

ใน Swift มีใครสามารถอธิบายวิธีการแทนที่คุณสมบัติในซูเปอร์คลาสด้วยออบเจ็กต์อื่นที่ย่อยจากคุณสมบัติดั้งเดิมได้หรือไม่

ยกตัวอย่างง่ายๆนี้:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override let chassis = RacingChassis() //Error here
}

สิ่งนี้ทำให้เกิดข้อผิดพลาด:

Cannot override with a stored property 'chassis'

หากฉันมีแชสซีเป็น 'var' แทนฉันจะได้รับข้อผิดพลาด:

Cannot override mutable property 'chassis' of type 'Chassis' with covariant type 'RacingChassis'

สิ่งเดียวที่ฉันพบในคำแนะนำภายใต้ "คุณสมบัติการแทนที่" ระบุว่าเราต้องแทนที่ getter และ setter ซึ่งอาจใช้ได้กับการเปลี่ยนค่าของคุณสมบัติ (ถ้าเป็น 'var') แต่สิ่งที่เกี่ยวกับการเปลี่ยนคลาสคุณสมบัติ ?

คำตอบ:


115

Swift ไม่อนุญาตให้คุณเปลี่ยนประเภทคลาสของตัวแปรหรือคุณสมบัติใด ๆ คุณสามารถสร้างตัวแปรพิเศษในคลาสย่อยที่จัดการประเภทคลาสใหม่แทนได้:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    var chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis = RacingChassis()
    override var chassis: Chassis {
        get {
            return racingChassis
        }
        set {
            if let newRacingChassis = newValue as? RacingChassis {
                racingChassis = newRacingChassis
            } else {
                println("incorrect chassis type for racecar")
            }
        }
    }
}

ดูเหมือนว่าไม่มีใครประกาศคุณสมบัติด้วยไวยากรณ์ let และแทนที่ด้วย var ในคลาสย่อยหรือในทางกลับกันซึ่งอาจเป็นเพราะการนำ superclass ไปใช้อาจไม่ได้คาดหวังว่าคุณสมบัตินั้นจะเปลี่ยนแปลงเมื่อเริ่มต้นแล้ว ดังนั้นในกรณีนี้จำเป็นต้องประกาศคุณสมบัติด้วย 'var' ในซูเปอร์คลาสด้วยเพื่อให้ตรงกับคลาสย่อย (ดังแสดงในตัวอย่างด้านบน) หากไม่สามารถเปลี่ยนซอร์สโค้ดในซูเปอร์คลาสได้อาจเป็นการดีที่สุดที่จะทำลาย RaceCar ปัจจุบันและสร้าง RaceCar ใหม่ทุกครั้งที่แชสซีจำเป็นต้องกลายพันธุ์


1
ขออภัยเพิ่งลบความคิดเห็นของฉันและเห็นการตอบกลับของคุณ ผมมีปัญหาเพราะในกรณีของฉันจริง superclass ของฉันคือชั้น Objective-C กับstrongทรัพย์สินและผมได้รับข้อผิดพลาดในการพยายามที่จะแทนที่มัน - แต่ดูเหมือนว่าฉันพลาดว่ามันแปล "ยังไม่ได้เปิดโดยปริยายตัวเลือก" ( chassis!) ใน รวดเร็วดังนั้นoverride var chassis : Chassis!แก้ไข
James

4
สิ่งนี้ใช้ไม่ได้กับ Swift 1.1 ภายใต้ Xcode 6.1 อีกต่อไป สร้างข้อผิดพลาด: "ไม่สามารถแทนที่" let "property" chassis "ที่ไม่เปลี่ยนรูปด้วย getter ของ" var "" มีความคิดสำหรับทางออกที่ดีกว่านี้หรือไม่?
Darrarski

1
นอกจากนี้ไม่สามารถใช้งานได้ใน Swift 1.2 ภายใต้ Xcode 6.3 beta อีกต่อไป ไม่สามารถแทนที่คุณสมบัติที่เปลี่ยนแปลงไม่ได้ 'A' ของประเภท 'Type1' ด้วยประเภทโควาเรียน 'Type2'
bubuxu

10
นี่เป็นความอ่อนแอจริงๆและความล้มเหลวของ Swift, imo เนื่องจาก RacingChassis เป็นแชสซีคอมไพเลอร์จึงไม่มีปัญหาในการให้คุณปรับแต่งคลาสของคุณสมบัติในคลาสย่อย หลายภาษาอนุญาตให้ทำเช่นนี้และการไม่สนับสนุนจะนำไปสู่วิธีแก้ปัญหาที่น่าเกลียดเช่นนี้ ไม่มีความผิด : /
devios1

1
ตัวเลือกระบุวิธีการแนะนำว่าตัวแปรอาจไม่มีอยู่เนื่องจากถูกจัดเตรียมโดยฟังก์ชันที่ไม่ส่งคืนค่าที่คาดไว้อีกต่อไป กรณีดังกล่าวอาจเกิดขึ้นหากฟังก์ชันถูกแทนที่ ดูคำตอบของฉันสำหรับรายละเอียด

10

ดูเหมือนว่าจะได้ผล

class Chassis {
    func description() -> String {
        return "Chassis"
    }
}
class RacingChassis : Chassis {
    override func description() -> String {
        return "Racing Chassis"
    }

    func racingChassisMethod() -> String {
        return "Wrooom"
    }
}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override var chassis: RacingChassis {
    get {
        return self.chassis
    }
    set {
        self.chassis = newValue
    }
    }
}

var car = Car()
car.chassis.description()

var raceCar = RaceCar()
raceCar.chassis.description()
raceCar.chassis.racingChassisMethod()

1
สิ่งนี้ใช้งานได้เนื่องจากคุณสมบัติของแชสซีถูกกำหนดไว้letในCarคลาสซึ่งทำให้ไม่สามารถเปลี่ยนแปลงได้ เราสามารถเปลี่ยนchassisคุณสมบัติในRaceCarชั้นเรียนเท่านั้น
David Arve

4
สิ่งนี้ใช้ไม่ได้กับ Swift 2.0 มันให้ "ข้อผิดพลาด: ไม่สามารถแทนที่ 'let' property 'chassis' ที่ไม่เปลี่ยนรูปด้วย getter of a 'var'"
mohamede1945

สิ่งนี้ใช้ไม่ได้ใน swift 4.0 เช่นกัน การดำเนินการ layground ล้มเหลว: ข้อผิดพลาด: MyPlaygrounds.playground: 14: 15: ข้อผิดพลาด: ไม่สามารถแทนที่แชสซี 'let' property 'ที่ไม่เปลี่ยนรูปด้วย getter ของ' var 'override var chassis: RacingChassis {^ MyPlaygrounds.playground: 11: 6: note : พยายามแทนที่คุณสมบัติที่นี่ให้แชสซี = แชสซี () ^
ริม

7

ลองสิ่งนี้:

class Chassis{
     var chassis{
         return "chassis"
     } 
}

class RacingChassis:Chassis{
     var racing{
         return "racing"
     } 
}

class Car<Type:Chassis> {
     let chassis: Type
     init(chassis:Type){
        self.chassis = chassis
     }
}

class RaceCar: Car<RacingChassis> {
     var description{
         return self.chassis.racing
     } 
}

แล้ว:

let racingChassis = RacingChassis()
let raceCar = RaceCar(chassis:racingChassis)
print(raceCar.description) //output:racing

รายละเอียดในhttp://www.mylonly.com/14957025459875.html


อาเรียบร้อยและสะอาด
Inder Kumar Rathore

3

Solution Dash ที่จัดเตรียมไว้ให้ใช้งานได้ดียกเว้นว่าต้องประกาศ super class ด้วยคีย์เวิร์ด let แทนที่จะเป็น var นี่คือวิธีแก้ปัญหาที่เป็นไปได้ แต่ไม่แนะนำ!

โซลูชันด้านล่างนี้จะรวบรวมด้วย Xcode 6.2, SWIFT 1.1 (หากคลาสทั้งหมดอยู่ในไฟล์ที่รวดเร็วแตกต่างกัน) แต่ควรหลีกเลี่ยงเนื่องจากสามารถนำไปสู่พฤติกรรมที่ไม่คาดคิดได้ (รวมถึงการขัดข้องโดยเฉพาะอย่างยิ่งเมื่อใช้ประเภทที่ไม่ใช่ทางเลือก) หมายเหตุ: สิ่งนี้ไม่ทำงานกับ XCODE 6.3 BETA 3, SWIFT 1.2

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    var chassis:Chassis? = Chassis()
}

class RaceCar: Car {
    override var chassis: RacingChassis? {
        get {
            return super.chassis as? RacingChassis
        }
        set {
            super.chassis = newValue
        }
    }
}

1
ใช้ไม่ได้กับ Swift 2.0 มันให้ "ไม่สามารถแทนที่คุณสมบัติที่เปลี่ยนแปลงได้ 'แชสซี' ของประเภท 'แชสซี?' กับ covariant ประเภท 'RacingChassis?' "
mohamede1945

2

ในทางทฤษฎีคุณได้รับอนุญาตให้ทำเช่นนี้ ...

class ViewController {

    var view: UIView! { return _view }

    private var _view: UIView!
}

class ScrollView : UIView {}

class ScrollViewController : ViewController {

    override var view: ScrollView! { return super.view as ScrollView! }
}

class HomeView : ScrollView {}

class HomeViewController : ScrollViewController {

    override var view: HomeView! { return super.view as HomeView! }
}

ทำงานได้อย่างสมบูรณ์แบบในสนามเด็กเล่น Xcode

แต่ถ้าคุณลองทำในโปรเจ็กต์จริงข้อผิดพลาดของคอมไพเลอร์จะบอกคุณว่า:

'view' การประกาศไม่สามารถแทนที่การประกาศ superclass มากกว่าหนึ่งรายการ

ฉันตรวจสอบเฉพาะ Xcode 6.0 GM ณ ตอนนี้

น่าเสียดายที่คุณต้องรอจนกว่า Apple จะแก้ไขปัญหานี้

ฉันได้ส่งรายงานข้อบกพร่องด้วย 18518795


นี่เป็นวิธีแก้ปัญหาที่น่าสนใจและใช้ได้ใน 6.1
Chris Conover

1

ฉันได้เห็นเหตุผลมากมายว่าทำไมการออกแบบ API โดยใช้ตัวแปรแทนฟังก์ชันจึงเป็นปัญหาและสำหรับฉันที่ใช้คุณสมบัติที่คำนวณแล้วรู้สึกเหมือนเป็นวิธีแก้ปัญหา มีเหตุผลที่ดีที่จะเก็บตัวแปรอินสแตนซ์ของคุณไว้ ที่นี่ฉันได้สร้างโปรโตคอลรถยนต์ที่สอดคล้องกับรถยนต์ โปรโตคอลนี้มีวิธีการเข้าถึงที่ส่งคืนอ็อบเจ็กต์ Chassis เนื่องจากรถเป็นไปตามนั้นคลาสย่อย RaceCar จึงสามารถแทนที่และส่งคืนคลาสย่อยแชสซีอื่นได้ สิ่งนี้ทำให้คลาส Car สามารถตั้งโปรแกรมไปยังอินเทอร์เฟซ (รถยนต์) และคลาส RaceCar ที่รู้เกี่ยวกับ RacingChassis สามารถเข้าถึงตัวแปร _racingChassis ได้โดยตรง

class Chassis {}
class RacingChassis: Chassis {}

protocol Automobile {
    func chassis() -> Chassis
}

class Car: Automobile {
    private var _chassis: Chassis

    init () {
        _chassis = Chassis()
    }

    func chassis() -> Chassis {
        return _chassis
    }
}

class RaceCar: Car {
    private var _racingChassis: RacingChassis

    override init () {
        _racingChassis = RacingChassis()
        super.init()
    }

    override func chassis() -> Chassis {
        return _racingChassis
    }
}

อีกตัวอย่างหนึ่งของสาเหตุที่การออกแบบ API โดยใช้ตัวแปรแบ่งออกคือเมื่อคุณมีตัวแปรในโปรโตคอล หากคุณต้องการแยกฟังก์ชั่นโปรโตคอลทั้งหมดออกเป็นส่วนขยายคุณสามารถทำได้ยกเว้นคุณสมบัติที่เก็บไว้ไม่สามารถวางไว้ในส่วนขยายและต้องกำหนดไว้ในคลาส (เพื่อให้สิ่งนี้รวบรวมได้คุณจะต้องยกเลิกการใส่โค้ดใน คลาส AdaptableViewController และลบตัวแปรโหมดออกจากส่วนขยาย):

protocol Adaptable {
    var mode: Int { get set }
    func adapt()
}

class AdaptableViewController: UIViewController {
    // var mode = 0
}

extension AdaptableViewController: Adaptable {

    var mode = 0 // compiler error

    func adapt() {
        //TODO: add adapt code
    }
}

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

protocol Adaptable {
    func mode() -> Int
    func adapt()
}

class AdaptableViewController: UIViewController {
}

extension AdaptableViewController: Adaptable {
    func mode() -> Int {
        return 0
    }
    func adapt() {
        // adapt code
    }
}

1

คุณสามารถบรรลุได้ด้วยการใช้ยาชื่อสามัญ:

class Descriptor {
    let var1 = "a"
}

class OtherDescriptor: Descriptor {
    let var2 = "b"
}

class Asset<D: Descriptor> {
    let descriptor: D

    init(withDescriptor descriptor: D) {
        self.descriptor = descriptor
    }

    func printInfo() {
        print(descriptor.var1)
    }
}

class OtherAsset<D: OtherDescriptor>: Asset<D> {
    override func printInfo() {
        print(descriptor.var1, descriptor.var2)
    }
}

let asset = Asset(withDescriptor: Descriptor())
asset.printInfo() // a

let otherAsset = OtherAsset(withDescriptor: OtherDescriptor())
otherAsset.printInfo() // a b

ด้วยวิธีนี้คุณจะมีรหัสปลอดภัย 100% โดยไม่ต้องบังคับแกะ

แต่นี่เป็นการแฮ็กและหากคุณจำเป็นต้องกำหนดคุณสมบัติหลายอย่างใหม่กว่าการประกาศคลาสของคุณจะดูเหมือนยุ่งเหยิงทั้งหมด ดังนั้นควรระมัดระวังกับแนวทางนี้


1

ขึ้นอยู่กับว่าคุณวางแผนจะใช้คุณสมบัติอย่างไรวิธีที่ง่ายที่สุดในการทำเช่นนี้คือใช้ประเภททางเลือกสำหรับคลาสย่อยของคุณและแทนที่didSet {}เมธอดสำหรับ super:

class Chassis { }
class RacingChassis: Chassis { }

class Car {
    // Declare this an optional type, and do your 
    // due diligence to check that it's initialized
    // where applicable
    var chassis: Chassis?
}
class RaceCar: Car {
    // The subclass is naturally an optional too
    var racingChassis: RacingChassis?
    override var chassis: Chassis {
        didSet {
            // using an optional, we try to set the type
            racingChassis = chassis as? RacingChassis
        }
    }
}

เห็นได้ชัดว่าคุณต้องใช้เวลาในการตรวจสอบเพื่อให้แน่ใจว่าสามารถเริ่มต้นคลาสได้ด้วยวิธีนี้ แต่การตั้งค่าคุณสมบัติเป็นทางเลือกคุณจะป้องกันตัวเองจากสถานการณ์ที่การร่ายไม่ได้ผลอีกต่อไป


0

คุณสามารถสร้างตัวแปรอื่นของ RacingChassis ได้

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    let chassis: Chassis
    init(){
        chassis = Chassis()
}}

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}

วิธีนี้ใช้งานได้ แต่ฉันคิดว่าคำตอบของ Dash ก้าวไปอีกขั้นโดยการลบล้างchassisและอนุญาตให้เรารับ / ตั้งค่าRacingChassisอินสแตนซ์ของเราด้วยchassisคุณสมบัติ
James

0

ลองสิ่งนี้:

class Chassis {}
class RacingChassis : Chassis {}
class SuperChassis : RacingChassis {}

class Car {
    private var chassis: Chassis? = nil
    func getChassis() -> Chassis? {
        return chassis
    }

    func setChassis(chassis: Chassis) {
        self.chassis = chassis
    }
}

class RaceCar: Car {
    private var chassis: RacingChassis {
        get {
            return getChassis() as! RacingChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = RacingChassis()
    }
}

class SuperCar: RaceCar {
    private var chassis: SuperChassis {
        get {
            return getChassis() as! SuperChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = SuperChassis()
    }
}

0
class Chassis {}
class RacingChassis : Chassis {}

class Car {
    fileprivate let theChassis: Chassis
    var chassis: Chassis {
        get {
            return theChassis
        }
    }
    fileprivate init(_ chassis: Chassis) {
        theChassis = chassis
    }
    convenience init() {
        self.init(Chassis())
    }
}
class RaceCar: Car {
    override var chassis: RacingChassis {
        get {
            return theChassis as! RacingChassis
        }
    }
    init() {
        super.init(RacingChassis())
    }
}

0

ต่อไปนี้อนุญาตให้ใช้อ็อบเจ็กต์เดียวในคลาสพื้นฐานและคลาสที่ได้รับ ในคลาสที่ได้รับใช้คุณสมบัติอ็อบเจ็กต์ที่ได้รับ

class Car {
    var chassis:Chassis?

    func inspect() {
        chassis?.checkForRust()
    }
}

class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        } 
    }

    override func inspect() {
        super.inspect()
        racingChassis?.tuneSuspension()
    }
}

0

คำตอบอื่น ๆ มีการเปลี่ยนแปลงเล็กน้อย แต่ง่ายและปลอดภัยกว่าพร้อมประโยชน์ที่ดี

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        }
    }
}

ประโยชน์ที่ได้รับ ได้แก่ ไม่มีข้อ จำกัด ว่าแชสซีต้องเป็นอะไร (var, let, optional, etc) และเป็นคลาสย่อย RaceCar ได้ง่าย คลาสย่อยของ RaceCar สามารถมีค่าที่คำนวณได้เองสำหรับแชสซี (หรือ racingChassis)


-2

เพียงตั้งค่าคุณสมบัติ imageview ใหม่ด้วยรูปแบบการตั้งชื่อที่แตกต่างกันเช่น imgview เนื่องจาก imageView เป็นคุณสมบัติของตัวเองอยู่แล้วและเราไม่สามารถกำหนดคุณสมบัติที่แข็งแกร่ง 2 คุณสมบัติได้

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