คุณสมบัติตัวแปรแบบอ่านอย่างเดียวและไม่คำนวณใน Swift


126

ฉันกำลังพยายามคิดบางอย่างด้วยภาษา Apple Swift ใหม่ สมมติว่าฉันเคยทำสิ่งต่อไปนี้ใน Objective-C ฉันมีreadonlyคุณสมบัติและไม่สามารถเปลี่ยนแปลงได้ทีละรายการ อย่างไรก็ตามเมื่อใช้วิธีการเฉพาะคุณสมบัติจะเปลี่ยนไปในทางตรรกะ

ฉันใช้ตัวอย่างต่อไปนี้นาฬิกาที่เรียบง่ายมาก ฉันจะเขียนสิ่งนี้ใน Objective-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

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

เนื่องจากไม่มีสิ่งเหล่านี้ใน Swift ฉันจึงพยายามหาสิ่งที่เทียบเท่า ถ้าฉันทำสิ่งนี้:

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

วิธีนี้ใช้ได้ผล แต่ใคร ๆ ก็เปลี่ยนคุณสมบัติได้โดยตรง

บางทีฉันอาจมีการออกแบบที่ไม่ดีใน Objective-C และนั่นเป็นเหตุผลว่าทำไม Swift ใหม่ที่มีศักยภาพเทียบเท่าจึงไม่สมเหตุสมผล ถ้าไม่มีและมีคนตอบฉันจะขอบคุณมาก;)

บางทีกลไกการควบคุมการเข้าถึงในอนาคตที่Apple สัญญาไว้คือคำตอบ?

ขอบคุณ!


2
การควบคุมการเข้าถึงคือคำตอบ คุณจะสร้างคุณสมบัติที่คำนวณแบบอ่านอย่างเดียวในขณะที่ใช้คุณสมบัติส่วนตัวสำหรับข้อมูลจริง
Leo Natan

อย่าลืมเปิดรายงานข้อบกพร่อง (การปรับปรุง) เพื่อแจ้งให้ Apple ทราบว่าสิ่งนี้หายไปอย่างร้ายแรงเพียงใด
Leo Natan

1
สำหรับผู้ใช้ใหม่: เลื่อนไปด้านล่าง ...
Gustaf Rosenblad

2
กรุณาทำเครื่องหมายคำตอบของ @ Ethan ว่าถูกต้อง
phatmann

คำตอบ:


356

เพียงนำหน้าการประกาศทรัพย์สินด้วยprivate(set)ดังนี้:

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

privateเก็บไว้ในเครื่องเป็นไฟล์ต้นฉบับในขณะที่internalเก็บไว้ในโมดูล / โครงการ

private(set)สร้างread-onlyคุณสมบัติในขณะที่privateตั้งค่าทั้งสองsetและgetเป็นส่วนตัว


28
"ส่วนตัว" เพื่อเก็บไว้ในเครื่องเป็นไฟล์ต้นฉบับ "ภายใน" เพื่อเก็บไว้ในเครื่องของโมดูล / โครงการ private (set) ทำให้ชุดเป็นแบบส่วนตัวเท่านั้น ส่วนตัวทำให้ทั้งตั้งค่าและรับฟังก์ชั่นเป็นส่วนตัว
user965972

3
ควรเพิ่มความคิดเห็นแรกในคำตอบนี้เพื่อให้สมบูรณ์ยิ่งขึ้น
Rob Norback

ตอนนี้ (Swift 3.0.1) ระดับการควบคุมการเข้าถึงเปลี่ยนไป: การประกาศ "fileprivate" สามารถเข้าถึงได้ด้วยรหัสในไฟล์ต้นฉบับเดียวกับการประกาศเท่านั้น " การประกาศ "ส่วนตัว" สามารถเข้าถึงได้ด้วยรหัสที่อยู่ในขอบเขตการปิดล้อมทันทีของการประกาศเท่านั้น
Jurasic

20

มีสองวิธีพื้นฐานในการทำสิ่งที่คุณต้องการ วิธีแรกคือการมีทรัพย์สินส่วนตัวและทรัพย์สินที่คำนวณสาธารณะซึ่งส่งคืนทรัพย์สินนั้น:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

แต่สามารถทำได้ด้วยวิธีอื่นที่สั้นกว่าดังที่ระบุไว้ในส่วน"การควบคุมการเข้าถึง"ของหนังสือ"The Swift Programming Language" :

public class Clock {

    public private(set) var hours = 0

}

โปรดทราบว่าคุณต้องระบุผู้เริ่มต้นสาธารณะเมื่อประกาศประเภทสาธารณะ แม้ว่าคุณจะระบุค่าเริ่มต้นให้กับคุณสมบัติทั้งหมด แต่init()ต้องกำหนดไว้อย่างชัดเจนสาธารณะ:

public class Clock {

    public private(set) var hours = 0

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

}

นอกจากนี้ยังมีการอธิบายไว้ในส่วนเดียวกันของหนังสือเมื่อพูดถึงค่าเริ่มต้นเริ่มต้น


5

เนื่องจากไม่มีการควบคุมการเข้าถึง (หมายความว่าคุณไม่สามารถทำสัญญาการเข้าถึงที่แตกต่างกันไปขึ้นอยู่กับว่าใครเป็นผู้โทร) นี่คือสิ่งที่ฉันจะทำในตอนนี้:

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

นี่เป็นเพียงการเพิ่มระดับของทิศทางในการเข้าถึงเคาน์เตอร์โดยตรงเท่านั้น ชั้นเรียนอื่นสามารถสร้างนาฬิกาจากนั้นเข้าถึงเคาน์เตอร์ได้โดยตรง แต่แนวคิด - นั่นคือสัญญาที่เรากำลังพยายามสร้างคือคลาสอื่นควรใช้คุณสมบัติและวิธีการระดับบนสุดของนาฬิกาเท่านั้น เราไม่สามารถบังคับใช้สัญญานั้นได้ แต่จริงๆแล้วมันเป็นไปไม่ได้เลยที่จะบังคับใช้ใน Objective-C ด้วย


ดูเหมือนว่าสิ่งนี้จะเปลี่ยนไปแล้วใช่มั้ย? developer.apple.com/swift/blog/?id=5
Dan Rosenstark

1
@ แน่นอนว่าคำถาม / คำตอบทั้งหมดนี้ถูกเขียนขึ้นนานก่อนที่จะเพิ่มการควบคุมการเข้าถึงลงใน Swift และ Swift ยังคงพัฒนาอยู่ดังนั้นคำตอบจึงยังคงล้าสมัย
แมต

2

จริงๆแล้วการควบคุมการเข้าถึง (ซึ่งยังไม่มีใน Swift) ไม่ได้บังคับใช้อย่างที่คุณคิดใน Objective C ผู้คนสามารถแก้ไขตัวแปรแบบอ่านอย่างเดียวของคุณได้โดยตรงหากพวกเขาต้องการจริงๆ พวกเขาไม่ได้ทำกับอินเทอร์เฟซสาธารณะของชั้นเรียน

คุณสามารถทำสิ่งที่คล้ายกันใน Swift (ตัดและวางโค้ดของคุณรวมถึงการแก้ไขบางอย่างฉันไม่ได้ทดสอบ):

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

ซึ่งเหมือนกับสิ่งที่คุณมีใน Objective C ยกเว้นว่าคุณสมบัติที่จัดเก็บจริงจะปรากฏในอินเทอร์เฟซสาธารณะ

ในความรวดเร็วคุณยังสามารถทำสิ่งที่น่าสนใจมากขึ้นซึ่งคุณสามารถทำได้ใน Objective C แต่มันอาจจะสวยกว่าอย่างรวดเร็ว (แก้ไขในเบราว์เซอร์ฉันไม่ได้ทดสอบ):

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

"ผู้คนสามารถแก้ไขตัวแปรแบบอ่านอย่างเดียวของคุณได้โดยตรง" หมายความว่าอย่างไร อย่างไร?
user1244109

ฉันอ้างถึง Objective C ใน Objective C คุณสามารถเข้าถึงทุกอย่างเกี่ยวกับคลาสผ่าน Objective C Runtime API ได้
ไฟล์อนาล็อก

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