Có thể cho phép did set được gọi trong quá trình khởi tạo trong Swift không?


217

Câu hỏi

Tài liệu của Apple chỉ định rằng:

Các quan sát viên will set và did set không được gọi khi một thuộc tính được khởi tạo lần đầu tiên. Chúng chỉ được gọi khi giá trị của thuộc tính được đặt bên ngoài bối cảnh khởi tạo.

Có thể buộc những thứ này được gọi trong quá trình khởi tạo không?

Tại sao?

Hãy nói tôi có lớp học này

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

Tôi đã tạo phương thức doStuff, để làm cho các cuộc gọi xử lý ngắn gọn hơn, nhưng tôi chỉ muốn xử lý thuộc tính trong didSethàm. Có cách nào để buộc điều này để gọi trong quá trình khởi tạo không?

Cập nhật

Tôi quyết định chỉ gỡ bỏ intializer cho lớp của tôi và buộc bạn đặt thuộc tính sau khi khởi tạo. Điều này cho phép tôi biết didSetsẽ luôn luôn được gọi. Tôi đã không quyết định nếu điều này là tốt hơn về tổng thể, nhưng nó phù hợp với hoàn cảnh của tôi.


thực sự tốt nhất chỉ là "chấp nhận đây là cách thức hoạt động nhanh chóng". nếu bạn đặt một giá trị khởi tạo nội tuyến trên câu lệnh var, tất nhiên điều đó không gọi là "did Set". đó là lý do tại sao tình huống "init ()" cũng không được gọi là "did Set". Tất cả đều có lý.
Fattie

@Logan Câu hỏi tự trả lời câu hỏi của tôi;) cảm ơn!
AamirR

2
Nếu bạn muốn sử dụng trình khởi tạo tiện lợi, bạn có thể kết hợp nó với defer:convenience init(someProperty: AnyObject) { self.init() defer { self.someProperty = someProperty }
Darek Cieśla

Câu trả lời:


100

Tạo một Phương thức tập hợp riêng và sử dụng nó trong Phương thức init của bạn:

class SomeClass {
    var someProperty: AnyObject! {
        didSet {
            //do some Stuff
        }
    }

    init(someProperty: AnyObject) {
        setSomeProperty(someProperty)
    }

    func setSomeProperty(newValue:AnyObject) {
        self.someProperty = newValue
    }
}

Bằng cách khai báo somePropertydưới dạng: AnyObject!(một tùy chọn ngầm định chưa được mở), bạn cho phép tự khởi tạo hoàn toàn mà không somePropertyđược đặt. Khi bạn gọi setSomeProperty(someProperty)bạn đang gọi tương đương self.setSomeProperty(someProperty). Thông thường bạn sẽ không thể làm điều này vì bản thân chưa được khởi tạo hoàn toàn. Vì somePropertykhông yêu cầu khởi tạo và bạn đang gọi một phương thức phụ thuộc vào bản thân, Swift rời khỏi bối cảnh khởi tạo và did set sẽ chạy.


3
Tại sao điều này khác với self.someProperty = newValue trong init? Bạn đã có một trường hợp thử nghiệm làm việc?
mmmmmm

2
Không biết tại sao nó hoạt động - nhưng nó làm. Trực tiếp đặt giá trị mới trong init () - Phương thức không gọi did set () - nhưng sử dụng Phương thức đã cho setSomeProperty ().
Oliver

50
Tôi nghĩ rằng tôi biết những gì đang xảy ra ở đây. Bằng cách khai báo somePropertydưới dạng: AnyObject!(một tùy chọn ngầm định chưa được mở), bạn cho phép selfkhởi tạo hoàn toàn mà không somePropertyđược đặt. Khi bạn gọi setSomeProperty(someProperty)bạn đang gọi tương đương self.setSomeProperty(someProperty). Thông thường bạn sẽ không thể làm điều này vì selfchưa được khởi tạo hoàn toàn. Vì somePropertykhông yêu cầu khởi tạo và bạn đang gọi một phương thức phụ thuộc vào self, Swift rời khỏi bối cảnh khởi tạo và didSetsẽ chạy.
Logan

3
Dường như mọi thứ được đặt bên ngoài init () DOES được gọi là did set. Theo nghĩa đen, bất cứ thứ gì không được đặt init() { *HERE* }sẽ được gọi là did set. Tuyệt vời cho câu hỏi này nhưng sử dụng chức năng phụ để đặt một số giá trị dẫn đến did set được gọi. Không tuyệt vời nếu bạn muốn sử dụng lại chức năng setter đó.
WCByrne 14/03/2015

4
@Mark nghiêm túc, cố gắng áp dụng ý thức hay logic thông thường cho Swift là vô lý. Nhưng bạn có thể tin tưởng vào upvotes hoặc tự mình thử nó. Tôi vừa làm và nó hoạt động. Và vâng, nó không có ý nghĩa gì cả.
Dan Rosenstark

305

Nếu bạn sử dụng deferbên trong một initializer , để cập nhật bất kỳ thuộc tính tùy chọn hoặc tiếp tục cập nhật các thuộc tính phi tùy chọn mà bạn đã khởi tạo và sau khi bạn gọi bất kỳ super.init()phương pháp, sau đó bạn willSet, didSetvv sẽ được gọi. Tôi thấy điều này thuận tiện hơn việc thực hiện các phương pháp riêng biệt mà bạn phải theo dõi cuộc gọi ở đúng nơi.

Ví dụ:

public class MyNewType: NSObject {

    public var myRequiredField:Int

    public var myOptionalField:Float? {
        willSet {
            if let newValue = newValue {
                print("I'm going to change to \(newValue)")
            }
        }
        didSet {
            if let myOptionalField = self.myOptionalField {
                print("Now I'm \(myOptionalField)")
            }
        }
    }

    override public init() {
        self.myRequiredField = 1

        super.init()

        // Non-defered
        self.myOptionalField = 6.28

        // Defered
        defer {
            self.myOptionalField = 3.14
        }
    }
}

Sẽ mang lại:

I'm going to change to 3.14
Now I'm 3.14

6
Mặc dù vậy, tôi rất thích trò lừa bịp, tôi thích nó ...
Chris Hatton

4
Tuyệt quá. Bạn tìm thấy một trường hợp hữu ích khác của việc sử dụng defer. Cảm ơn.
Ryan

1
Công dụng tuyệt vời của defer! Ước gì tôi có thể cung cấp cho bạn nhiều hơn một upvote.
Christian Schnorr

3
rất thú vị .. Tôi chưa bao giờ thấy defer được sử dụng như thế này trước đây. Thật hùng hồn và hiệu quả.
aBikis

Nhưng bạn đã đặt myOptionalField hai lần, điều này không tốt từ góc độ hiệu suất. Điều gì xảy ra nếu tính toán nặng nề diễn ra?
Yakiv Kovalskyi

77

Như một biến thể của câu trả lời của Oliver, bạn có thể gói các dòng trong một bao đóng. Ví dụ:

class Classy {

    var foo: Int! { didSet { doStuff() } }

    init( foo: Int ) {
        // closure invokes didSet
        ({ self.foo = foo })()
    }

}

Chỉnh sửa: Câu trả lời của Brian Hampal đẹp hơn imho. Điều tốt đẹp về anh ấy là nó gợi ý về ý định.


2
Đó là thông minh và không gây ô nhiễm lớp với các phương pháp dư thừa.
điểm

Câu trả lời tuyệt vời, cảm ơn! Đáng lưu ý là trong Swift 2.2, bạn cần phải đóng dấu ngoặc trong ngoặc đơn luôn.
Tối đa

@Max Chúc mừng, mẫu bao gồm parens ngay bây giờ
original_username

2
Khéo léo trả lời! Một lưu ý: nếu lớp của bạn là lớp con của một thứ khác, bạn sẽ cần gọi super.init () trước ({self.foo = foo}) ()
kcstricks

1
@Hlung, tôi không có cảm giác nó sẽ bị phá vỡ trong tương lai hơn bất cứ điều gì khác, nhưng vẫn có một vấn đề là nếu không bình luận mã thì nó không rõ lắm. Ít nhất câu trả lời "hoãn" của Brian cung cấp cho người đọc một gợi ý rằng chúng tôi muốn thực hiện mã sau khi khởi tạo.
original_username

10

Tôi đã có cùng một vấn đề và điều này làm việc cho tôi

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        defer { self.someProperty = someProperty }
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

Bạn có nó ở đâu?
Vanya

3
Cách tiếp cận này không còn hiệu quả. Trình biên dịch đưa ra hai lỗi: - 'tự' được sử dụng trong lệnh gọi phương thức '$ defer' trước khi tất cả các thuộc tính được lưu trữ được khởi tạo - Trả về từ trình khởi tạo mà không khởi tạo tất cả các thuộc tính được lưu trữ
Edward B

Bạn đúng nhưng có một cách giải quyết. Bạn chỉ cần khai báo someProperty là ẩn chưa được cài đặt hoặc tùy chọn và cung cấp cho nó một giá trị mặc định với số không kết hợp lại để tránh thất bại. tldr; someProperty phải là một loại tùy chọn
Đánh dấu

2

Điều này hoạt động nếu bạn làm điều này trong một lớp con

class Base {

  var someProperty: AnyObject {
    didSet {
      doStuff()
    }
  }

  required init() {
    someProperty = "hello"
  }

  func doStuff() {
    print(someProperty)
  }
}

class SomeClass: Base {

  required init() {
    super.init()

    someProperty = "hello"
  }
}

let a = Base()
let b = SomeClass()

Trong aví dụ, didSetkhông được kích hoạt. Nhưng trong bví dụ, didSetđược kích hoạt, bởi vì nó nằm trong lớp con. Nó phải làm một cái gì đó với những gì initialization contextthực sự có nghĩa là, trong trường hợp này superclassđã quan tâm đến điều đó


1

Mặc dù đây không phải là một giải pháp, một cách khác để thực hiện nó là sử dụng một hàm tạo của lớp:

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            // do stuff
        }
    }

    class func createInstance(someProperty: AnyObject) -> SomeClass {
        let instance = SomeClass() 
        instance.someProperty = someProperty
        return instance
    }  
}

1
Giải pháp này sẽ yêu cầu bạn thay đổi tùy chọn somePropertyvì nó sẽ không mang lại giá trị trong quá trình khởi tạo.
Mick MacCallum

1

Trong trường hợp cụ thể mà bạn muốn gọi willSethoặc didSetbên trong initcho một thuộc tính có sẵn trong siêu lớp của bạn, bạn chỉ cần trực tiếp gán siêu tài sản của mình:

override init(frame: CGRect) {
    super.init(frame: frame)
    // this will call `willSet` and `didSet`
    someProperty = super.someProperty
}

Lưu ý rằng giải pháp Charlesism với việc đóng cửa sẽ luôn hoạt động trong trường hợp đó. Vì vậy, giải pháp của tôi chỉ là một thay thế.


-1

Bạn có thể giải quyết nó theo cách obj-с:

class SomeClass {
    private var _someProperty: AnyObject!
    var someProperty: AnyObject{
        get{
            return _someProperty
        }
        set{
            _someProperty = newValue
            doStuff()
        }
    }
    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

1
Xin chào @yshilov, tôi không nghĩ rằng điều này thực sự giải quyết được vấn đề mà tôi hy vọng vì về mặt kỹ thuật, khi bạn gọi self.somePropertybạn đang rời khỏi phạm vi khởi tạo. Tôi tin rằng điều tương tự có thể được thực hiện bằng cách làm var someProperty: AnyObject! = nil { didSet { ... } }. Tuy nhiên, một số người có thể đánh giá cao nó như một tác phẩm xung quanh, cảm ơn vì sự bổ sung
Logan

@Logan không, điều đó sẽ không hoạt động. did set sẽ bị bỏ qua hoàn toàn trong init ().
yshilov
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.