Sự khác biệt giữa init tiện lợi và init trong các ví dụ nhanh chóng, rõ ràng tốt hơn là gì


82

Tôi đang gặp khó khăn để hiểu sự khác biệt giữa cả hai hoặc mục đích của convenience init.


Bạn đã đọc phần Khởi tạo của cuốn sách Ngôn ngữ lập trình Swift chưa? Chính xác thì sự nhầm lẫn của bạn là gì?
rmaddy

1
Đã có câu trả lời trong SO. Vui lòng tham khảo - stackoverflow.com/questions/30896231/…
Santosh,

Câu trả lời:


105

Tiêu chuẩn init:

Bộ khởi tạo được chỉ định là bộ khởi tạo chính cho một lớp. Một trình khởi tạo được chỉ định khởi tạo đầy đủ tất cả các thuộc tính được giới thiệu bởi lớp đó và gọi một trình khởi tạo siêu lớp thích hợp để tiếp tục quá trình khởi tạo lên chuỗi siêu lớp.

convenience init:

Các trình khởi tạo tiện lợi là thứ yếu, hỗ trợ các trình khởi tạo cho một lớp. Bạn có thể định nghĩa bộ khởi tạo tiện lợi để gọi bộ khởi tạo được chỉ định từ cùng lớp với bộ khởi tạo tiện lợi với một số tham số của bộ khởi tạo được chỉ định được đặt thành giá trị mặc định. Bạn cũng có thể xác định một trình khởi tạo tiện lợi để tạo một phiên bản của lớp đó cho một trường hợp sử dụng cụ thể hoặc kiểu giá trị đầu vào.

theo Tài liệu Swift

Tóm lại, điều này có nghĩa là bạn có thể sử dụng một trình khởi tạo tiện lợi để gọi một trình khởi tạo được chỉ định nhanh hơn và "thuận tiện" hơn. Vì vậy, trình khởi tạo tiện lợi yêu cầu sử dụng self.initthay vì super.initbạn có thể thấy khi ghi đè trình khởi tạo được chỉ định.

ví dụ về mã giả:

init(param1, param2, param3, ... , paramN) {
     // code
}

// can call this initializer and only enter one parameter,
// set the rest as defaults
convenience init(myParamN) {
     self.init(defaultParam1, defaultParam2, defaultParam3, ... , myParamN)
}

Tôi sử dụng chúng rất nhiều khi tạo các chế độ xem tùy chỉnh và các chế độ này có các trình khởi tạo dài với chủ yếu là mặc định. Các tài liệu làm công việc giải thích tốt hơn tôi có thể, hãy xem chúng!


73

Trình khởi tạo tiện lợi được sử dụng khi bạn có một số lớp có nhiều thuộc tính khiến việc luôn khởi tạo ứng dụng với tất cả các biến đó là khá "khó chịu", vì vậy những gì bạn làm với trình khởi tạo tiện lợi là bạn chỉ cần chuyển một số biến để khởi tạo và gán phần còn lại với một giá trị mặc định. Có một video rất hay trên trang web của Ray Wenderlich, không chắc nó có miễn phí hay không vì tôi có tài khoản trả phí. Đây là một ví dụ mà bạn có thể thấy rằng thay vì khởi tạo đối tượng của tôi với tất cả các biến đó, tôi chỉ đặt tiêu đề cho nó.

struct Scene {
  var minutes = 0
}

class Movie {
  var title: String
  var author: String
  var date: Int
  var scenes: [Scene]

  init(title: String, author: String, date: Int) {
    self.title = title
    self.author = author
    self.date = date
    scenes = [Scene]()
  }

  convenience init(title:String) {
    self.init(title:title, author: "Unknown", date:2016)
  }

  func addPage(page: Scene) {
    scenes.append(page)
  }
}


var myMovie = Movie(title: "my title") // Using convenicence initializer
var otherMovie = Movie(title: "My Title", author: "My Author", date: 12) // Using a long normal initializer

4
Ví dụ tốt. Điều này đã làm cho khái niệm rõ ràng. Cảm ơn bạn.
Arjun Kalidas

5
Chúng ta cũng có thể có init () với các giá trị mặc định cho tác giả và tham số ngày là `init (title: String, author: String =" Unknown ", date: Int = 2016) {self.title = title self.author = author self. date = date Scene = [Scene] ()} `vậy tại sao chúng ta cần khởi tạo tiện lợi?
shpad20

@ shpad20 câu hỏi tương tự ở đây. Bạn có tìm thấy lý do tại sao chúng ta phải sử dụng sự tiện lợi mặc dù chúng ta có thể tạo một trình khởi tạo khác và gửi tham số mặc định từ nó. Vui lòng cho tôi biết nếu bạn tìm thấy câu trả lời
100

sự tiện lợi là bổ sung cho self.init "chính" trong khi self.init với các tham số mặc định là thay thế cho self.init "chính". hackingwithswift.com/example-code/language/…
user1105951

@ shpad20 hãy xem ví dụ của tôi bên dưới để biết rõ nơi mà các trình khởi tạo tiện lợi đánh bại các giá trị mặc định. Hy vọng nó giúp.
Chris Graf

14

Đây là một ví dụ đơn giản, được lấy từ cổng Apple Developer .

Về cơ bản, bộ khởi tạo được chỉ định là init(name: String), nó đảm bảo rằng tất cả các thuộc tính được lưu trữ đều được khởi tạo.

Trình init()khởi tạo tiện lợi, không cần tham số, tự động đặt giá trị của thuộc tính nameđược lưu trữ [Unnamed]bằng cách sử dụng trình khởi tạo được chỉ định.

class Food {
    let name: String

    // MARK: - designated initializer
    init(name: String) {
        self.name = name
    }

    // MARK: - convenience initializer
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

// MARK: - Examples
let food = Food(name: "Cheese") // name will be "Cheese"
let food = Food()               // name will be "[Unnamed]"

Nó hữu ích, khi bạn xử lý các lớp lớn, với ít nhất một vài thuộc tính được lưu trữ. Tôi khuyên bạn nên đọc thêm một số về tùy chọn và kế thừa tại cổng Apple Developer.


6

Trong đó trình khởi tạo tiện lợi đánh bại việc thiết lập các giá trị tham số mặc định

Đối với tôi, convenience initializersrất hữu ích nếu có nhiều việc phải làm hơn là chỉ đơn giản đặt giá trị mặc định cho thuộc tính lớp.

Triển khai lớp với init () được chỉ định

Nếu không, tôi chỉ cần đặt giá trị mặc định trong initđịnh nghĩa, ví dụ:

class Animal {

    var race: String // enum might be better but I am using string for simplicity
    var name: String
    var legCount: Int

    init(race: String = "Dog", name: String, legCount: Int = 4) {
        self.race = race
        self.name = name
        self.legCount = legCount // will be 4 by default
    }
}

Tiện ích mở rộng lớp với init ()

Tuy nhiên, có thể có nhiều việc phải làm hơn là chỉ đặt một giá trị mặc định và đó là điều convenience initializershữu ích:

extension Animal {
    convenience init(race: String, name: String) {
        var legs: Int

        if race == "Dog" {
            legs = 4
        } else if race == "Spider" {
            legs = 8
        } else {
            fatalError("Race \(race) needs to be implemented!!")
        }

        // will initialize legCount automatically with correct number of legs if race is implemented
        self.init(race: race, name: name, legCount: legs)
    }
}

Các ví dụ sử dụng

// default init with all default values used
let myFirstDog = Animal(name: "Bello")

// convenience init for Spider as race
let mySpider = Animal(race: "Spider", name: "Itzy")

// default init with all parameters set by user
let myOctopus = Animal(race: "Octopus", name: "Octocat", legCount: 16)

// convenience init with Fatal error: Race AlienSpecies needs to be implemented!!
let myFault = Animal(race: "AlienSpecies", name: "HelloEarth")

1
tốt và đơn giản được giải thích ở đây
vivi

4

Một bộ khởi tạo tiện lợi có thể được định nghĩa trong phần mở rộng lớp . Nhưng một tiêu chuẩn - không thể.


1
Mặc dù không phải là một câu trả lời đầy đủ cho câu hỏi, đây là điều mà tôi vẫn chưa xem xét, cảm ơn!
Marc-André Weibezahn

3

những convenience initlàm cho nó bắt buộc để khởi tạo một lớp học với giá trị.

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây


2

Lưu ý: Đọc toàn bộ văn bản

Bộ khởi tạo được chỉ định là bộ khởi tạo chính cho một lớp. Một bộ khởi tạo được chỉ định khởi tạo đầy đủ tất cả các thuộc tính được giới thiệu bởi lớp đó và gọi một bộ khởi tạo siêu lớp thích hợp để tiếp tục quá trình khởi tạo lên đến chuỗi siêu lớp.

Các trình khởi tạo tiện lợi là thứ yếu, hỗ trợ các trình khởi tạo cho một lớp. Bạn có thể xác định trình khởi tạo tiện lợi để gọi trình khởi tạo được chỉ định từ cùng lớp với trình khởi tạo tiện lợi với một số tham số của trình khởi tạo được chỉ định được đặt thành mặc định.

Các bộ khởi tạo được chỉ định cho các lớp được viết theo cách giống như các bộ khởi tạo đơn giản cho các kiểu giá trị:

init(parameters) {
statements
}

Các bộ khởi tạo tiện lợi được viết theo cùng một kiểu, nhưng với công cụ sửa đổi tiện lợi được đặt trước từ khóa init, được phân tách bằng dấu cách:

convenience init(parameters) {
statements
}

Một ví dụ thực tế như sau:

class Food {
var name: String
init(name: String) {
    self.name = name
}
convenience init() {
    self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon”

Bộ khởi tạo init (tên: Chuỗi) từ lớp Food được cung cấp như một bộ khởi tạo được chỉ định vì nó đảm bảo rằng tất cả các thuộc tính được lưu trữ của một thực thể Food mới đều được khởi tạo hoàn toàn. Lớp Food không có lớp cha và do đó, bộ khởi tạo init (tên: String) không cần gọi super.init () để hoàn tất quá trình khởi tạo của nó.

“Lớp Food cũng cung cấp một trình khởi tạo tiện lợi, init (), không có đối số. Bộ khởi tạo init () cung cấp tên giữ chỗ mặc định cho một thực phẩm mới bằng cách ủy quyền cho init của lớp Thực phẩm (tên: Chuỗi) với giá trị tên là [Không tên]: ”

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]”

Lớp thứ hai trong hệ thống phân cấp là một lớp con của Thực phẩm được gọi là RecipeIngredient. Lớp RecipeIngredient lập mô hình một thành phần trong công thức nấu ăn. Nó giới thiệu một thuộc tính Int được gọi là số lượng (ngoài thuộc tính tên mà nó kế thừa từ Food) và xác định hai bộ khởi tạo để tạo các cá thể RecipeIngredient:

class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
    self.quantity = quantity
    super.init(name: name)
}
override convenience init(name: String) {
    self.init(name: name, quantity: 1)
}
}

Lớp RecipeIngredient có một bộ khởi tạo được chỉ định duy nhất, init (tên: Chuỗi, số lượng: Int), có thể được sử dụng để điền tất cả các thuộc tính của một cá thể RecipeIngredient mới. Bộ khởi tạo này bắt đầu bằng cách gán đối số số lượng đã truyền cho thuộc tính số lượng, đây là thuộc tính mới duy nhất được giới thiệu bởi RecipeIngredient. Sau khi làm như vậy, trình khởi tạo ủy quyền cho trình khởi tạo init (tên: Chuỗi) của lớp Thực phẩm.

trang: 536 Trích từ: Apple Inc. “Ngôn ngữ lập trình Swift (Swift 4).” iBooks. https://itunes.apple.com/pk/book/the-swift-programming-language-swift-4-0-3/id881256329?mt=11


2

Vì vậy, nó rất hữu ích khi bạn không cần chỉ định từng và mọi thuộc tính cho một lớp. Vì vậy, ví dụ: nếu tôi muốn tạo tất cả các cuộc phiêu lưu với giá trị HP bắt đầu là 100, tôi sẽ sử dụng init tiện lợi sau đây và chỉ cần thêm tên. Điều này sẽ cắt giảm mã rất nhiều.

class Adventure { 

// Instance Properties

    var name: String
    var hp: Int
    let maxHealth: Int = 100

    // Optionals

    var specialMove: String?

    init(name: String, hp: Int) {

        self.name = name
        self.hp = hp
    }

    convenience init(name: String){
        self.init(name: name, hp: 100)
    }
}

2

Sẽ có ý nghĩa nếu trường hợp sử dụng của bạn là gọi một trình khởi tạo trong một trình khởi tạo khác trong cùng một lớp .

Cố gắng làm điều này trong sân chơi

class Player {
    let name: String
    let level: Int

    init(name: String, level: Int) {
        self.name = name
        self.level = level
    }
    
    init(name: String) {
        self.init(name: name, level: 0) //<- Call the initializer above?

        //Sorry you can't do that. How about adding a convenience keyword?
    }
}

Player(name:"LoseALot")

Với từ khóa tiện lợi

class Player {
    let name: String
    let level: Int

    init(name: String, level: Int) {
        self.name = name
        self.level = level
    }
    
    //Add the convenience keyword
    convenience init(name: String) {
        self.init(name: name, level: 0) //Yes! I am now allowed to call my fellow initializer!
    }
}

1

Tất cả các câu trả lời đều có vẻ tốt nhưng, hãy hiểu nó bằng một ví dụ đơn giản

class X{                                     
   var temp1
   init(a: Int){
        self.temp1 = a
       }

Bây giờ, chúng ta biết một lớp có thể kế thừa một lớp khác,

class Z: X{                                     
   var temp2
   init(a: Int, b: Int){
        self.temp2 = b
        super.init(a: a)
       }

Bây giờ, trong trường hợp này khi tạo cá thể cho lớp Z, bạn sẽ phải cung cấp cả hai giá trị 'a' và 'b'.

let z = Z(a: 1, b: 2)

Nhưng, điều gì sẽ xảy ra nếu bạn chỉ muốn truyền giá trị của b và muốn phần còn lại lấy giá trị mặc định cho người khác, thì trong trường hợp đó, bạn cần khởi tạo các giá trị khác một giá trị mặc định. Nhưng chờ đợi như thế nào ?, vì điều đó U cần thiết lập nó tốt trước khi chỉ trong lớp.

//This is inside the class Z, so consider it inside class Z's declaration
convenience init(b: Int){
    self.init(a: 0, b: b)
}
convenience init(){
    self.init(a: 0, b: 0)
}

Và bây giờ, bạn có thể tạo các cá thể của lớp Z với việc cung cấp một số, tất cả hoặc không có giá trị nào cho các biến.

let z1 = Z(b: 2)
let z2 = Z()
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.