ผู้ได้รับทรัพย์สินและผู้ตั้งถิ่นฐาน


195

ด้วยคลาสแบบง่ายนี้ฉันได้รับคำเตือนคอมไพเลอร์

กำลังพยายามแก้ไข / เข้าถึงxภายใน setter / getter ของตนเอง

และเมื่อฉันใช้มันเช่นนี้

var p: point = Point()
p.x = 12

ฉันได้รับ EXC_BAD_ACCESS ฉันจะทำสิ่งนี้ได้อย่างไรถ้าไม่มี ivars สำรองอย่างชัดเจน

class Point {

    var x: Int {
        set {
            x = newValue * 2 //Error
        }
        get {
            return x / 2 //Error
        }
    }
    // ...
}

1
การทำเช่นนี้จะทำให้โหลดเพิ่มบนคอมไพเลอร์และจะใช้ CPU อย่างจริงจัง ฉันเพิ่งทำอย่างนั้น: | . นี่เป็นข้อผิดพลาดที่ฉันกำลังสร้าง (ฉันใช้สนามเด็กเล่น)
ฮันนี่

ลักษณะการทำงานนี้จะทำให้เกิดความสับสนแทนการใช้ที่คุณต้องการจะใช้set didSetคุณสมบัติทำงานแตกต่างกันในสวิฟท์กว่า Objective-C หรือภาษาอื่น ๆ setเมื่อคุณใช้ ดูคำตอบด้านล่างจาก @jackและตัวอย่างของdidSetจาก @cSquirrel
Paul Solt

คำตอบ:


232

Setters และ Getters นำไปใช้กับcomputed properties; คุณสมบัติดังกล่าวไม่มีที่เก็บข้อมูลในอินสแตนซ์ - ค่าจาก getter นั้นหมายถึงการคำนวณจากคุณสมบัติของอินสแตนซ์อื่น ในกรณีของคุณไม่มีxการมอบหมาย

อย่างชัดเจน: "ฉันจะทำสิ่งนี้ได้อย่างไรโดยไม่ต้องสำรองข้อมูลอย่างชัดเจน" คุณไม่สามารถ - คุณจะต้องมีอะไรบางอย่างในการสำรองข้อมูลสถานที่ให้บริการคำนวณ ลองสิ่งนี้:

class Point {
  private var _x: Int = 0             // _x -> backingX
  var x: Int {
    set { _x = 2 * newValue }
    get { return _x / 2 }
  }
}

โดยเฉพาะใน Swift REPL:

 15> var pt = Point()
pt: Point = {
  _x = 0
}
 16> pt.x = 10
 17> pt
$R3: Point = {
  _x = 20
}
 18> pt.x
$R4: Int = 10

106

Setters / getters ใน Swift ค่อนข้างแตกต่างจาก ObjC สถานที่ให้บริการจะกลายเป็นสถานที่ให้บริการคำนวณซึ่งหมายความว่ามันไม่ได้มีตัวแปรการสนับสนุนเช่น_xในขณะที่มันจะอยู่ใน ObjC

ในรหัสวิธีการแก้ปัญหาด้านล่างนี้คุณสามารถดูxTimesTwoไม่ได้เก็บอะไร แต่เพียงคำนวณxผลจาก

ดูเอกสารอย่างเป็นทางการเกี่ยวกับคุณสมบัติที่คำนวณได้

ฟังก์ชั่นที่คุณต้องการนอกจากนี้ยังอาจจะมีผู้สังเกตการณ์ทรัพย์สิน

สิ่งที่คุณต้องการคือ:

var x: Int

var xTimesTwo: Int {
    set {
       x = newValue / 2
    }
    get {
        return x * 2
    }
}

คุณสามารถแก้ไขคุณสมบัติอื่น ๆ ภายใน setter / getters ซึ่งเป็นคุณสมบัติที่มีไว้สำหรับ


1
ใช่นั่นคือสิ่งที่ฉันกำลังอ่านในเอกสารประกอบ ฉันข้ามส่วนคุณสมบัติและดูเหมือนว่าไม่มีวิธีแก้ไขปัญหานี้ใช่ไหม
Atomix

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

5
คุณสามารถใช้didSetซึ่งจะช่วยให้คุณเปลี่ยนค่าได้ทันทีหลังจากที่มีการตั้งค่า ไม่มีอะไรที่จะได้รับ ...
ObjectiveCsam

1
หมายเหตุ: หากคุณต้องการที่จะเปลี่ยนค่าเพราะมันเป็นการป้อนข้อมูลที่ไม่ถูกต้องคุณจะต้องตั้งค่าxกลับไปในoldValue didSetการเปลี่ยนแปลงในพฤติกรรมนี้ทำให้เกิดความสับสนมากมาจากคุณสมบัติ Objective-C ขอบคุณ!
Paul Solt

105

คุณสามารถปรับแต่งค่าชุดโดยใช้คุณสมบัติผู้สังเกตการณ์ ในการทำเช่นนี้ให้ใช้ 'didSet' แทน 'set'

class Point {

var x: Int {
    didSet {
        x = x * 2
    }
}
...

สำหรับทะเยอทะยาน ...

class Point {

var doubleX: Int {
    get {
        return x / 2
    }
}
...

คุณจะเริ่มต้นxด้วยค่าเริ่มต้นในรูปแบบนี้ได้อย่างไร
i_am_jorf

3
var x: Int = 0 { didSet { ...ฉันเห็นมันจะเป็น:
i_am_jorf

31

เพื่ออธิบายรายละเอียดเกี่ยวกับคำตอบของ GoZoner:

ปัญหาที่แท้จริงของคุณที่นี่คือคุณโทรหาผู้ทะเยอทะยานซ้ำ

var x:Int
    {
        set
        {
            x = newValue * 2 // This isn't a problem
        }
        get {
            return x / 2 // Here is your real issue, you are recursively calling 
                         // your x property's getter
        }
    }

เช่นเดียวกับความคิดเห็นของรหัสที่แนะนำข้างต้นคุณกำลังเรียกใช้ตัวเรียกใช้คุณสมบัติ x ซึ่งจะดำเนินการต่อไปจนกว่าคุณจะได้รับรหัส EXC_BAD_ACCESS (คุณสามารถเห็นตัวหมุนที่มุมล่างขวาของสภาพแวดล้อมสนามเด็กเล่นของ Xcode)

พิจารณาตัวอย่างจากเอกสารประกอบของ Swift :

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

สังเกตว่าทรัพย์สินที่คำนวณโดยศูนย์ไม่เคยแก้ไขหรือส่งคืนตัวเองในการประกาศของตัวแปร


กฎของหัวแม่มือจะไม่เคยเข้าถึงคุณสมบัติตัวเองจากภายในgetทะเยอทะยานเช่น เพราะนั่นจะเรียกอีกอย่างหนึ่งgetซึ่งจะเรียกอีกอย่าง . . อย่าแม้แต่จะพิมพ์มัน เนื่องจากการพิมพ์ยังต้องการ 'รับ' ค่าก่อนจึงจะสามารถพิมพ์ได้!
ฮันนี่

19

เพื่อแทนที่setterและgetterสำหรับตัวแปร swift ให้ใช้รหัสที่ระบุด้านล่าง

var temX : Int? 
var x: Int?{

    set(newX){
       temX = newX
    }

    get{
        return temX
    }
}

เราจำเป็นต้องเก็บค่าของตัวแปรไว้ในตัวแปรชั่วคราวเนื่องจากการพยายามเข้าถึงตัวแปรตัวเดียวที่มีการแทนที่ getter / setter จะส่งผลให้เกิดลูปไม่สิ้นสุด

เราสามารถเรียกผู้ตั้งตัวได้อย่างนี้

x = 10

ทะเยอทะยานจะถูกเรียกเมื่อยิงเส้นด้านล่างของรหัส

var newVar = x

8

คุณกำลังซ้ำกำหนดด้วยx xราวกับว่ามีคนถามคุณว่าคุณอายุเท่าไหร่? และคุณตอบว่า "ฉันอายุสองเท่า" ซึ่งไม่มีความหมาย

คุณต้องบอกว่าฉันอายุสองเท่าของจอห์นหรือตัวแปรอื่น ๆยกเว้นตัวคุณเอง

ตัวแปรที่คำนวณจะขึ้นอยู่กับตัวแปรอื่นเสมอ


กฎของหัวแม่มือจะไม่เคยเข้าถึงคุณสมบัติตัวเองจากภายในgetทะเยอทะยานเช่น เพราะนั่นจะเรียกอีกอย่างหนึ่งgetซึ่งจะเรียกอีกอย่าง . . อย่าแม้แต่จะพิมพ์มัน เนื่องจากการพิมพ์ยังต้องการ 'รับ' ค่าก่อนจึงจะสามารถพิมพ์ได้!

struct Person{
    var name: String{
        get{
            print(name) // DON'T do this!!!!
            return "as"
        }
        set{
        }
    }
}

let p1 = Person()

ตามที่จะให้คำเตือนต่อไปนี้:

ความพยายามในการเข้าถึง 'ชื่อ' จากภายในเป็นผู้ทะเยอทะยาน

ข้อผิดพลาดดูเหมือนคลุมเครือเช่นนี้:

ป้อนคำอธิบายรูปภาพที่นี่

didSetเป็นทางเลือกที่คุณอาจต้องการที่จะใช้ ด้วยdidSetคุณจะได้รับการเก็บค่าที่ตั้งไว้ก่อนและเพิ่งได้รับการตั้งค่า ดูเพิ่มเติมที่คำตอบนี้


8

อัปเดต: Swift 4

ใน setter ชั้นล่างและทะเยอทะยานถูกนำไปใช้กับตัวแปร sideLength

class Triangle: {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) { //initializer method
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get { // getter
            return 3.0 * sideLength
        }
        set(newValue) { //setter
            sideLength = newValue / 4.0
        }
   }

การสร้างวัตถุ

var triangle = Triangle(sideLength: 3.9, name: "a triangle")

ทะเยอทะยาน

print(triangle.perimeter) // invoking getter

Setter

triangle.perimeter = 9.9 // invoking setter

6

ลองใช้สิ่งนี้:

var x:Int!

var xTimesTwo:Int {
    get {
        return x * 2
    }
    set {
        x = newValue / 2
    }
}

นี่เป็นคำตอบโดยทั่วไปของ Jack Wu แต่ความแตกต่างคือในคำตอบของ Jack Wu ตัวแปร x ของเขาคือvar x: Intของฉันตัวแปร x ของฉันเป็นแบบนี้: var x: Int!ดังนั้นสิ่งที่ฉันทำก็คือทำให้มันเป็นตัวเลือก


1

อัปเดตสำหรับ Swift 5.1

ในฐานะของสวิฟท์ 5.1 ตอนนี้คุณจะได้รับตัวแปรของคุณโดยไม่ต้องใช้รับคำหลัก ตัวอย่างเช่น:

var helloWorld: String {
"Hello World"
}

ถ้าฉันพยายามที่จะเปลี่ยนhelloWorld = "macWorld" , ไม่สามารถกำหนดให้กับค่า: 'helloWorld' เป็นข้อผิดพลาดของคุณสมบัติ get-only แสดงขึ้น
McDonal_11

เป็นไปได้หรือไม่ที่จะกำหนดค่าใหม่ หรือไม่ ?
McDonal_11

ฉันไม่คิดว่ามันเป็นไปได้
atalayasa

ดังนั้นสวัสดี, HelloWorld: String {"Hello World"} และให้ helloWorld: String = "Hello World" เหมือนกันหรือไม่
McDonal_11

ใช่ฉันก็คิดว่าอย่างนั้น.
atalayasa

0

Setters และ getters ใน Swift นำไปใช้กับคุณสมบัติ / ตัวแปรที่คำนวณ คุณสมบัติ / ตัวแปรเหล่านี้ไม่ได้ถูกจัดเก็บจริงในหน่วยความจำ แต่จะคำนวณตามค่าของคุณสมบัติ / ตัวแปรที่เก็บไว้

ดูเอกสารสวิฟท์แอปเปิ้ลในเรื่อง: สวิฟท์ประกาศตัวแปร


0

นี่คือคำตอบทางทฤษฎี ที่สามารถพบได้ที่นี่

คุณสมบัติ {get set} ไม่สามารถเป็นคุณสมบัติที่เก็บไว้คงที่ได้ ควรเป็นคุณสมบัติที่คำนวณได้และควรนำมาใช้และตั้งค่า

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