Thuộc tính biến chỉ đọc và không tính toán trong Swift


126

Tôi đang cố gắng tìm ra thứ gì đó với ngôn ngữ Apple Swift mới. Giả sử tôi đã từng làm một cái gì đó như sau trong Objective-C. Tôi có readonlytài sản, và chúng không thể được thay đổi cá nhân. Tuy nhiên, bằng cách sử dụng một phương thức cụ thể, các thuộc tính được thay đổi một cách hợp lý.

Tôi lấy ví dụ sau đây, một chiếc đồng hồ rất đơn giản. Tôi sẽ viết điều này trong Mục tiêu-C.

@interface Clock : NSObject

@property (readonly) NSUInteger hours;
@property (readonly) NSUInteger minutes;
@property (readonly) NSUInteger seconds;

- (void)incrementSeconds;

@end

@implementation Clock

- (void)incrementSeconds {
     _seconds++;

     if (_seconds == 60) {
        _seconds = 0;
        _minutes++;

         if (_minutes == 60) {
            _minutes = 0;
            _hours++;
        }
    }
}

@end

Đối với một mục đích cụ thể, chúng ta không thể chạm trực tiếp vào giây, phút và giờ và chỉ được phép tăng từng giây bằng một phương pháp. Chỉ có phương pháp này có thể thay đổi các giá trị bằng cách sử dụng thủ thuật của các biến thể hiện.

Vì không có những thứ như vậy trong Swift, tôi đang cố gắng tìm một thứ tương đương. Nếu tôi làm điều này:

class Clock : NSObject {

    var hours:   UInt = 0
    var minutes: UInt = 0
    var seconds: UInt = 0

    func incrementSeconds() {

        self.seconds++

        if self.seconds == 60 {

            self.seconds = 0
            self.minutes++

            if self.minutes == 60 {

                self.minutes = 0
                self.hours++
            }
        }
    }
}

Điều đó sẽ làm việc, nhưng bất cứ ai cũng có thể thay đổi trực tiếp các thuộc tính.

Có lẽ tôi đã có một thiết kế tồi trong Objective-C và đó là lý do tại sao tương đương Swift mới tiềm năng không có ý nghĩa. Nếu không và nếu ai đó có câu trả lời, tôi sẽ rất biết ơn;)

Có lẽ các cơ chế kiểm soát truy cập trong tương lai được Apple hứa hẹn là câu trả lời?

Cảm ơn!


2
Kiểm soát truy cập là câu trả lời. Bạn sẽ tạo các thuộc tính được đọc chỉ trong khi sử dụng các thuộc tính riêng cho dữ liệu thực tế.
Leo Natan

Đảm bảo mở một báo cáo lỗi (tăng cường) để cho Apple biết điều này bị thiếu như thế nào.
Leo Natan

1
Đối với người dùng mới: Cuộn xuống phía dưới ...
Gustaf Rosenblad

2
Vui lòng đánh dấu câu trả lời của @ Ethan là chính xác.
phatmann

Câu trả lời:


356

Đơn giản chỉ cần tiền tố khai báo tài sản với private(set), như vậy:

public private(set) var hours:   UInt = 0
public private(set) var minutes: UInt = 0
public private(set) var seconds: UInt = 0

privategiữ nó cục bộ cho một tệp nguồn, trong khi internalgiữ nó cục bộ cho mô-đun / dự án.

private(set)tạo một read-onlytài sản, trong khi privateđặt cả hai setgetriêng tư.


28
"Riêng tư" để giữ cục bộ cho tệp nguồn, "nội bộ" để giữ cục bộ cho mô-đun / dự án. private (set) chỉ làm cho set riêng tư. Riêng tư làm cho cả thiết lập và nhận chức năng riêng tư
user965972

3
Bình luận đầu tiên nên được thêm vào câu trả lời này để làm cho nó đầy đủ hơn.
Rob Norback

Ngay bây giờ (Swift 3.0.1) Các cấp độ kiểm soát truy cập đã thay đổi: Chỉ có thể truy cập khai báo "fileprivate" bằng mã trong cùng một tệp nguồn như khai báo. Khai báo "riêng tư" chỉ có thể được truy cập bằng mã trong phạm vi kèm theo ngay lập tức của khai báo.
Jurasic

20

Có hai cách cơ bản để làm những gì bạn muốn. Cách đầu tiên là có một tài sản riêng và một tài sản được tính toán công khai trả lại tài sản đó:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Nhưng điều này có thể đạt được theo một cách khác, ngắn hơn, như đã nêu trong phần "Kiểm soát truy cập" của cuốn sách "Ngôn ngữ lập trình Swift" :

public class Clock {

    public private(set) var hours = 0

}

Là một lưu ý phụ, bạn PHẢI cung cấp một trình khởi tạo công khai khi khai báo một loại công khai. Ngay cả khi bạn cung cấp các giá trị mặc định cho tất cả các thuộc tính, init()phải được xác định rõ ràng công khai:

public class Clock {

    public private(set) var hours = 0

    public init() {
      hours = 0
    }
    // or simply `public init() {}`

}

Điều này cũng được giải thích trong cùng một phần của cuốn sách, khi nói về các trình khởi tạo mặc định.


5

Vì không có kiểm soát truy cập (có nghĩa là bạn không thể tạo hợp đồng truy cập khác nhau tùy thuộc vào người gọi là ai), đây là những gì tôi sẽ làm ngay bây giờ:

class Clock {
    struct Counter {
        var hours = 0;
        var minutes = 0;
        var seconds = 0;
        mutating func inc () {
            if ++seconds >= 60 {
                seconds = 0
                if ++minutes >= 60 {
                    minutes = 0
                    ++hours
                }
            }
        }
    }
    var counter = Counter()
    var hours : Int { return counter.hours }
    var minutes : Int { return counter.minutes }
    var seconds : Int { return counter.seconds }
    func incrementTime () { self.counter.inc() }
}

Điều này chỉ đơn thuần là thêm một mức độ gián tiếp, như đã từng, để truy cập trực tiếp vào quầy; một lớp khác có thể tạo Đồng hồ và sau đó truy cập trực tiếp vào bộ đếm của nó. Nhưng ý tưởng - tức là hợp đồng mà chúng tôi đang cố gắng thực hiện - là một lớp khác chỉ nên sử dụng các thuộc tính và phương thức cấp cao nhất của Đồng hồ. Chúng tôi không thể thực thi hợp đồng đó, nhưng thực tế cũng không thể thực thi được trong Objective-C.


Hình như điều này đã thay đổi kể từ đó, đúng không? developer.apple.com/swift/blog/?id=5
Dan Rosenstark

1
@Yar chắc chắn, toàn bộ câu hỏi / câu trả lời này đã được viết từ lâu trước khi quyền kiểm soát truy cập được thêm vào Swift. Và Swift vẫn đang phát triển, vì vậy câu trả lời sẽ tiếp tục bị lỗi thời.
matt

2

Trên thực tế, kiểm soát truy cập (chưa tồn tại trong Swift) không được thi hành như bạn nghĩ trong Mục tiêu C. Mọi người có thể sửa đổi trực tiếp các biến chỉ đọc của bạn, nếu họ thực sự muốn. Họ không làm điều đó với giao diện chung của lớp.

Bạn có thể làm một cái gì đó tương tự trong Swift (cắt và dán mã của bạn, cộng với một số sửa đổi, tôi đã không kiểm tra nó):

class Clock : NSObject {

    var _hours:   UInt = 0
    var _minutes: UInt = 0
    var _seconds: UInt = 0

    var hours: UInt {
    get {
      return _hours
    }
    }

    var minutes: UInt {
    get {
      return _minutes
    }
    }

    var seconds: UInt  {
    get {
      return _seconds
    }
    }

    func incrementSeconds() {

        self._seconds++

        if self._seconds == 60 {

            self._seconds = 0
            self._minutes++

            if self._minutes == 60 {

                self._minutes = 0
                self._hours++
            }
        }
    }
}

giống như những gì bạn có trong Mục tiêu C ngoại trừ các thuộc tính được lưu trữ thực tế có thể nhìn thấy trong giao diện công cộng.

Trong swift bạn cũng có thể làm điều gì đó thú vị hơn, điều mà bạn cũng có thể làm trong Objective C, nhưng có lẽ nó đẹp hơn trong swift (được chỉnh sửa trong trình duyệt, tôi đã không kiểm tra nó):

class Clock : NSObject {

    var hours: UInt = 0

    var minutes: UInt {
    didSet {
      hours += minutes / 60
      minutes %= 60
    }
    }

    var seconds: UInt  {
    didSet {
      minutes += seconds / 60
      seconds %= 60
    }
    }

    // you do not really need this any more
    func incrementSeconds() {
        seconds++
    }
}

"Mọi người có thể sửa đổi trực tiếp các biến chỉ đọc của bạn" - ý bạn chính xác là gì? Làm sao?
dùng1244109

Tôi đã đề cập đến Mục tiêu C. Trong Mục tiêu C, bạn có quyền truy cập động vào mọi thứ về một lớp thông qua API Thời gian chạy Mục tiêu C.
Tập tin tương tự

tôi bằng cách nào đó hiểu bạn ngụ ý rằng cho nhanh chóng. Có tò mò. Cảm ơn
dùng1244109
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.