Phương pháp hay nhất để triển khai trình khởi tạo sẵn có trong Swift


100

Với đoạn mã sau, tôi cố gắng xác định một lớp mô hình đơn giản và nó là bộ khởi tạo khả dụng, lấy từ điển (json-) làm tham số. Trình khởi tạo sẽ trả về nilnếu tên người dùng không được xác định trong json ban đầu.

1. Tại sao mã không biên dịch? Thông báo lỗi cho biết:

Tất cả các thuộc tính được lưu trữ của một cá thể lớp phải được khởi tạo trước khi trả về nil từ bộ khởi tạo.

Điều đó không có ý nghĩa. Tại sao tôi nên khởi tạo các thuộc tính đó khi tôi định quay lại nil?

2. Cách tiếp cận của tôi có phải là phương pháp phù hợp hay sẽ có những ý tưởng hoặc mô hình chung khác để đạt được mục tiêu của tôi?

class User: NSObject {

    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        if let value: String = dictionary["user_name"] as? String {
            userName = value
        }
        else {
           return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()
    }
}

Tôi đã gặp vấn đề tương tự, với vấn đề của tôi, tôi kết luận rằng mỗi giá trị từ điển nên được mong đợi và vì vậy tôi buộc mở các giá trị. Nếu tài sản không có ở đó, tôi sẽ có thể bắt lỗi. Ngoài ra, tôi đã thêm một canSetCalculablePropertiestham số boolean cho phép trình khởi tạo của tôi tính toán các thuộc tính có thể hoặc không thể được tạo nhanh chóng. Ví dụ: nếu một dateCreatedkhóa bị thiếu và tôi có thể đặt thuộc tính ngay lập tức vì canSetCalculablePropertiestham số là true, thì tôi chỉ cần đặt nó thành ngày hiện tại.
Adam Carter

Câu trả lời:


71

Cập nhật: Từ Nhật ký thay đổi Swift 2.2 (phát hành ngày 21 tháng 3 năm 2016):

Các trình khởi tạo lớp được chỉ định được khai báo là khả dụng hoặc đang ném giờ có thể trả về nil hoặc tạo ra một lỗi tương ứng trước khi đối tượng được khởi tạo hoàn toàn.


Đối với Swift 2.1 trở về trước:

Theo tài liệu của Apple (và lỗi trình biên dịch của bạn), một lớp phải khởi tạo tất cả các thuộc tính được lưu trữ của nó trước khi trả về niltừ trình khởi tạo khả dụng:

Tuy nhiên, đối với các lớp, bộ khởi tạo khả dụng chỉ có thể kích hoạt lỗi khởi tạo sau khi tất cả các thuộc tính được lưu trữ được giới thiệu bởi lớp đó đã được đặt thành giá trị ban đầu và bất kỳ ủy quyền trình khởi tạo nào đã diễn ra.

Lưu ý: Nó thực sự hoạt động tốt cho các cấu trúc và kiểu liệt kê, chỉ không phải các lớp.

Cách được đề xuất để xử lý các thuộc tính được lưu trữ không thể khởi tạo trước khi trình khởi tạo không thành công là khai báo chúng dưới dạng các tùy chọn không được bao bọc hoàn toàn.

Ví dụ từ tài liệu:

class Product {
    let name: String!
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

Trong ví dụ trên, thuộc tính name của lớp Sản phẩm được định nghĩa là có kiểu chuỗi tùy chọn không được bao bọc hoàn toàn (String!). Bởi vì nó thuộc loại tùy chọn, điều này có nghĩa là thuộc tính name có giá trị mặc định là nil trước khi nó được gán một giá trị cụ thể trong quá trình khởi tạo. Giá trị nil mặc định này đến lượt nó có nghĩa là tất cả các thuộc tính được giới thiệu bởi lớp Sản phẩm đều có giá trị ban đầu hợp lệ. Do đó, trình khởi tạo khả dụng cho Sản phẩm có thể gây ra lỗi khởi tạo khi bắt đầu trình khởi tạo nếu nó được chuyển qua một chuỗi trống, trước khi gán một giá trị cụ thể cho thuộc tính name trong trình khởi tạo.

Tuy nhiên, trong trường hợp của bạn, chỉ cần xác định userNamelà a String!không khắc phục được lỗi biên dịch vì bạn vẫn cần lo lắng về việc khởi tạo các thuộc tính trên lớp cơ sở của mình , NSObject. May mắn thay, với userNameđược định nghĩa là a String!, bạn thực sự có thể gọi super.init()trước khi bạn return nilbắt đầu NSObjectlớp cơ sở của bạn và sửa lỗi biên dịch.

class User: NSObject {

    let userName: String!
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        super.init()

        if let value = dictionary["user_name"] as? String {
            self.userName = value
        }
        else {
            return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            self.isSuperUser = value
        }

        self.someDetails = dictionary["some_details"] as? Array
    }
}

1
Cảm ơn bạn rất nhiều không chỉ đúng, nhưng cũng cũng giải thích
Kai Huppmann

9
trong swift1.2, Ví dụ từ tài liệu tạo ra lỗi "Tất cả các thuộc tính được lưu trữ của một cá thể lớp phải được khởi tạo trước khi trả về nil từ trình khởi tạo"
jeffrey

2
@jeffrey Đúng vậy, ví dụ từ tài liệu ( Productlớp) không thể kích hoạt lỗi khởi tạo trước khi chỉ định một giá trị cụ thể, mặc dù tài liệu nói rằng nó có thể. Các tài liệu không đồng bộ với phiên bản Swift mới nhất. Thay vào đó, bạn nên biến nó thành varhiện tại let. nguồn: Chris Lattner .
Arjan

1
Tài liệu có đoạn mã này hơi khác một chút: trước tiên bạn đặt thuộc tính, sau đó kiểm tra xem nó có xuất hiện hay không. Xem “Các công cụ khởi tạo khả dụng cho các lớp”, “Ngôn ngữ lập trình Swift”. `` Class Product {let name: String! ? init (tên: String) {self.name = tên nếu name.isEmpty {return nil}}} `` `
Misha Karpenko

Tôi cũng đã đọc điều này trong tài liệu của Apple nhưng tôi không hiểu tại sao điều này lại được yêu cầu. Không thành công có nghĩa là trả về nil, điều quan trọng là các thuộc tính đã được khởi tạo chưa?
Alper

132

Điều đó không có ý nghĩa. Tại sao tôi nên khởi tạo các thuộc tính đó khi tôi định trả về nil?

Theo Chris Lattner, đây là một lỗi. Đây là những gì anh ấy nói:

Đây là một giới hạn triển khai trong trình biên dịch swift 1.1, được ghi lại trong ghi chú phát hành. Trình biên dịch hiện không thể phá hủy các lớp được khởi tạo một phần trong mọi trường hợp, vì vậy nó không cho phép hình thành tình huống mà nó sẽ phải làm. Chúng tôi coi đây là một lỗi cần được sửa trong các bản phát hành trong tương lai, không phải là một tính năng.

Nguồn

BIÊN TẬP:

Vì vậy, swift hiện là mã nguồn mở và theo bảng thay đổi này, nó hiện đã được sửa trong ảnh chụp nhanh của swift 2.2

Các trình khởi tạo lớp được chỉ định được khai báo là khả dụng hoặc đang ném giờ có thể trả về nil hoặc tạo ra một lỗi tương ứng trước khi đối tượng được khởi tạo hoàn toàn.


2
Cảm ơn vì đã giải quyết quan điểm của tôi rằng ý tưởng khởi tạo các thuộc tính sẽ không cần thiết nữa có vẻ không hợp lý lắm. Và +1 để chia sẻ một nguồn, điều này chứng tỏ rằng Chris Lattner cảm thấy thích tôi;).
Kai Huppmann

22
FYI: "Thật vậy. Đây vẫn là thứ mà chúng tôi muốn cải thiện, nhưng đã không thực hiện cắt giảm cho Swift 1.2". - Chris Lattner 10 tháng 2 năm 2015
dreamlab

14
FYI: Trong Swift 2.0 beta 2, đây vẫn là một vấn đề và nó cũng là một vấn đề với trình khởi tạo.
aranasaurus

7

Tôi chấp nhận rằng câu trả lời của Mike S là khuyến nghị của Apple, nhưng tôi không nghĩ đó là phương pháp hay nhất. Toàn bộ điểm của một hệ thống kiểu mạnh là chuyển lỗi thời gian chạy sang thời gian biên dịch. "Giải pháp" này đánh bại mục đích đó. IMHO, tốt hơn là bạn nên tiếp tục và khởi tạo tên người dùng ""và sau đó kiểm tra nó sau super.init (). Nếu cho phép userName trống, thì hãy đặt cờ.

class User: NSObject {
    let userName: String = ""
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: [String: AnyObject]) {
        if let user_name = dictionary["user_name"] as? String {
            userName = user_name
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()

        if userName.isEmpty {
            return nil
        }
    }
}

Cảm ơn bạn, nhưng tôi không thấy ý tưởng về hệ thống loại mạnh bị lỗi bởi câu trả lời của Mike. Nhìn chung, bạn trình bày cùng một giải pháp với sự khác biệt là giá trị ban đầu được đặt thành "" thay vì nil. Hơn nữa, bạn mã mất đi để sử dụng "" như một tên người dùng (mà có vẻ khá học vấn, nhưng ít nhất nó khác khỏi bị không được thiết lập trong json / từ điển)
Kai Huppmann

2
Sau khi xem xét, tôi thấy rằng bạn đúng, nhưng chỉ vì userName là một hằng số. Nếu đó là một biến, thì câu trả lời được chấp nhận sẽ tệ hơn của tôi vì userName sau này có thể được đặt thành nil.
Daniel T.

Tôi thích câu trả lời này. @KaiHuppmann, nếu bạn muốn cho phép tên người dùng trống, bạn cũng có thể chỉ cần Bool needReturnNil đơn giản. Nếu giá trị không tồn tại trong từ điển, hãy đặt needReturnNil thành true và đặt userName thành bất kỳ. Sau super.init (), kiểm tra needReturnNil và trả về nil nếu cần.
Richard Venable

6

Một cách khác để vượt qua giới hạn là làm việc với một hàm lớp để thực hiện việc khởi tạo. Bạn thậm chí có thể muốn chuyển chức năng đó sang một tiện ích mở rộng:

class User: NSObject {

    let username: String
    let isSuperUser: Bool
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {

         self.userName = userName
         self.isSuperUser = isSuperUser
         self.someDetails = someDetails

         super.init()
    }
}

extension User {

    class func fromDictionary(dictionary: NSDictionary) -> User? {

        if let username: String = dictionary["user_name"] as? String {

            let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
            let someDetails = dictionary["some_details"] as? [String]

            return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
        }

        return nil
    }
}

Sử dụng nó sẽ trở thành:

if let user = User.fromDictionary(someDict) {

     // Party hard
}

1
Tôi thích điều này; Tôi thích các hàm tạo minh bạch về những gì họ muốn, và việc chuyển vào từ điển là rất không rõ ràng.
Ben Leggiero

3

Mặc dù Swift 2.2 đã được phát hành và bạn không còn phải khởi tạo hoàn toàn đối tượng trước khi không khởi tạo được nữa, nhưng bạn cần giữ ngựa cho đến khi https://bugs.swift.org/browse/SR-704 được sửa.


1

Tôi phát hiện ra điều này có thể được thực hiện trong Swift 1.2

Có một số điều kiện:

  • Các thuộc tính bắt buộc phải được khai báo dưới dạng các tùy chọn không được bao bọc hoàn toàn
  • Chỉ định một giá trị cho các thuộc tính cần thiết của bạn chính xác một lần. Giá trị này có thể là không.
  • Sau đó, gọi super.init () nếu lớp của bạn đang kế thừa từ một lớp khác.
  • Sau khi tất cả các thuộc tính bắt buộc của bạn đã được gán một giá trị, hãy kiểm tra xem giá trị của chúng có như mong đợi hay không. Nếu không, trả về nil.

Thí dụ:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

        if self.property == nil {
            return nil
        }
    }
}

0

Bộ khởi tạo khả dụng cho một loại giá trị (nghĩa là một cấu trúc hoặc kiểu liệt kê) có thể gây ra lỗi khởi tạo bất kỳ lúc nào trong quá trình triển khai bộ khởi tạo của nó

Tuy nhiên, đối với các lớp, bộ khởi tạo khả dụng chỉ có thể kích hoạt lỗi khởi tạo sau khi tất cả các thuộc tính được lưu trữ được giới thiệu bởi lớp đó đã được đặt thành giá trị ban đầu và bất kỳ ủy quyền trình khởi tạo nào đã diễn ra.

Trích từ: Apple Inc. “ Ngôn ngữ lập trình Swift. ”IBooks. https://itun.es/sg/jEUH0.l


0

Bạn có thể sử dụng init tiện lợi :

class User: NSObject {
    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
        self.userName = userName
        self.isSuperUser = isSuperUser
        self.someDetails = someDetails
    }     

    convenience init? (dict: NSDictionary) {            
       guard let userName = dictionary["user_name"] as? String else { return nil }
       guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
       guard let someDetails = dictionary["some_details"] as? [String] else { return nil }

       self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
    } 
}
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.