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
.
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
.
Câu trả lời:
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.init
thay vì super.init
bạ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!
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
Đâ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.
Đối với tôi, convenience initializers
rấ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.
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
}
}
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 initializers
hữ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)
}
}
// 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")
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ể.
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
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)
}
}
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!
}
}
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()