Tài sản và setters


194

Với lớp đơn giản này, tôi nhận được cảnh báo trình biên dịch

Cố gắng sửa đổi / truy cập xtrong setter / getter của chính nó

và khi tôi sử dụng nó như thế này:

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

Tôi nhận được EXC_BAD_ACCESS. Làm thế nào tôi có thể làm điều này mà không cần sao lưu rõ ràng?

class Point {

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

1
Làm như vậy cũng sẽ tăng thêm tải cho trình biên dịch và sẽ tiêu tốn CPU mạnh mẽ. Tôi vừa mới làm điều đó: | . Đây là lỗi tôi đã tạo ra. (Tôi đang sử dụng sân chơi)
Honey

Hành vi này là khó hiểu, thay vì sử dụng setbạn muốn được sử dụng didSet. Các thuộc tính hoạt động khác nhau trong Swift so với Objective-C hoặc các ngôn ngữ khác khi bạn triển khai set. Xem câu trả lời dưới đây từ @jack và một ví dụ về didSettừ @cSquirrel
Paul Solt

Câu trả lời:


232

Setters và Getters áp dụng cho computed properties; các thuộc tính như vậy không có lưu trữ trong thể hiện - giá trị từ getter có nghĩa là được tính từ các thuộc tính thể hiện khác. Trong trường hợp của bạn, không có xđể được chỉ định.

Rõ ràng: "Làm thế nào tôi có thể làm điều này mà không cần sao lưu rõ ràng". Bạn không thể - bạn sẽ cần một cái gì đó để sao lưu thuộc tính được tính toán. Thử cái này:

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

Cụ thể, trong 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 trong Swift khá khác so với ObjC. Thuộc tính trở thành một thuộc tính được tính toán có nghĩa là nó không có biến sao lưu như _xtrong ObjC.

Trong đoạn code giải pháp dưới đây bạn sẽ nhìn thấy xTimesTwokhông không lưu trữ bất cứ điều gì, nhưng chỉ đơn giản là tính toán kết quả từ x.

Xem tài liệu chính thức về các thuộc tính được tính toán .

Chức năng bạn muốn cũng có thể là Trình quan sát tài sản .

Những gì bạn cần là:

var x: Int

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

Bạn có thể sửa đổi các thuộc tính khác trong setter / getters, đó là ý nghĩa của chúng.


1
Vâng, đó là những gì tôi đang đọc trong tài liệu. Tôi đã bỏ qua phần tài sản và dường như không có cách nào xung quanh điều này, phải không?
Atomix

Có, bạn sẽ cần một biến đối tượng khác để "quay lại" biến đầu tiên nếu bạn thực sự muốn làm điều này. Tuy nhiên, setters không có nghĩa cho mục đích này, có lẽ bạn nên suy nghĩ lại những gì bạn đang cố gắng thực hiện với điều này.
Jack

5
bạn có thể sử dụng didSetsẽ cho phép bạn thay đổi giá trị ngay sau khi được đặt. Không có gì để nhận mặc dù ...
ObjectiveCsam

1
LƯU Ý: Nếu bạn muốn hoàn nguyên giá trị vì đó là đầu vào không hợp lệ, bạn cần đặt xlại oldValuevào didSet. Sự thay đổi hành vi này rất khó hiểu đến từ các thuộc tính Objective-C, cảm ơn!
Paul Solt

105

Bạn có thể tùy chỉnh giá trị cài đặt bằng cách sử dụng trình quan sát thuộc tính. Để thực hiện việc này, hãy sử dụng 'did set' thay vì 'set'.

class Point {

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

Còn về getter ...

class Point {

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

Làm thế nào bạn có thể khởi tạo xvới một giá trị mặc định trong mẫu này?
i_am_jorf

3
Tôi thấy, nó sẽ là : var x: Int = 0 { didSet { ....
i_am_jorf

31

Để giải thích về câu trả lời của GoZoner:

Vấn đề thực sự của bạn ở đây là bạn đang gọi đệ quy getter của mình.

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

Giống như nhận xét mã được đề xuất ở trên, bạn vô cùng gọi hàm getter của thuộc tính x, nó sẽ tiếp tục thực thi cho đến khi bạn nhận được mã EXC_BAD_ACCESS (bạn có thể thấy công cụ quay vòng ở góc dưới bên phải của môi trường sân chơi Xcode của bạn).

Xem xét ví dụ từ tài liệu 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)
        }
    }
}

Lưu ý cách thuộc tính trung tâm không bao giờ sửa đổi hoặc trả về chính nó trong khai báo của biến.


Quy tắc của ngón tay cái là không bao giờ truy cập vào chính tài sản từ bên trong getter tức là get. Bởi vì điều đó sẽ kích hoạt cái khác getsẽ kích hoạt cái khác. . . Thậm chí đừng in nó. Bởi vì in ấn cũng đòi hỏi phải 'lấy' giá trị trước khi có thể in nó!
Mật ong

19

Để ghi đè settergettercho các biến nhanh, sử dụng mã đã cho bên dưới

var temX : Int? 
var x: Int?{

    set(newX){
       temX = newX
    }

    get{
        return temX
    }
}

Chúng ta cần giữ giá trị của biến trong một biến tạm thời, vì cố gắng truy cập vào cùng một biến có getter / setter đang bị ghi đè sẽ dẫn đến các vòng lặp vô hạn.

Chúng ta có thể gọi setter đơn giản như thế này

x = 10

Getter sẽ được gọi khi bắn bên dưới dòng mã đã cho

var newVar = x

8

Bạn đang định nghĩa đệ quy xvới x. Như thể ai đó hỏi bạn bao nhiêu tuổi? Và bạn trả lời "Tôi gấp đôi tuổi tôi". Điều đó là vô nghĩa.

Bạn phải nói tôi gấp đôi tuổi của John hoặc bất kỳ biến nào khác ngoài chính bạn.

các biến được tính toán luôn phụ thuộc vào một biến khác.


Quy tắc của ngón tay cái là không bao giờ truy cập vào chính tài sản từ bên trong getter tức là get. Bởi vì điều đó sẽ kích hoạt cái khác getsẽ kích hoạt cái khác. . . Thậm chí đừng in nó. Bởi vì in ấn cũng đòi hỏi phải 'lấy' giá trị trước khi có thể in nó!

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

let p1 = Person()

Vì điều đó sẽ đưa ra cảnh báo sau:

Cố gắng truy cập 'tên' từ bên trong getter của chính nó.

Lỗi trông mơ hồ như thế này:

nhập mô tả hình ảnh ở đây

Là một thay thế bạn có thể muốn sử dụng didSet. Với việc didSetbạn sẽ nắm giữ giá trị đã được đặt trước đó và vừa được đặt thành. Để biết thêm xem câu trả lời này .


8

Cập nhật: Swift 4

Trong lớp setter và getter bên dưới được áp dụng cho biến 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
        }
   }

Tạo đối tượng

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

Getter

print(triangle.perimeter) // invoking getter

Setter

triangle.perimeter = 9.9 // invoking setter

6

Hãy thử sử dụng cái này:

var x:Int!

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

Về cơ bản, đây là câu trả lời của Jack Wu, nhưng điểm khác biệt là trong câu trả lời của Jack Wu, biến x của anh ấy là var x: Int, trong biến của tôi, biến x của tôi giống như thế này: var x: Int!vì vậy tất cả những gì tôi đã làm là biến nó thành một loại tùy chọn.


1

Cập nhật cho Swift 5.1

Kể từ Swift 5.1, giờ bạn có thể lấy biến của mình mà không cần sử dụng từ khóa get . Ví dụ:

var helloWorld: String {
"Hello World"
}

Nếu tôi cố gắng thay đổi, helloWorld = "macWorld" , Không thể gán giá trị: 'helloWorld' là lỗi thuộc tính chỉ nhận được hiển thị.
McDonal_11

là bất kỳ có thể để gán giá trị mới? hay không ?
McDonal_11

Tôi không nghĩ rằng nó có thể.
atalayasa

Vì vậy, nghĩa đen, var helloWorld: String {"Hello World"} và, hãy để helloWorld: String = "Hello World" giống nhau?
McDonal_11

Vâng tôi cũng nghĩ thế.
atalayasa

0

Setters và getters trong Swift áp dụng cho các thuộc tính / biến được tính toán. Các thuộc tính / biến này không thực sự được lưu trữ trong bộ nhớ, mà được tính toán dựa trên giá trị của các thuộc tính / biến được lưu trữ.

Xem tài liệu Swift của Apple về chủ đề: Tuyên bố biến đổi Swift .


0

Đây là một câu trả lời lý thuyết. Điều đó có thể được tìm thấy ở đây

Thuộc tính {get set} không thể là thuộc tính được lưu trữ không đổi. Nó phải là một thuộc tính được tính toán và cả get và set nên được thực hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.