Mục đích của will set và did set trong Swift là gì?


265

Swift có cú pháp khai báo thuộc tính rất giống với C #: s:

var foo: Int {
    get { return getFoo() }
    set { setFoo(newValue) }
}

Tuy nhiên, nó cũng có willSetdidSethành động. Chúng được gọi trước và sau khi setter được gọi, tương ứng. Mục đích của họ là gì, xem xét rằng bạn có thể có cùng mã bên trong setter không?


11
Cá nhân tôi không thích nhiều câu trả lời ở đây. Họ đi quá nhiều vào cú pháp. Sự khác biệt là nhiều hơn về ngữ nghĩa và sẵn sàng mã. Tài sản được tính toán ( get& set) về cơ bản để có một tài sản được tính toán dựa trên một tài sản khác, ví dụ: chuyển đổi nhãn textthành một năm Int. didSet& willSetở đó để nói ... này giá trị này đã được đặt, bây giờ hãy thực hiện điều này, ví dụ như DataSource của chúng tôi đã được cập nhật ... vì vậy hãy tải lại bảngView để nó bao gồm các hàng mới. Để biết ví dụ khác, hãy xem câu trả lời của dfri về cách gọi đại biểu vàodidSet
Honey

Câu trả lời:


324

Vấn đề có vẻ là đôi khi, bạn cần một thuộc tính có lưu trữ tự động một số hành vi, ví dụ để thông báo cho các đối tượng khác rằng thuộc tính vừa thay đổi. Khi tất cả những gì bạn có là get/ set, bạn cần một trường khác để giữ giá trị. Với willSetdidSet, bạn có thể thực hiện hành động khi giá trị được sửa đổi mà không cần trường khác. Chẳng hạn, trong ví dụ đó:

class Foo {
    var myProperty: Int = 0 {
        didSet {
            print("The value of myProperty changed from \(oldValue) to \(myProperty)")
        }
    }
}

myPropertyin giá trị cũ và mới mỗi lần sửa đổi. Chỉ với getters và setters, tôi sẽ cần điều này thay vào đó:

class Foo {
    var myPropertyValue: Int = 0
    var myProperty: Int {
        get { return myPropertyValue }
        set {
            print("The value of myProperty changed from \(myPropertyValue) to \(newValue)")
            myPropertyValue = newValue
        }
    }
}

Vì vậy, willSetdidSetđại diện cho một nền kinh tế của một vài dòng, và ít tiếng ồn hơn trong danh sách trường.


248
Chú ý: willSetdidSetkhông được gọi khi bạn đặt thuộc tính từ trong một phương thức init như Apple lưu ý:willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
Klaas

4
Nhưng dường như chúng được gọi trên một thuộc tính mảng khi làm điều này: myArrayProperty.removeAtIndex(myIndex)... Không mong đợi.
Andreas

4
Bạn có thể gói nhiệm vụ trong một câu lệnh {} defer trong trình khởi tạo, điều này làm cho các phương thức will set và did set được gọi khi phạm vi của trình khởi tạo được thoát. Tôi không nhất thiết phải giới thiệu nó, chỉ nói rằng nó có thể. Một trong những hậu quả là nó chỉ hoạt động nếu bạn khai báo thuộc tính tùy chọn, vì nó không hoàn toàn được khởi tạo từ trình khởi tạo.
Marmoy

Hãy giải thích dòng dưới đây. Tôi không nhận được, phương thức này hoặc biến var propertyChangedListener: (Int, Int) -> Void = {println ("Giá trị của myProperty đã thay đổi từ ($ 0) thành ($ 1)")}
Vikash Rajput

Khởi tạo các thuộc tính trong cùng một dòng KHÔNG được hỗ trợ trong Swift 3. Bạn nên thay đổi câu trả lời để phù hợp với swift 3.
Ramazan Polat

149

Sự hiểu biết của tôi là thiết lập và nhận được cho các thuộc tính được tính toán (không có sự hỗ trợ từ các thuộc tính được lưu trữ )

nếu bạn đến từ một Objective-C trong tâm trí rằng các quy ước đặt tên đã thay đổi. Trong Swift, một biến iVar hoặc cá thể được đặt tên là thuộc tính được lưu trữ

Ví dụ 1 (chỉ đọc thuộc tính) - với cảnh báo:

var test : Int {
    get {
        return test
    }
}

Điều này sẽ dẫn đến một cảnh báo vì điều này dẫn đến một cuộc gọi hàm đệ quy (chính trình gọi getter). Cảnh báo trong trường hợp này là "Cố gắng sửa đổi 'kiểm tra' trong trình getter của chính nó".

Ví dụ 2. Đọc / ghi có điều kiện - có cảnh báo

var test : Int {
    get {
        return test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        //(prevents same value being set)
        if (aNewValue != test) {
            test = aNewValue
        }
    }
}

Vấn đề tương tự - bạn không thể làm điều này vì nó gọi đệ quy setter. Ngoài ra, lưu ý mã này sẽ không phàn nàn về việc không có trình khởi tạo vì không có thuộc tính được lưu trữ để khởi tạo .

Ví dụ 3. đọc / ghi thuộc tính được tính toán - với cửa hàng sao lưu

Dưới đây là mẫu cho phép cài đặt có điều kiện một thuộc tính được lưu trữ thực tế

//True model data
var _test : Int = 0

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

Lưu ý Dữ liệu thực tế được gọi là _test (mặc dù nó có thể là bất kỳ dữ liệu hoặc kết hợp dữ liệu nào) Lưu ý cũng cần cung cấp một giá trị ban đầu (thay vào đó bạn cần sử dụng một phương thức init) vì thực tế _test là một biến thể

Ví dụ 4. Sử dụng ý chí và đã thiết lập

//True model data
var _test : Int = 0 {

    //First this
    willSet {
        println("Old value is \(_test), new value is \(newValue)")
    }

    //value is set

    //Finaly this
    didSet {
        println("Old value is \(oldValue), new value is \(_test)")
    }
}

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

Ở đây chúng ta thấy will set và did set chặn một sự thay đổi trong một tài sản được lưu trữ thực tế. Điều này hữu ích để gửi thông báo, đồng bộ hóa, v.v ... (xem ví dụ bên dưới)

Ví dụ 5. Ví dụ cụ thể - Container ViewContoder

//Underlying instance variable (would ideally be private)
var _childVC : UIViewController? {
    willSet {
        //REMOVE OLD VC
        println("Property will set")
        if (_childVC != nil) {
            _childVC!.willMoveToParentViewController(nil)
            self.setOverrideTraitCollection(nil, forChildViewController: _childVC)
            _childVC!.view.removeFromSuperview()
            _childVC!.removeFromParentViewController()
        }
        if (newValue) {
            self.addChildViewController(newValue)
        }

    }

    //I can't see a way to 'stop' the value being set to the same controller - hence the computed property

    didSet {
        //ADD NEW VC
        println("Property did set")
        if (_childVC) {
//                var views  = NSDictionaryOfVariableBindings(self.view)    .. NOT YET SUPPORTED (NSDictionary bridging not yet available)

            //Add subviews + constraints
            _childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false)       //For now - until I add my own constraints
            self.view.addSubview(_childVC!.view)
            let views = ["view" : _childVC!.view] as NSMutableDictionary
            let layoutOpts = NSLayoutFormatOptions(0)
            let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|",  options: layoutOpts, metrics: NSDictionary(), views: views)
            let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
            self.view.addConstraints(lc1)
            self.view.addConstraints(lc2)

            //Forward messages to child
            _childVC!.didMoveToParentViewController(self)
        }
    }
}


//Computed property - this is the property that must be used to prevent setting the same value twice
//unless there is another way of doing this?
var childVC : UIViewController? {
    get {
        return _childVC
    }
    set(suggestedVC) {
        if (suggestedVC != _childVC) {
            _childVC = suggestedVC
        }
    }
}

Lưu ý việc sử dụng các thuộc tính được lưu trữ và tính toán. Tôi đã sử dụng một thuộc tính được tính toán để ngăn việc đặt cùng một giá trị hai lần (để tránh những điều xấu xảy ra!); Tôi đã sử dụng will Set và didset để chuyển tiếp thông báo tới viewControllers (xem tài liệu và thông tin về UIViewControll trên các thùng chứa viewContoder)

Tôi hy vọng điều này có ích, và làm ơn ai đó hét lên nếu tôi mắc lỗi ở bất cứ đâu tại đây!


3
Tại sao không thể sử dụng Tôi sử dụng did set cùng với get và set ..?
Ben Sinclair

//I can't see a way to 'stop' the value being set to the same controller - hence the computed property cảnh báo biến mất sau khi tôi sử dụng if let newViewController = _childVC { thay vì if (_childVC) {
evfemist

5
get và set được sử dụng để tạo một thuộc tính được tính toán. Đây là các phương thức hoàn toàn và không có lưu trữ sao lưu (biến thể hiện). will set và did set là để quan sát các thay đổi đối với các thuộc tính biến được lưu trữ. Dưới mui xe, những thứ này được hỗ trợ bởi bộ lưu trữ, nhưng trong Swift, tất cả được kết hợp thành một.
dùng3675131

Trong ví dụ 5 của bạn get, tôi nghĩ bạn cần thêm if _childVC == nil { _childVC = something }và sau đó return _childVC.
JW.ZG

18

Chúng được gọi là Quan sát tài sản :

Người quan sát tài sản quan sát và phản ứng với những thay đổi trong giá trị của tài sản. Người quan sát tài sản được gọi mỗi khi giá trị của tài sản được đặt, ngay cả khi giá trị mới giống với giá trị hiện tại của tài sản.

Trích từ: Apple Inc., Ngôn ngữ lập trình Swift. Sách điện tử. https://itun.es/ca/jEUH0.l

Tôi nghi ngờ việc cho phép mọi thứ chúng ta thường làm với KVO như ràng buộc dữ liệu với các thành phần UI hoặc kích hoạt các tác dụng phụ của việc thay đổi thuộc tính, kích hoạt quy trình đồng bộ hóa, xử lý nền, v.v.


16

GHI CHÚ

willSetdidSetngười quan sát không được gọi khi một thuộc tính được đặt trong trình khởi tạo trước khi diễn ra ủy quyền


16

Bạn cũng có thể sử dụng didSetđể đặt biến thành một giá trị khác. Điều này không khiến người quan sát được gọi lại như đã nêu trong hướng dẫn Thuộc tính . Ví dụ, nó hữu ích khi bạn muốn giới hạn giá trị như dưới đây:

let minValue = 1

var value = 1 {
    didSet {
        if value < minValue {
            value = minValue
        }
    }
}

value = -10 // value is minValue now.

10

Nhiều câu trả lời được viết tốt bao gồm câu hỏi tốt, nhưng tôi sẽ đề cập, trong một số chi tiết, một bổ sung mà tôi tin là đáng để bao quát.


Các willSetdidSetsở hữu nhà quan sát có thể được sử dụng để gọi các đại biểu, ví dụ, đối với thuộc tính của lớp mà chỉ bao giờ được cập nhật bởi tương tác người dùng, nhưng nơi bạn muốn tránh gọi các đại biểu tại khởi đối tượng.

Tôi sẽ trích dẫn bình luận được bình chọn của Klaas cho câu trả lời được chấp nhận:

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.

Điều này khá gọn gàng vì nó có nghĩa là didSettài sản là một lựa chọn tốt về điểm khởi chạy cho các hàm gọi lại & chức năng của đại biểu, cho các lớp tùy chỉnh của riêng bạn.

Ví dụ: xem xét một số đối tượng kiểm soát người dùng tùy chỉnh, với một số thuộc tính chính value(ví dụ: vị trí trong kiểm soát xếp hạng), được triển khai như một lớp con của UIView:

// CustomUserControl.swift
protocol CustomUserControlDelegate {
    func didChangeValue(value: Int)
    // func didChangeValue(newValue: Int, oldValue: Int)
    // func didChangeValue(customUserControl: CustomUserControl)
    // ... other more sophisticated delegate functions
}

class CustomUserControl: UIView {

    // Properties
    // ...
    private var value = 0 {
        didSet {
            // Possibly do something ...

            // Call delegate.
            delegate?.didChangeValue(value)
            // delegate?.didChangeValue(value, oldValue: oldValue)
            // delegate?.didChangeValue(self)
        }
    }

    var delegate: CustomUserControlDelegate?

    // Initialization
    required init?(...) { 
        // Initialise something ...

        // E.g. 'value = 1' would not call didSet at this point
    }

    // ... some methods/actions associated with your user control.
}

Sau đó chức năng ủy quyền có thể được sử dụng trong, nói rằng, một số điều khiển xem để quan sát những thay đổi quan trọng trong mô hình cho CustomViewController, giống như bạn muốn sử dụng chức năng đại diện vốn có của UITextFieldDelegatecho UITextFieldcác đối tượng (ví dụ textFieldDidEndEditing(...)).

Trong ví dụ đơn giản này, sử dụng một cuộc gọi lại đại biểu từ thuộc tính didSetcủa lớp valueđể báo cho bộ điều khiển xem rằng một trong các đầu ra của nó đã có cập nhật mô hình liên quan:

// ViewController.swift
Import UIKit
// ...

class ViewController: UIViewController, CustomUserControlDelegate {

    // Properties
    // ...
    @IBOutlet weak var customUserControl: CustomUserControl!

    override func viewDidLoad() {
        super.viewDidLoad()
        // ...

        // Custom user control, handle through delegate callbacks.
        customUserControl = self
    }

    // ...

    // CustomUserControlDelegate
    func didChangeValue(value: Int) {
        // do some stuff with 'value' ...
    }

    // func didChangeValue(newValue: Int, oldValue: Int) {
        // do some stuff with new as well as old 'value' ...
        // custom transitions? :)
    //}

    //func didChangeValue(customUserControl: CustomUserControl) {
    //    // Do more advanced stuff ...
    //}
}

Ở đây, thuộc valuetính đã được gói gọn, nhưng nói chung: trong các tình huống như thế này, hãy cẩn thận không cập nhật thuộc valuetính của customUserControlđối tượng trong phạm vi của hàm ủy nhiệm được liên kết (ở đây didChangeValue():) trong trình điều khiển xem hoặc bạn sẽ kết thúc bằng đệ quy vô hạn.


4

Các trình quan sát will set và did set cho các thuộc tính bất cứ khi nào thuộc tính được gán một giá trị mới. Điều này đúng ngay cả khi giá trị mới giống với giá trị hiện tại.

Và lưu ý rằng willSetcần một tên tham số để làm việc xung quanh, mặt khác, didSetkhông.

Trình quan sát did set được gọi sau khi giá trị của thuộc tính được cập nhật. Nó so sánh với giá trị cũ. Nếu tổng số bước đã tăng, một thông báo sẽ được in để cho biết có bao nhiêu bước mới đã được thực hiện. Trình quan sát did set không cung cấp tên tham số tùy chỉnh cho giá trị cũ và tên mặc định của oldValue được sử dụng thay thế.


2

Getter và setter đôi khi quá nặng để thực hiện chỉ để quan sát những thay đổi giá trị phù hợp. Thông thường, điều này cần thêm xử lý biến tạm thời và kiểm tra thêm, và bạn sẽ muốn tránh ngay cả những lao động nhỏ bé đó nếu bạn viết hàng trăm getters và setters. Những thứ này là dành cho tình hình.


1
Bạn đang nói rằng có một lợi thế về hiệu suất khi sử dụng willSetdidSetso với mã setter tương đương? Điều này có vẻ như một tuyên bố táo bạo.
zneak

1
@zneak Mình dùng từ sai. Tôi đang tuyên bố nỗ lực lập trình viên, không phải chi phí xử lý.
Eonil

1

Trong lớp (cơ sở) của riêng bạn willSetdidSetkhá tự do , thay vào đó, bạn có thể xác định một thuộc tính được tính toán (nghĩa là các phương thức get và set) truy cập a _propertyVariablevà thực hiện trước và sau khi truy tố .

Nếu, tuy nhiên , bạn ghi đè lên một lớp nơi có tài sản đã được xác định , sau đó các willSetdidSethữu ích và không dư thừa!


1

Một điều didSetthực sự tiện dụng là khi bạn sử dụng các cửa hàng để thêm cấu hình bổ sung.

@IBOutlet weak var loginOrSignupButton: UIButton! {
  didSet {
        let title = NSLocalizedString("signup_required_button")
        loginOrSignupButton.setTitle(title, for: .normal)
        loginOrSignupButton.setTitle(title, for: .highlighted)
  }

hoặc sử dụng will Set có ý nghĩa gì đó đối với các phương thức của cửa hàng này, phải không?
Elia

-5

Tôi không biết C #, nhưng với một chút phỏng đoán tôi nghĩ tôi hiểu những gì

foo : int {
    get { return getFoo(); }
    set { setFoo(newValue); }
}

làm. Nó trông rất giống với những gì bạn có trong Swift, nhưng nó không giống nhau: trong Swift bạn không có getFoosetFoo. Đó không phải là một chút khác biệt: nó có nghĩa là bạn không có bất kỳ lưu trữ cơ bản nào cho giá trị của bạn.

Swift đã lưu trữ và tính toán các thuộc tính.

Một thuộc tính được tính toán getvà có thể có set(nếu có thể ghi). Nhưng mã trong getter và setter, nếu chúng thực sự cần lưu trữ một số dữ liệu, phải thực hiện nó trong các thuộc tính khác . Không có lưu trữ sao lưu.

Một tài sản được lưu trữ, mặt khác, có lưu trữ sao lưu. Nhưng nó khônggetset. Thay vào đó, nó có willSetdidSetbạn có thể sử dụng để quan sát các thay đổi biến đổi và cuối cùng, kích hoạt các tác dụng phụ và / hoặc sửa đổi giá trị được lưu trữ. Bạn không có willSetdidSetcho các thuộc tính được tính toán và bạn không cần chúng vì đối với các thuộc tính được tính toán, bạn có thể sử dụng mã setđể kiểm soát các thay đổi.


Đây là ví dụ về Swift. getFoosetFoolà các trình giữ chỗ đơn giản cho bất cứ điều gì bạn muốn các getters và setters để làm. C # cũng không cần chúng. (Tôi đã bỏ lỡ một vài sự tinh tế cú pháp như tôi đã hỏi trước khi tôi có quyền truy cập vào trình biên dịch.)
zneak

1
Ồ được thôi. Nhưng điểm quan trọng là thuộc tính được tính KHÔNG có bộ lưu trữ bên dưới. Xem thêm câu trả lời khác của tôi: stackoverflow.com/a/24052566/574590
Tệp tương tự
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.