مدیریت بهتر Extensionها در سوییفت

یکی از باحال‌ترین قابلیت‌های زبان برنامه‌نویسی سوییفت، Extension و کاربردهای اونه.

توی این مقاله نمی‌خوایم خود Extension و نحوه استفاده از اون رو یادآوری کنیم؛ ولی می‌خوایم یه راهکار معرفی کنیم، تا با استفاده از اون، استفاده از Extensionها یکم بهتر و باحال‌تر بشه. ?


خب.

فرض کنیم یه extension نوشتیم برای UIColor که میاد از رنگ انتخابی ما، یه عکس یا همون UIImage تولید می‌کنه.

extension UIColor { 
    func toImage() -> UIImage { 
        let color = self 
        let rect = CGRect(x: 0, y: 0, width: 1, height: 1) 
        UIGraphicsBeginImageContext(rect.size) 
        let context = UIGraphicsGetCurrentContext()        
        context!.setFillColor(color.cgColor)        
        context!.fill(rect) 
        let img = UIGraphicsGetImageFromCurrentImageContext() 
        UIGraphicsEndImageContext() 
        return img!    
    }
}

و بصورت زیر هم می‌تونیم ازش استفاده کنیم:

let redImage = UIColor.red.toImage()

مشکلی هم نیست. بهمین راحتی، بهمین خوشمزگی. ?

ولی...!


افتاد مشکل‌ها!

۱. مشکل اول توی پیاده‌سازی ما، اینه که وقتی یه نفر بیاد و این کد رو ببینه، نمیتونه تشخیص بده که متد toImage() از متدهای استاندارد و اصلی خود UIColor هست یا نه!

۲. مشکل بعدی، اینه که اگه هرکدوم از کتابخانه‌هایی که توی پروژه ازشون استفاده می‌کنیم، یه Extension روی UIColor تعریف کرده باشن و داخلش یه متد مثل متد ما تعریف کرده باشن چی؟!


راه حل

یه راه حل برای مشکل پیشرو، اینه که خیلی راحت، به همه متدهایی که تعریف می‌کنیم، یه پیشوند مثل my_ و یا omid_ اضافه کنیم.

extension UIColor {
    func omid_toImage() -> UIImage {
        let color = self
        let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context!.setFillColor(color.cgColor)
        context!.fill(rect)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img!
    }
}

...

let redImage = UIColor.red.omid_toImage()

این راه حل ممکنه در نگاه اول خوب باشه، ولی اگه ما روی بیست‌تا کلاس مختلف، مجموعا ۱۰۰ تا متد دلخواه با استفاده از Extension تعریف کرده باشیم چی؟! باید مطمئن بشیم که پیشوند انتخابی‌مون، به ابتدا تمام متدها اضافه بشه.


استفاده از Protocol؛ راه حل اصلی

نظرتون چیه به جای my_toImage() از my.toImage() استفاده کنیم؟!


رویه اینه که یه کلاس/ساختمان تعریف می‌کنیم، و همه متدهامون رو با استفاده از اون، و مفهوم Protocol و Extension، خیلی هوشمندانه و باحال اضافه می‌کنیم. ?

import UIKit

public protocol MyHelperCompatible {
    associatedtype someType
    var my: someType { get }
}

public extension MyHelperCompatible {
    public var my: MyHelper<Self> { 
        get { 
            return MyHelper(self) 
        }
    }
}

public struct MyHelper<Base> {
    let base: Base
    init(_ base: Base) { 
        self.base = base
    }
}

// All conformance here
extension UIColor: MyHelperCompatible {}

پروتوکل MyHelperCompatible که تعریف کردیم، یه مشخصه به اسم my داره، که میاد و جایگزین اون کلاس/ساختمان مورد بحث میشه.

بعدش میایم و بر اساس مورد استفاده‌مون، MyHelper رو گسترش می‌دیم:

import Foundation
import UIKit

extension MyHelper where Base: UIColor {
    func toImage() -> UIImage { 
        let color = self.base
        let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context!.setFillColor(color.cgColor)
        context!.fill(rect) let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext() 
        return img!
    }
}

در واقع با استفاده از کد بالا، (و البته تعریف ساختمان MyHelper) داریم می‌گیم، اگه نوع پایه‌ای مورد استفاده برای MyHelper از نوع UIColor بود، این متد toImage رو بهش اضافه کن. و بصورت زیر هم می‌تونیم ازش استفاده کنیم:

let redImage = UIColor.red.my.toImage()


همین رویه رو می‌تونیم برای اضافه کردن متدهای مورد نظرمون، به انواع مختلف داده، بکار ببریم. و اینکه هر موقع خواستیم این متدها دیگه در دسترس نباشن، می‌تونیم خطی که باهاش نوع داده رو با پروتوکل تعریف شده هماهنگ می‌کنه، حذف یا کامنت کنیم.

// extension UIColor: MyHelperCompatible {}


نکات پایانی

? برای اینکه مطالب این مقاله و نحوه پیاده‌سازی و کارکرد رو کامل متوجه بشیم، باید با مفاهیم Protocol، Extension، Generic آشنایی داشته باشیم.

? ممکنه پیاده‌سازی همچین چیزی، بار اول، یکم پیچیده و نامفهوم باشه، ولی در نهایت کمک زیادی می‌کنه.

در آخر، می‌تونین از همین رویه، برای آماده‌کردن و پیاده‌سازی یه Framework مثل PersianSwift استفاده کنین، و مطمئن باشین که متدهاتون با بقیه متدها و فریم‌ورک‌ها هم‌زیستی خواهند داشت! ?



? منبع مطلب: این مطلب رو بر اساس این پست نوشتم.