Hiểu thêm về Swift với Tuples, Protocols, Delegates
Trong bài Tìm hiểu Swift lần trước chúng ta đã có 1 ứng dụng tính tiền Tip đơn giản trên iOS. Tuy nhiên mỗi khi chúng ta chọn số Tip để tính ra số tiền thì chung ta sẽ lại phải nhớ tổng số tiền (trước thuế) ở trong đầu. Điều này khá là bất tiện. Nó sẽ tốt hơn nếu method calcTipWithTipPct trả về 2 ...
Trong bài Tìm hiểu Swift lần trước chúng ta đã có 1 ứng dụng tính tiền Tip đơn giản trên iOS. Tuy nhiên mỗi khi chúng ta chọn số Tip để tính ra số tiền thì chung ta sẽ lại phải nhớ tổng số tiền (trước thuế) ở trong đầu. Điều này khá là bất tiện.
Nó sẽ tốt hơn nếu method calcTipWithTipPct trả về 2 giá trị là số tiền tip và số tiền trước thuế (số tiền dùng để tính tip).
Đối với Objective-C, nếu bạn muốn làm method trả về 2 giá trị, bạn sẽ phải tạo 1 object với 2 property hoặc trả về 1 dictionary chứa 2 giá trị. Đối với Swift, chúng ta có thêm cách khác nữa là: Tuples.
Chúng ta sẽ cùng thử một chút với Tuples để hiểu thêm về nó. Tạo 1 file Playground mới trong Xcode hoặc bạn cũng có thể dùng file có sẵn của bạn. Xoá tất cả nội dung và chúng ta sẽ cùng làm từ đầu
Unnamed Tuples
Cùng bắt đầu bằng việc tạo ra 1 Unamed Tuples bằng cách gõ dòng sau vào trong file playground:
let tipAndTotal = (4.00, 25.19)
Ở đây bạn đã nhóm 2 giá trị Double (tip và total) vào trong 1 giá trị Tuple. Chúng ta sử dụng inferred syntax vì compiler sẽ tự động xác định được kiểu biến dựa trên giá trị khởi tạo. Ngoài ra chúng ta cũng có thể khai báo theo kiểu explicitly như sau
let tipAndTotal:(Double, Double) = (4.00, 25.19)
Để truy xuất đến giá trị trong Tuple, chúng ta có 2 cách, 1 là theo index
tipAndTotal.0 tipAndTotal.1
2 là theo tên
let (theTipAmt, theTotal) = tipAndTotal theTipAmt theTotal
Named Tuples
Sử dụng Unamed Tuples khiến chúng ta mất thêm vài dòng code để có thể truy cập được mỗi item. Sử dụng Named Tuples sẽ làm cho việc đó dễ dàng hơn bằng cách khai báo tên ngay từ khi khởi tạo
let tipAndTotalNamed = (tipAmt:4.00, total:25.19) tipAndTotalNamed.tipAmt tipAndTotalNamed.total
Dễ dàng hơn rất nhiều phải không? Nếu bạn chú ý thì chúng ta đã sử dụng inferred syntax. Bạn cũng có thể dùng explicit syntax như sau:
let tipAndTotalNamed:(tipAmt:Double, total:Double) = (4.00, 25.19)
Khi sử dụng explicit syntax thì việc đặt tên cho biến phía bên phải là không cần thiết.
Returning Tuples
Bây giờ bạn cũng hiểu cơ bản về Tuples rồi, chúng ta sẽ cùng thử sử dụng nó trong ứng dụng Tip Calculator để trả về 2 giá trị.
Thêm đoạn code sau vào trong file playground:
let total = 21.19 let taxPct = 0.06 let subtotal = total / (taxPct + 1) func calcTipWithTipPct(tipPct:Double) -> (tipAmt:Double, total:Double) { let tipAmt = subtotal * tipPct let finalTotal = total + tipAmt return (tipAmt, finalTotal) } calcTipWithTipPct(0.20)
method calcTipWithTipPct này cũng giống như method calcTipWithTipPct chúng ta đã sử dụng trong bài trước. Ngoại trừ việc thay vì trả về 1 giá Double, chúng ta trả về (tipAmt:Double, total:Double)
Bây giờ bạn có thể xoá tất cả nội dung trong file playground vì chúng ta sẽ bắt đầu một phần hoàn toàn mới.
Full Prototype
Ở thời điểm này, bạn đã sẵn sàng cho việc sử dụng thứ mới học được vào trong class TipCalculatorModel. Nhưng trước khi thực sự thay đổi class đó trong project, hãy thử một chút những thay đổi này trong file playground nhé. Copy nội dung của class TipCalculatorModel trong project TipCalculator vào file playground và thử thay đổi method calcTipWithTipPct giống như bạn đã làm trước đó. Sau đó thay đổi returnPossibleTips trả về Dictionary Ints và Tuples thay vì Ints và Double như trước đó.
Hãy thử xem thế nào nhé, bạn cũng có thể tham khảo đoạn code dưới đây:
import Foundation class TipCalculatorModel { var total: Double var taxPct: Double var subtotal: Double { get { return total / (taxPct + 1) } } init(total: Double, taxPct: Double) { self.total = total self.taxPct = taxPct } func calcTipWithTipPct(tipPct:Double) -> (tipAmt:Double, total:Double) { let tipAmt = subtotal * tipPct let finalTotal = total + tipAmt return (tipAmt, finalTotal) } func returnPossibleTips() -> [Int: (tipAmt:Double, total:Double)] { let possibleTipsInferred = [0.15, 0.18, 0.20] let possibleTipsExplicit:[Double] = [0.15, 0.18, 0.20] var retval = Dictionary<Int, (tipAmt:Double, total:Double)>() for possibleTip in possibleTipsInferred { let intPct = Int(possibleTip*100) retval[intPct] = calcTipWithTipPct(possibleTip) } return retval } }
Save file này lại và khởi tạo một file playground mới. Chúng ta sẽ dùng tới file playground sau.
Protocols
Một Protocol là một list các method chỉ định một "contract" hoặc "interface". Thêm dòng sau vào file playground mới tạo để hiểu rõ hơn:
protocol Speaker { func Speak() }
Protocol này khai báo 1 method là Speak(). Bất kỳ 1 class nào "conform" protocol này đều phải khai báo method này. Add thêm đoạn code sau vào file để hiểu hơn:
class Vicki: Speaker { func Speak() { println("Hello, I am Vicki!") } } class Ray: Speaker { func Speak() { println("Yo, I am Ray!") } }
Để 1 class conform 1 protocol, bạn khai báo : sau tên class và tiếp theo là tên protocol. 2 class trên không kế thừa class nào nên bạn chỉ việc thêm trực tiếp tên của protocol vào sau :. Nếu class của bạn conform protocol nhưng không implement method của protocol thì sẽ có lỗi. Hãy thử xoá method Speak() ở class Ray bạn sẽ thấy lỗi xuất hiện.
Bây giờ hãy thử đối với class kế thừa:
class Animal { } class Dog : Animal, Speaker { func Speak() { println("Woof!") } }
Class Dog kế thừa class Animal và conform protocol Speaker. 1 class chỉ có thể kế thừa 1 class nhưng có thể conform nhiều protocol.
Optional Protocols
Bạn có thể đánh dấu 1 method của protocol trở thành optional bằng việc thêm keyword optional trước method và thêm tag @objc trước protocol. Hãy thử nó bằng cách thay thế protocol Speaker như sau:
@objc protocol Speaker { func Speak() optional func TellJoke() }
Nếu bạn thấy báo lỗi ở @objc hãy thêm dòng sau vào dòng đầu tiên của file playground
import Foundation
và thêm tag @objc trước tất cả các method func Speak() của mỗi class conform protocol Speaker
Bạn có thể thấy rằng không có lỗi cho cả class Vicki, Ray hay Dog tuy rằng không class nào khai báo method TellJoke()
Trong ví dụ này, Vicki và Ray có thể "joke" còn Dog thì tất nhiên là không rồi nên chúng ta sẽ chỉ implement method TellJoke() trên 2 class này:
class Vicki: Speaker { @objc func Speak() { println("Hello, I am Vicki!") } func TellJoke() { println("Q: What did Sushi A say to Sushi B?") } } class Ray: Speaker { @objc func Speak() { println("Yo, I am Ray!") } func TellJoke() { println("Q: Whats the object-oriented way to become wealthy?") } func WriteTutorial() { println("I'm on it!") } }
Sử dụng Protocols
Chúng ta đã tạo ra 1 protocol và vài class conform nó, bây giờ hãy thử sử dụng chúng. Thêm đoạn code sau vào file playgrund của bạn:
var speaker: Speaker speaker = Ray() speaker.Speak() // speaker.WriteTutorial() // error! (speaker as! Ray).WriteTutorial() speaker = Vicki() speaker.Speak()
Chú ý rằng thay vì khai báo speaker như là Ray thì chúng ta lại khai báo Speaker. Điều đó có nghĩa rằng chúng ta chỉ có thể gọi đến những method được implement trong Speaker ngay cả khi thực sự speaker là kiểu Ray. Để gọi đến những method khác của class Ray chúng ta sẽ phải tạm thời cast speaker sang Ray như bên trên.
Bây giờ thì thêm những dòng sau để thử với optional method:
speaker.TellJoke?() speaker = Dog() speaker.TellJoke?()
Delegates
1 delegate thực tế là 1 biến mà conform 1 protocol, thực hiện việc thông báo 1 sự kiến xảy ra hoặc thực hiện nhiều sub-tasks. Để hiểu thêm về nó. bạn hãy từ từ thử theo từng bước sau.
Thêm 1 class mới DateSimulator vào file playground của bạn:
class DateSimulator { let a:Speaker let b:Speaker init(a:Speaker, b:Speaker) { self.a = a self.b = b } func simulate() { println("Off to dinner...") a.Speak() b.Speak() println("Walking back home...") a.TellJoke?() b.TellJoke?() } } let sim = DateSimulator(a:Vicki(), b:Ray()) sim.simulate()
Tưởng tượng bạn có thể thông báo cho class khác khi cuộc hẹn bắt đầu hoặc kết thúc. Điều này có thể hữu ích, ví dụ nếu bạn muốn status thông báo xuất hiện hoặc biến mất khi những sự kiện này xảy ra.
Trước tiên chúng ta cần tạo ra protocol vói các sự kiện mà bạn muốn thông báo. Thêm đoạn code sau trước class DateSimulator
protocol DateSimulatorDelegate { func dateSimulatorDidStart(sim:DateSimulator, a:Speaker, b:Speaker) func dateSimulatorDidEnd(sim:DateSimulator, a: Speaker, b:Speaker) }
Sau đó tạo 1 class conform delegate này:
class LoggingDateSimulator:DateSimulatorDelegate { func dateSimulatorDidStart(sim:DateSimulator, a:Speaker, b:Speaker) { println("Date started!") } func dateSimulatorDidEnd(sim:DateSimulator, a: Speaker, b: Speaker) { println("Date ended!") } }
Tiếp theo chúng ta thêm 1 property mới cho class DateSimulator
var delegate:DateSimulatorDelegate?
Ngay phía trước dòng
sim.simulate()
thêm dòng sau:
sim.delegate = LoggingDateSimulator()
Cuối cùng, thay đổi 1 chút simulate() function để gọi delegate lúc bắt đầu và kết thúc của method:
func simulate() { delegate?.dateSimulatorDidStart(self, a: a, b: b) println("Off to dinner...") a.Speak() b.Speak() println("Walking back home...") a.TellJoke?() b.TellJoke?() delegate?.dateSimulatorDidEnd(self, a: a, b: b) }
Đến đây chắc các bạn cũng đã hiểu được cơ bản về Tuples, Protocol cũng như Delegate rồi đúng không? Hẹn gặp lại các bạn trong các bài tới.