Tag: design pattern

  • [DesignPattern] Simple Factory Pattern

    [DesignPattern] Simple Factory Pattern

    Background

    Hầu hết anh em developer đều đã nghe qua về Design Pattern

    Tuy nhiên mình thấy còn nhiều người (gồm cả mình) đều không có kinh nghiêm áp dụng nó.

    Nên mình lập topic về design pattern để mọi người có thể chia sẻ kinh nghiệm và cách áp dụng nó trong các bài toán cụ thể.

    Vậy đầu tiên chúng ta phải hiểu design pattern là gì?

    • Theo mình hiểu design pattern đơn giản là các giải pháp mẫu tối ưu cho từng tình huống cụ thể trong lập trình OOP.

    Taị sao lại sử dụng design pattern?

    Một thực tế là mình nghe rất nhiều câu như refactor đi (dm code như shit, fucking coding …),đập hết đi xây lại.

    Tại sao chúng ta lại muốn như thế?

    Vì hầu hết các source code ban đầu đều rất khó maintain và mở rộng, nên khi có một yêu cầu mới hay một bug,

    anh em lại cặm cụi sửa code, sửa bug này lại sinh ra bug khác nên effort để giải quyết một vấn đề rất tốn kém.

    Do đó nếu mình không chỉ "code để chạy được" mà suy nghĩ, áp dụng các design pattern trươc khi viết ra

    sẽ rút ngắn phần lớn thời gian development và maintain.

    Sau đây mình xin trình bầy một số design pattern mà mình đã đọc qua.

    Đầu tiên mình xin tập trung vào một loại pattern mà chắc anh em ai cũng nghe qua, đó là Factory Pattern

    Factory Pattern có 3 loại: Simple factory, Factory method và Abstract Factory

    Bài lần này mình sẽ trình bày về Simple Factory Pattern

    Simple Factory Pattern

    Bài toán

    Nào chúng ta hãy làm quen với Simple Factory Pattern với một ví dụ như sau:

    Giả sử bạn đang viết chương trình đặt hàng và ship hàng cho một cửa hàng Pizza

    Quá trình đặt hàng một chiếc Pizza sẽ qua các công đoạn như chuẩn bị (prepare), nướng (bake) và đóng hộp (box)

    Xử lý để chạy được & vấn đề

    Chương trình có thể được viết như sau

    Class PizzaStore {
      public Pizza orderPizza() {
        Pizza pizza = new Pizza();
        pizza.prepare();
        pizza.bake();
        pizza.box();
        return pizza;
      }
      public void shipPizza() {
        Pizza pizza = new Pizza();
        pizza.ship();
       }
    }
    

    OK như vậy là có sample về chương trình orderPizza và shipPizza.

    Nhưng chủ cửa hàng lại muốn có nhiều món pizza cơ mà.

    Ví du: Pizza gà (ChickenPizza), Pizza phô mai(CheesePizza)

    Bạn nên làm thế nào ???

    Dễ mà tạo một lớp cha Pizza và create 2 lớp con là ChickenPizza và CheesePizza

    Sau đó add thêm parameter type cho orderPizza và shipPizza

    Class PizzaStore {
      public Pizza orderPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle ("cheese".equals(type) {
          pizza = new ChickenPizza();
        }
        pizza.prepare();
        pizza.bake();
        pizza.box();
        return pizza;
      }
      public void shipPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        }
        pizza.ship();
      }
    }
    

    OK các bạn thấy thế nào, chương trình chạy ngon không có lỗi gì luôn :D.

    Một tuần sau cửa hàng thấy cần thêm món Pizza hải sản (SeaFoodPizza) để tăng thêm khách hàng

    Bạn sẽ vào sửa method orderPizza???

    Class PizzaStore {
      public Pizza orderPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        } else if ("seafood".equals(type) {
          pizza = new SeaFoodPizza();
        }
        pizza.prepare();
        pizza.bake();
        pizza.box();
      }
      public void shipPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        }
        pizza.ship();
      }
    }
    

    Cơn ác mộng mới chỉ bắt đầu :)). Đấy là mình chỉ liệt kê 2 chức năng cơ bản, điều gì sẽ xảy ra nếu còn rất nhiều chức năng khác cần lấy thông tin pizza theo từng loại

    Ví du: Lấy thông tin giá, tên, … của từng loại Pizza

    Bạn sẽ phải hì hục sửa code tất cả cả các method đấy nếu cửa hàng tạo thêm một loại Pizza.

    Vâng bạn sẽ vẫn cố gắng sửa để nó có thể chạy được nhưng bạn sẽ tốn rất nhiều effort để làm việc này nếu có hàng chục method cần sửa.

    Bạn cũng lo lắng mình có thể quên xử lý ở một method nào đấy,…

    -> Giờ bạn đã sợ maintain chưa

    Áp dụng Simple Factory Pattern vào bài toán

    Quá nhiều điều để lo lắng chúng ta phải refactor thôi.

    Đầu tiên chắc các bạn cũng thấy luôn, chúng ta cần đóng gói việc khởi tạo object bên dưới

    if ("chicken".equals(type) {
      pizza = new ChickenPizza();
    } esle if ("cheese".equals(type) {
      pizza = new ChickenPizza();
    } else if ("seafood".equals(type) {
      pizza = new SeaFoodPizza();
    }
    

    Chuyển đoạn code trên sang một class factory

    Class SimplePizzaFactory {
      public Pizza createPizza(type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        } else if ("seafood".equals(type) {
          pizza = new SeaFoodPizza();
        }
        return pizza;
     }
    }
    

    Tiếp theo chúng ta sẽ sử dụng SimplePizzaFactory để tạo trong PizzaStore để tạo các object

    Class PizzaStore {
      SimplePizzaFactory mFactory;
    
      public PizzaStore (SimplePizzaFactory factory) {
        mFactory = factory;
      }
    
      public Pizza orderPizza(String type) {
        Pizza pizza = mFactory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.box();
      }
      public void shipPizza(String type) {
        Pizza pizza = mFactory.createPizza(type);
        pizza.ship();
      }
    }
    

    Bạn thấy công việc đơn giản hơn chưa, việc khởi tạo object cần thiết được xử lý trong Factory

    Như vậy chúng ta không cần phải lo lắng sửa code ở từng method như orderPizzahay shipPizza, … nữa 🙂

    Conlution

    Đây chỉ là một ví dụ rất cơ bản về Design Pattern giúp mọi người dễ hình dung và tiếp cận

    Chúng ta còn rất nhiều bài toán phức tạp khác cần Design Pattern để xử lý

    Quan trọng mọi người hiểu được việc "code để chạy" có thể nhanh trong thời điểm đấy

    Tuy nhiên việc mọi người phải rework để phát triển nó sẽ tốn gấp đôi gấp mười lần effort nếu mọi người có thể làm Design cẩn thận từ đầu

    Do đó việc hiểu các Design pattern hỗ trợ rất tốt cho mọi người trong việc triển khai các bài toán cụ thể

    Hy vọng bài này giúp mọi người hiểu được cơ bản Design Pattern là gì và tầm quan trọng của nó trong software

  • Design Pattern: Builder Pattern trong iOS

    Design Pattern: Builder Pattern trong iOS

    Design Pattern: Builder pattern trong iOS

    Nghe đến Design Pattern, chắc hẳn mỗi lập trình viên đều biết đến kỹ thuật quan trọng này và đã từng áp dụng nó ít nhất một lần. Design pattern giúp bạn giải quyết vấn đề một cách tối ưu nhất, cung cấp cho bạn các giải pháp trong lập trình hướng đối tượng (OOP). Phần lớn các ngôn ngữ lập trình đều có thể áp dụng Design pattern và Swift cũng không ngoại lệ!

    Design pattern có 3 nhóm chính:

    • Creational Pattern bao gồm: Abstract Factory, Factory Method, Singleton, Builder, Prototype.
    • Structural Pattern bao gồm: Adapter, Bridge, Composite, Decorator, Facade, Proxy và Flyweight.
    • Behavioral Pattern bao gồm: Interpreter, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy và Visitor.

    Hôm nay, chúng ta sẽ cùng tìm hiểu một design pattern trong nhóm Creational Pattern (Nhóm khởi tạo) là Builder pattern.

    Nội dung

    • Builder pattern là gì?
    • Vấn đề
    • Sử dụng nó như thế nào
    • Áp dụng trong iOS
    • Tổng kết

    Builder pattern là gì?

    Trong OOP, Builder pattern là một loại pattern thuộc nhóm Creational pattern (Tạo dựng), vì vậy nó sẽ giúp chúng ta giải quyết các vấn đề liên quan tới tạo dựng một object bằng cách:

    • Chia nhỏ các hàm khởi tạo của một object.
    • Đặt cấu hình mặc định và logic xử lý liên quan tới việc khởi tạo một object từ trong class ra bên ngoài và đẩy vào Builder class.

    Và việc chia nhỏ và đẩy vào Builder class như thế nào thì chúng ta hãy cùng tìm hiểu tiếp về vấn đề và áp dụng Builder Pattern trong một bài toán.

    Vấn đề

    Giả sử, chúng ta có một chương trình đặt bánh pizza, và mục tiêu của chúng ta là tạo ra chương trình đặt bánh để lấy yêu cầu từ khách hàng.

    Chúng ta có đối tượng Pizza, vả khởi tạo các đối tượng Pizza:

    
    class Pizza {
        
        var smell: String
        var base: String
        var size: String
        var isChiliSauce: Bool
        var isKetchup: Bool
        
        init(smell: String, base: String, size: String, isChiliSauce: Bool, isKetchup: Bool) {
            self.smell = smell
            self.base = base
            self.size = size
            self.isChiliSauce = isChiliSauce
            self.isKetchup = isKetchup
        }
    }
    
    
    
    let sizeM = Pizza(smell: "Bò", base: "Mỏng", size: "M", isChiliSauce: true, isKetchup: true)
    
    let sizeMChicken = Pizza(smell: "Gà", base: "Mỏng", size: "M", isChiliSauce: true, isKetchup: true)
    
    let sizeL = Pizza(smell: "Gà", base: "Dày", size: "L", isChiliSauce: true, isKetchup: true)
    
    

    Chúng ta có thể thấy hàm tạo khá nhiều thuộc tính, và có những thuộc tính mặc định ít thay đổi. Vì những thuộc tính đó ít thay đổi nên sẽ không tránh khỏi việc duplicate code. Do đó, người ta sử dụng Builder Pattern.

    Sử dụng nó như thế nào

    Class diagram

    Cùng phân tích một chút nhé:

    • Director: Đại diện cho module cần kết quả từ việc khởi tạo Pizza.
    • PizzaBuilder: Một class implement InterfaceBuilder, có nhiệm vụ khởi tạo ra Pizza, thông qua hàm build().
    • Pizza: Là object cần khởi tạo.

    Demo

    Một protocol nhằm khai báo các phương thức

    
    protocol PizzaBuilderProtocol {
        
        func build() -> Pizza
        
        func setSmell(_ smell: String) -> PizzaBuilder
    
        func setBase(_ base: String) -> PizzaBuilder
    
        func setSize(_ size: String) -> PizzaBuilder
        
        func withChiliSauce(_ isChiliSauce: Bool) -> PizzaBuilder
        
        func withKetchup(_ isKetchup: Bool) -> PizzaBuilder
    }
    
    

    PizzaBuilder triển khai các phương thức đã khai báo ở PizzaBuilderProtocol

    
    class PizzaBuilder: PizzaBuilderProtocol {
        
        private var pizza = Pizza()
        
        func build() -> Pizza {
            return self.pizza
        }
        
        func setSmell(_ smell: String) -> PizzaBuilder {
            pizza.smell = smell
            return self
        }
        
        func setBase(_ base: String) -> PizzaBuilder {
            pizza.base = base
            return self
        }
        
        func setSize(_ size: String) -> PizzaBuilder {
            pizza.size = size
            return self
        }
        
        func withChiliSauce(_ isChiliSauce: Bool) -> PizzaBuilder {
            pizza.isChiliSauce = isChiliSauce
            return self
        }
        
        func withKetchup(_ isKetchup: Bool) -> PizzaBuilder {
            pizza.isKetchup = isKetchup
            return self
        }
        
    }
    
    

    Cart ở đây giữ vai trò tương đương Director trong hình bên trên.

    
    class Cart {
        
        let orderId: String
        let products: [Pizza]
        
        init(orderId: String, products: [Pizza]) {
            self.orderId = orderId
            self.products = products
        }
    }
    
    let pizza1 = PizzaBuilder()
                .setSize("s")
                .setSmell("Gà")
                .setBase("Dày")
                .build()
            
    let cart1 = Cart(orderId: "OrderId1", products: [pizza1])
    
    

    Cách thực hiện thật đơn giản phải không? Bản chất của nó là chia nhỏ các properties trong Object ra thành các hàm getter, setter trong Builder và sau đó thực hiện set giá trị cho chúng và khởi tạo Object qua hàm build(). Và đó chính là cách viết theo Builder Pattern.

    Chắc hẳn khi các bạn đọc đến đây sẽ có một suy nghĩ rằng: Vậy tại sao không dùng default value? Đúng vậy, khi Builder Pattern được giới thiệu, ngôn ngữ họ sử dụng khi đó là C++. Nhưng, Swift của chúng ta đã cung cấp tính năng default value, và chúng ta có thể hoàn toàn loại bỏ Builder. Nói như vậy thì Builder Pattern sẽ trở nên vô dụng trong Swift?

    Chắc chắn là không. Những tài liệu trên mạng về Builder Pattern khá nhiều, và nó đang hướng người đọc vào vấn đề default value trong các ngôn ngữ như: C++, Java…Chúng ta hãy cùng nhìn lại về những thứ mà Builder làm, đó là giảm thiệu sự phức tạp khi khởi tạo thông qua hàm builder(). Bản chất chính là nó đang đóng gói việc khởi tạo một Object. Khi đó, chúng ta sẽ có thể xử lý nhiều hiện object, do something… trước khi trả về một Object hoàn chỉnh.

    Áp dụng trong iOS

    Chúng ta có thể áp dụng Builder Pattern ở rất nhiều trường hợp trong lập trình iOS. Mình, mình áp dụng trong việc customize một style cho Label, Button…

    Thường khi, chúng ta customize một Label theo một style nào đó, chúng ta sẽ thường khởi tạo như thế này, nhìn có vẻ khá rối và không được thú vị cho lắm.

    
    let attrString = NSMutableAttributedString(string: title)
            
    attrString.addAttributes([NSMutableAttributedString.Key.foregroundColor: UIColor.red],
                                      range: attrString.mutableString.range(of: title))
            
    attrString.addAttributes([NSMutableAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 13)],
                                      range: attrString.mutableString.range(of: title))
            
    attrString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 2,
                                         range: NSMakeRange(0, attrString.length))
            
    titleLabel.attributedText = attrString
    
    

    Chúng ta có thể áp dụng Builder pattern trong trường hợp này để nhìn code trở lên dễ đọc hơn và cũng dễ dàng phát triển trong tương lai.

    Tạo một builder và protocol

    
    protocol AttributedStringBuilderProtocol {
        
        func build() -> NSMutableAttributedString
        func setFont(_ font: UIFont, forSubString subString: String) -> AttributedStringBuilder
        func setColor(_ color: UIColor, forSubString subString: String) -> AttributedStringBuilder
        func withStrikeThrough() -> AttributedStringBuilder
        
    }
    
    class AttributedStringBuilder: AttributedStringBuilderProtocol {
        
        private var attrString: NSMutableAttributedString
        
        init(string: String) {
            self.attrString = NSMutableAttributedString(string: string)
        }
        
        func build() -> NSMutableAttributedString {
            return self.attrString
        }
        
        func setFont(_ font: UIFont, forSubString subString: String) -> AttributedStringBuilder {
            self.attrString.addAttributes([NSMutableAttributedString.Key.font: font],
                                          range: self.attrString.mutableString.range(of: subString))
            return self
        }
        
        func setColor(_ color: UIColor, forSubString subString: String) -> AttributedStringBuilder {
            self.attrString.addAttributes([NSMutableAttributedString.Key.foregroundColor: color],
                                          range: self.attrString.mutableString.range(of: subString))
            return self
        }
        
        func withStrikeThrough() -> AttributedStringBuilder {
            self.attrString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 2,
                                         range: NSMakeRange(0, self.attrString.length))
            return self
        }
    }
    
    

    Sử dụng

    
    let atrString = AttributedStringBuilder(string: title)
                .setFont(.boldSystemFont(ofSize: 13), forSubString: title)
                .setColor(.red, forSubString: title)
                .withStrikeThrough()
                .build()
    
    titleLabel.attributedText = attrString
    
    

    Như vậy, với trường hợp trên, việc sử dụng Builder Pattern sẽ giúp những dòng code của chúng ta trở nên dễ nhìn hơn và thuận lợi trong việc phát triển tiếp những properties của nó.

    Tổng kết

    Tóm lại, Design pattern: Builder Pattern là một trong những pattern dễ thực hiện. Nhưng khi sử dụng pattern này chúng ta cần phải mất thời gian để xác định được trường hợp nào cần nó, nó có thể trở nên rườm rà hơn khi sử dụng trong Swift. Nhưng chúng ta hãy đọc, tìm hiểu để áp dụng những tư duy và ý tưởng của một pattern vào một bài toán phù hợp. Đầu tư thời gian vào việc chuẩn bị xây dựng một sản phẩm sẽ luôn sẽ giúp việc bảo trì code trở nên dễ dàng hơn.

  • Swift—Design patterns: Multicast Delegate

    Swift—Design patterns: Multicast Delegate

    Multicast delegate

    Khái niệm

    • Chúng ta đều biết rằng delegate là mối quan hệ 1 – 1 giữa 2 objects, trong đó 1 ọbject sẽ gửi data/event và object còn lại sẽ nhận data hoặc thực thi event
    • Multicast delegate, về cơ bản chỉ là mối quan hệ 1 – n, trong đó, 1 object sẽ gửi dataevent đi, và có n class đón nhận data, event đó.

    Ứng dụng của multicase delegate, lúc nào thì dùng?

    • Dùng multicast delegate khi mà bạn muốn xây dựng một mô hình delegate có mối quan hệ 1 – nhiều.
    • Ví dụ: bạn có 1 class chuyên lấy thông tin data và các logic liên quan, và bạn muốn mỗi khi data của class được update và muốn implement các logic tương ứng của class này trên nhiều view/view controller khác nhau. Thì multicast delegate có thể dùng để xử lý bài toán này.

    So sánh với observer và notification

    • Tại sao không dùng observer: chủ yếu chúng ta dùng obverver để theo dõi data hoặc event nào đó xảy ra, và thực hiện logic sau khi nó đã xảy ra. Còn nếu dùng delegate, chúng ta còn có thể xử lý data hoặc event, hay nói cách khác, chúng ta có thể quyết định xem có cho phép event đó xảy ra hay không, ví dụ như các delegate của Table view như tableView:willSelectRowAtIndexPath:
    • Tại sao không dùng Notification thay vì multicast delegate? Về cơ bản, notification là thông tin một chiều từ 1 object gửi sang nhiều object nhận, chứ không thể có tương tác ngược lại. Ngoài ra, việc gửi data từ object gửi sang các object nhận thông qua userInfo thực sự là một điểm trừ rất lớn của Notifiction. Hơn nữa, việc quản lý Notification sẽ tốn công sức hơn là delegate, và chúng ta khá là khó khăn để nhìn ra mối quan hệ giữa object gửi và nhận khi dùng Notification.

    Implementation

    • Vì Swift không có sẵn phương pháp tạo multicast delegate nên chúng ta cần phải tạo ra 1 class helper, nhằm quản lý các object muốn nhận delegate cũng như gọi các method delegate muốn gửi.

    Đầu tiên, chúng ta tạo class helper như dưới:

    class MulticastDelegate<ProtocolType> {
        private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()
        
        func add(_ delegate: ProtocolType) {
            delegates.add(delegate as AnyObject)
        }
        
        func remove(_ delegateToRemove: ProtocolType) {
            for delegate in delegates.allObjects.reversed() {
                if delegate === delegateToRemove as AnyObject {
                    delegates.remove(delegate)
                }
            }
        }
        
        func invokeDelegates (_ invocation: (ProtocolType) -> Void) {
            for delegate in delegates.allObjects.reversed() {
                invocation(delegate as! ProtocolType)
            }
        }
    }
    

    Class này là helper class có các method là add:(:) dùng để add và remove các object muốn nhận delegate. Ngoài ra nó có method invokeDelegates(:)->Void để gửi method delegate sang toàn bộ các object muốn nhận delegate.

    Tiếp theo, define protocol (các delegate method) muốn implement:

    protocol SampleDelegate: class {
        func sendSampleDelegateWithoutData()
        func sendSampleDelegate(with string: String)
    }
    
    extension SampleDelegate {
        func sendSampleDelegateWithoutData() {        
        }
    }
    

    Ở đây, protocol SampleDelegate có 2 method là để ví dụ thêm rõ ràng rằng multicast delegate có thể thoải mái gửi các delegate tuỳ ý. Phần extention của SampleDelegate chỉ là để khiến cho method sendSampleDelegateWithoutData trở thành optional, không cần phải "conform" đến SampleDelegate. Đây là cách khá Swift, thay vì dùng cách sử dụng @objC và keywork optional

    Tiếp theo, define ra class sẽ gửi các method của delegate

    class SampleClass {
        var delegate = MulticastDelegate<SampleDelegate>()
        
        func didGetData() {
            delegate.invokeDelegates {
                $0.sendSampleDelegate(with: "Sample Data")
            }
        }
    }
    

    Ở đây, có thể thấy rằng delegate của class "SampleClass" thực chất là object của helpper "MulticastDelegate", và nó chỉ chấp nhận delegate là objects của các class mà conform đến protocol "SampleDelegate"

    Khai báo vài class conform đến protocol "SampleDelegate"

    class ReceivedDelegate1: SampleDelegate {
        func sendSampleDelegate(with string: String) {
            print("ReceivedDelegate === 1 === \(string)")
        }
        
        deinit {
            print("deinit ReceivedDelegate1")
        }
    }
    
    class ReceivedDelegate2: SampleDelegate {
        func sendSampleDelegate(with string: String) {
            print("ReceivedDelegate === 2 === \(string)")
        }
        
        deinit {
            print("deinit ReceivedDelegate2")
        }
    }
    

    OK, bây giờ test thử:

    let sendDelegate = SampleClass()
    let received1 = ReceivedDelegate1()
    sendDelegate.delegate.add(received1)
    
    do {
        let received2 = ReceivedDelegate2()
        sendDelegate.delegate.add(received2)
        sendDelegate.didGetData()
    }
    print("Đợi cho object received2 trong block do được release")
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        sendDelegate.didGetData()
    }
    
    

    Sau khi run đoạn source code này, ta được log như dưới:

    ReceivedDelegate === 2 === Sample Data
    ReceivedDelegate === 1 === Sample Data
    Đợi cho object received2 trong block do được release
    deinit ReceivedDelegate2
    ReceivedDelegate === 1 === Sample Data
    

    Giờ cùng phân tích:

    Trong block do thì object "sendDelegate" đã append được 2 delegate objects vào biến delegate của nó, tiến hành send delegate thì ta thấy rằng cả object của cả 2 class ReceivedDelegate1ReceivedDelegate2 đều nhận được.

    Sau block do thì object received2 sẽ được release, vì việc release sẽ tốn một chút thời gian cho nên chúng ta sẽ thử thực hiện việc send delegate sau khoảng thời gian 1s, sau khi received2 đã được release (bằng cách check log của method deinit)

    Lúc này, ta thấy rằng chỉ có object của class ReceivedDelegate1 là còn nhận được delegate, object của class ReceivedDelegate2 đã bị release nên không còn nhận được object nữa. Như vậy, cách làm này vẫn đảm bảo các delegate vẫn là weak reference, không gây ra leak memory.

    Đề làm được điều này thì ta đã sử dụng NSHashTable.weakObjects() để lưu weak reference đến các delegate được gán vào biến delegates của helper MulticastDelegate. Do đó đảm bảo được việc keep weak reference của class helper, nhằm tránh memory leak.

    Ví dụ xem file: https://github.com/nhathm/swift.sample/tree/master/DesignPatterns/MulticastDelegate.playground