Bài 21: Initialization trong swift – Lập trình Swift cơ bản

Trang chủ » Training » Bài 21: Initialization trong swift – Lập trình Swift cơ bản
01/03/2022 Training 11 viewed
Đối với một lập trình viên, trong một project bạn luôn phải liên tục khởi tạo các instance của các struct, class hoặc enum. Vì vậy, việc khởi tạo ngôn ngữ lập trình và hiểu rõ cũng như áp dụng thành thạo nó là một điều cực kỳ quan trọng. Điều này sẽ giúp bạn tăng performance cũng như chất lượng của source code.
Ở bài viết dưới đây, hãy cùng chúng tôi tìm hiểu kỹ hơn về quá trình khởi tạo trong ngôn ngữ lập trình Swift nhé.

Khởi tạo trong Structure

Khởi tạo mặc định

Khởi tạo mặc định cũng khá đơn giản và không gây quá nhiều khó khăn cho các lập trình viên.
let phoneX = Phone()

Syntax

init() {

   // New Instance sẽ được khởi tạo ở đây

}

Example

struct Rectangle {

    var length: Double

    var breadth: Double

    init() {

        length = 12.0

        breadth = 5.0

    }

}

var rec = Rectangle()

print("area of rectangle is \(rec.length * rec.breadth)")
Ở đoạn code bên trên 1 instance là rec được khởi tạo với các thuộc tính là chiều rộng và chiều dài lần lượt là length = 12, và breadth = 5, sau khi chạy đoạn code trên sẽ thu được kết quả:
area of rectangle is 60.0
Khi  xoá phần init() trong đoạn code trên ta sẽ được :
struct Rectangle {

    var length: Double

    var breadth: Double

}

var rec = Rectangle()

print("area of rectangle is \(rec.length * rec.breadth)")
Lỗi được thông báo khi chạy đoạn code là
error: missing argument for parameter ‘length’ in call
let rec = Rectangle()
Ở đây thì trình biên dịch đang bắt chúng ta phải khởi tạo với đối số của length nên đã thông báo lỗi.
Lý do là bạn cố khởi tạo cho struct Rectangle khi các stored property chưa được gán giá trị mặc định
Và ta có thể sửa đoạn code như sau:
struct Rectangle {

    var length: Double = 12.0

    var breadth: Double = 5.0

}

var rec = Rectangle()

print("area of rectangle is \(rec.length * rec.breadth)")
Chỉ cần gán giá trị mặc định cho tất cả các stored property bằng việc tạo ra instance rec với các stored property được gán giá trị ban đầu lần lượt là 12.0 và 5.0 và vấn đề sẽ được giải quyết ngay lập tức.
Như trên thì chúng ta đã được tiếp cận với việc khởi tạo 1 struct với kiểu mặc định, nhìn lại 2 cách viết ở trên nhé:
// 1

struct Rectangle {

    var length: Double

    var breadth: Double

    init() {

        length = 12.0

        breadth = 5.0

    }

}

// 2

struct Rectangle {

    var length: Double = 12.0

    var breadth: Double = 5.0

}
Với 2 cách viết nói trên sẽ đều đem đến cho chúng ta cùng một kết quả là  1 instance có các giá trị khởi tạo mặc định là 12.0 và 5.0. Bạn có thể áp dụng 1 trong 2 cách nhưng cách thứ 2 vẫn ưu việt hơn và dễ hiểu hơn.

Khởi tạo với các Parameter

struct Rectangle {
    var length: Double
    var breadth: Double

    init(length: Double, breadth: Double) {
        self.length = length
        self.breadth = breadth
    }
}

let rect = Rectangle(length: 6, breadth: 12)
print(" length is: \(rect.length)\n breadth is: \(rect.breadth) ")
Chúng ta sẽ nhận được kết quả như sau:
length is: 6.0
breadth is: 12.0
Ví dụ, nếu như phần phần init trong đoạn code trên bị xóa, điều gì sẽ xảy ra?
struct Rectangle {
    var length: Double
    var breadth: Double
}

let rect = Rectangle(length: 6, breadth: 12)
print(" length is: \(rect.length)\n breadth is: \(rect.breadth) ")
Và, đoạn code vẫn chạy một cách bình thường mà không bị báo lỗi. Điều này được lý giải rằng Swift đã tạo sẵn cho bạn 1 hàm khởi tạo rồi, tuy nhiên nếu bạn muốn đổi thứ tự các stored property thì khi gọi hàm khởi tạo mặc định thứ tự của Parameter cũng phải đổi tương ứng
struct Rectangle {

    var breadth: Double

    var length: Double

}
Khi gọi phải đảo lại parameter:
let rect = Rectangle(breadth: 6, length: 12)
  • Khi thêm init với name các parameter khác với mặc định trong struct:
struct Rectangle {

    var length: Double

    var breadth: Double

    init(length: Double) {

        self.length = length

        self.breadth = 5.0

    }

}

let rect = Rectangle(length: 6, breadth: 12) // Lỗi

print(" length is: \(rect.length)\n breadth is: \(rect.breadth) ")
Lý do đoạn code trên bị lỗi là do chúng ta đã viết 1 hàm init với parameter là length ở trong struct dẫn tới  swift không tự động viết hàm khởi tạo cho bạn nữa.
Vì vậy để vừa có hàm khởi tạo tự viết và vừa có hàm khởi tạo tự động mà swift tạo tự động, chúng ta cần chuyển hàm init của chúng ta qua extension:
struct Rectangle {

   var length: Double

   var breadth: Double

}
extension Rectangle {

   init(length: Double) {

       self.length = length

       self.breadth = length + 5.0

   }

}

Khởi tạo với các Parameter mặc định

struct Rectangle {

    var length: Double

    var breadth: Double

    init(length: Double = 12.0, breadth: Double = 5.0) {

        self.length = length

        self.breadth = breadth

    }

}

let rectA = Rectangle()

print(" rectA:\n length is: \(rectA.length)\n breadth is: \(rectA.breadth) ")

let rectB = Rectangle(length: 50)

print(" rectB:\n length is: \(rectB.length)\n breadth is: \(rectB.breadth) ")

let rectC = Rectangle(length: 20, breadth: 6)

print(" rectB:\n length is: \(rectC.length)\n breadth is: \(rectC.breadth) ")
Ta có kết quả là
rectA:
length is: 12.0
breadth is: 5.0
rectB:
length is: 50.0
breadth is: 5.0
rectB:
length is: 20.0
breadth is: 6.0
Việc khởi tạo với các parameter giúp chúng ta gọi hàm đa dạng hơn

Initializer Delegation

Initializer Delegation thực chất chính là việc gọi hàm khởi tạo từ các hàm khởi tạo khác.
Bạn có thể xem ví dụ dưới đây để hiểu rõ hơn nhé
struct Rectangle {

    var length: Double

    var breadth: Double

    // 1

    init(length: Double, breadth: Double) {

        self.length = length

        self.breadth = breadth

    }

    // 2

    init(length: Double) {

        let breadth = length + 2.0

        self.init(length: length, breadth: breadth)

    }

}

let rect = Rectangle(length: 50)

print(" rect:\n length is: \(rect.length)\n breadth is: \(rect.breadth) ")
Và chúng ta nhận được kết quả như sau:
rect:
length is: 50.0
breadth is: 52.0
Với đoạn code trên chúng ta có tới 2 hàm init trong struct, ở hàm init thứ 2 thì chúng ta thực hiện tính toán bề rộng trước khi gọi đến hàm init 1. Và đây được gọi là Initializer Delegation
Nếu như bạn muốn gọi self trước khi call hàm init số 1. Bạn cần thực hiện như sau
struct Rectangle {

    var length: Double

    var breadth: Double

    // 1

    init(length: Double, breadth: Double) {

        self.length = length

        self.breadth = breadth

    }

    // 2

    init(length: Double) {

        self.length = 5.0 // Báo lỗi: 'self' used before 'self.init' call or assignment to 'self'

        let breadth = length + 2.0

        self.init(length: length, breadth: breadth)

    }

}

let rect = Rectangle(length: 50)

print(" rect:\n length is: \(rect.length)\n breadth is: \(rect.breadth) ")
Lúc này thì compiler báo lỗi: ‘self’ used before ‘self.init’ call or assignment to ‘self’
Lỗi này cho biết chúng ta không được dùng self trước khi gọi Initializer Delegation. Giả sử như đoạn code trên pass qua compiler thì lúc này length = 50.0 chứ không phải bằng 5.0 như mong muốn.
Chú ý trong phần I:
– Khi khởi tạo 1 instance, chúng ta cần gán giá trị ban đầu cho tất cả các non-optional stored property.
– Không gọi self trước khi call Initializer Delegation

Khởi tạo trong Class

Hai loại khởi tạo trong class

1.1. Designated Initializer

Đây có lẽ được coi là khởi tạo chính của class và để hàm được khởi tạo, bạn cần gán giá trị ban đầu cho tất cả các non-optional stored property.
class Rectangle {

    var length: Double

    var breadth: Double

    // Designated Initializer

    init(length: Double, breadth: Double) {

        self.length = length

        self.breadth = breadth

    }

}
Khi class của bạn có stored property là non-optional thì bắt buộc phải khởi tạo với Designated Initializer.

1.2. Convenience Initializers

Convenience Initializers là hàm hỗ trợ việc khởi tạo của class, trong hàm khởi tạo này chúng ta sẽ gọi đến các hàm khởi tạo khác
class Rectangle {

    var length: Double

    var breadth: Double

    // Designated Initializer

    init(length: Double, breadth: Double) {

        self.length = length

        self.breadth = breadth

    }

    // Convenience Initializer

    convenience init(length: Double, area: Double) {

        let breadth = area / length

        self.init(length: length, breadth: breadth)

    }

}

let rect = Rectangle(length: 5, area: 20)

print(" rect:\n length is: \(rect.length)\n breadth is: \(rect.breadth) ")
Chúng ta sẽ nhận được kết quả dưới đây:
rect:
length is: 5.0
breadth is: 4.0

Khởi tạo với subclass

class Rectangle {

    var length: Double

    var breadth: Double

    // Designated Initializer

    init(length: Double, breadth: Double) {

        self.length = length

        self.breadth = breadth

    }

}

class Square: Rectangle {

    var area: Double

}
Khi chạy đoạn code trên, màn hình sẽ báo lỗi
stored property ‘area’ without initial value prevents synthesized initializers
    var area: Double
Như đã lưu ý ở phần trên, khi khởi tạo 1 instance bất kỳ thì luôn phải đảm bảo các giá trị mặc định cho tất cả các stored property non-optional và ở đây do đã thêm biến area ở subclass là Square nên swift yêu cầu phải gán giá trị.
Dưới đây là 2 cách để pass qua compiler
// option 1

class Square: Rectangle {

    var color: String = "red"

}

// option 2

class Square: Rectangle {

    var color: String

    init(length: Double, breadth: Double, color: String) {

        self.color = color

        super.init(length: length, breadth: breadth)

    }

}
Nếu viết như sau, chúng ta sẽ nhận được
class Square: Rectangle {

    var color: String

    init(length: Double, breadth: Double, color: String) {

        self.color = color

        self.length = length

        self.breadth = breadth

    }

}
Nhận được error với nội dung:
error: ‘self’ used in property access ‘length’ before ‘super.init’ call
        self.length = length
Compiler thông báo rằng bạn đang sử dụng self để truy cập vào property length trước khi gọi super.init. (super ở đây chính là class cha Rectangle) và việc dùng super.init là bắt buộc để gán giá trị cho length and breadth.

Thừa kế hàm khởi tạo

class Square: Rectangle {

    var color: String

    init(length: Double, breadth: Double, color: String) {

        self.color = color

        super.init(length: length, breadth: breadth)

    }

    override init(length: Double, breadth: Double) {

        self.color = "red"

        super.init(length: length, breadth: breadth)

    }

}
Chỉ với một vài dòng code đơn giản, thao tác nhanh gọn là bạn hoàn toàn có thể sử dụng hàm khởi tạo của class cha một cách ngon lành rồi.
Chia sẻ:
Tags:
TOP HOME