Viết hàm khởi tạo Init với Closure trong Swift

Lời nói đầu

Lúc mới bắt đầu học lập trình iOS, tôi đã học theo những tutorial trên Youtube. Để tạo một đối tượng UI, tôi gõ y nguyên theo những dòng code sau:

let makeBox: UIView = {
 let view = UIView()
 return view
}()

Ở vị trí của một người đang đi học, thì tôi chỉ copy và sử dụng chúng. Tuy nhiên, một ngày có người hỏi tôi rằng, "Tại sao lại phải thêm hai dấu ngoặc {} và tại sao lại có () ở cuối, đó có phải là một computed property không?. Tôi không thể trả lời. Do vậy, tôi viết bài này để giảng lại cho chính tôi ngày trước.

Để nắm được toàn bộ bài viết này, các bạn cần phải nắm và hiểu rõ những khái niệm sau:

  1. Closures
  2. Object Oriented Programming
Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster

Tạo UI Components

Trước khi tôi giải thích về phương pháp mới, hãy cùng nhau nhìn vào phương pháp phổ biến mà chúng ta thường dùng. Để tạo một button trong Swift, thường thì chúng ta sẽ làm như sau:

// Determine Size
let buttonSize = CGRect(x: 0, y: 0, width: 100, height: 100)
// Create Instance
let bobButton = UIButton(frame: buttonSize)
bobButton.backgroundColor = .black
bobButton.titleLabel?.text = "Bob"
bobButton.titleLabel?.textColor = .white

Giả dụ bạn cần phải tạo 3 button nữa, bạn sẽ phải copy đoạn code trên và thay đổi tên của chúng.

// New Button 
let bobbyButton = UIButton(frame: buttonSize)
bobbyButton.backgroundColor = .black
bobbyButton.titleLabel?.text = "Bob"
bobbyButton.titleLabel?.textColor = .white

Để tiện hơn thì bạn có thể làm như sau với tổ hợp phím Cmd + Ctrl + E

Hoặc bạn cũng có thể tạo một function để đỡ lặp đi lặp lại một công việc

func createButton(enterTitle: String) -> UIButton {

 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = enterTitle
 return button
}
createButton(enterTitle: "Yoyo") //  👍

Tuy nhiên, trong lập trình iOs, hiếm có trường hợp nào mà các button trông giống hệt nhau. Chính vì thế, function cần phải có thêm nhiều parameter khác như background color, title, border radius, shadow..:

func createButton(title: String, borderWidth: Double, backgrounColor, ...) -> Button

Việc thêm nhiều parameter vào function sẽ khiến nó trở nên khó đọc và khó hiểu hơn. Chúng ta đều muốn một function càng gọn gàng càng tốt.

Init đối tượng với closure

Trước khi đi vào phương pháp mới, chúng ta hãy trả lời câu hỏi ở đầu bài viết, dấu {} nghĩa là gì, và nó có phải là một Computer Property không? Không !, đó chỉ đơn giản là một closure block.

Đầu tiên chúng ta hãy khởi tạo một struct có tên là Human

struct Human {
 init() {
  print("Born 1996")
 }
}

Sau đó chúng ta tạo một đối tượng bằng closure

let createBob = { () -> Human in

 let human = Human()
 return human
}
let babyBob = createBob() // "Born 1996"

createBob là một closure với kiểu trả về là () -> Human. Sau đó chúng ta gọi clousre createBob() để tạo một instance của Human

Tuy nhiên, bạn phải tạo 2 constants: createBob và babyBob. Và nếu bạn muốn làm mọi thứ trong một câu lệnh thì sao?

let bobby = { () -> Human in
 let human = Human()
 return human
}()

Bây giờ closure block sẽ thực thi bằng cách thêm () vào và bobby bây giờ đã là một đối tượng Human.Tiếp theo hãy cùng áp dụng vào việc tạo một UI object:

let bobView = { () -> UIView in
 let view = UIView()
 view.backgroundColor = .black
 return view
}()

Tuyệt, chúng ta đã làm nó trở nên ngắn gọn hơn. Thực tế, chúng ta không cần phải chỉ ra rõ kiểu của closure block. Thay vào đó, chúng ta chỉ cần chỉ rõ kiểu của instance: 

let bobbyView: UIView = {
 let view = UIView()
 view.backgroundColor = .black
 return view
}()

Lợi ích của việc init bằng closure

Dễ dàng sao chép

Tôi không thích sử dụng Storyboard, tôi thích việc copy và paset những UI object. Thực tế, tôi có một thư viện code ở trong máy tính của mình. Giả dụ có một button như sau:

let myButton: UIButton = {
 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = "Button"
 button.titleLabel?.textColor = .white
 button.layer.cornerRadius = 1
 button.layer.masksToBounds = true
return button
}()

Tất cả nhưng gì tôi cần làm là copy những dòng code trên, và thay tên từ myButton thành newButton để sử dụng. Nếu tôi không dùng cách này, tôi sẽ phải thay đổi tên button 7,8 lần.

Nhìn code gọn gàng hơn

Nhóm các đối tượng lại với nhau sẽ khiến code trở nên gọn gàng hơn:

// Init with Closure 
let leftCornerButton: UIButton = {
 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = "Button"
 button.titleLabel?.textColor = .white
 button.layer.cornerRadius = 1
 button.layer.masksToBounds = true
return button
}()
let rightCornerButton: UIButton = {
 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = "Button"
 button.titleLabel?.textColor = .white
 button.layer.cornerRadius = 1
 button.layer.masksToBounds = true
return button
}()
vs

// Init With Fingers 
let leftCornerButton = UIButton(frame: buttonSize)
leftCornerButton.backgroundColor = .black
leftCornerButton.titleLabel?.text = "Button"
leftCornerButton.titleLabel?.textColor = .white
leftCornerButton.layer.cornerRadius = 1
leftCornerButton.layer.masksToBounds = true
let rightCornerButton = UIButton(frame: buttonSize)
rightCornerButton.backgroundColor = .black
rightCornerButton.titleLabel?.text = "Button"
rightCornerButton.titleLabel?.textColor = .white
rightCornerButton.layer.cornerRadius = 1
rightCornerButton.layer.masksToBounds = true

Mặc dù tạo một object với closure sẽ thêm một vài dòng code nữa, nhưng nhìn chung code sẽ dễ quản lý hơn khi chúng ta chỉ phải thêm các attributes vào button, thay vì rightCornerButotn hay leftCornerButton.

Apple đưa ra công cụ Swift Playgrounds để giúp những người mới bắt đầu học lập trình Apple đưa ra công cụ Swift Playgrounds để giúp những người mới bắt đầu học lập... Hồ Sỹ Hùng Blog Home Tuyển tập các công cụ lập trình hữu dụng cho lập trình viên iOS Tuyển tập các công cụ lập trình hữu dụng cho lập trình viên iOS Nguyễn Duy Khánh
Nguyễn Duy Khánh

iOS Developer, Former Student and Content Editor of TechMaster