Generic — ماهیت انواع عمومی و پارامتر T

نویسنده: IT Sectr منتشر شده: 2026-06-18 زمان مطالعه: 10 دقیقه

Generic (برنامه‌نویسی عمومی) — مکانیزمی در Swift است که امکان نوشتن نوع‌ها و توابع با نوع پارامتری را فراهم می‌کند. به جای تکرار کد برای Int، String و ساختارهای سفارشی، توسعه‌دهنده یک راه‌حل عمومی ایجاد می‌کند که با هر نوعی کار می‌کند و در عین حال تایپ‌سازی دقیق را حفظ می‌کند. کامپایلر نوع خاص را در زمان استفاده جایگزین می‌کند که امنیت را بدون کاهش عملکرد تضمین می‌کند. به گفته Swift Book, 2025، پارامترهای generic یکی از قابلیت‌های کلیدی زبان هستند که در پایه کتابخانه استاندارد و SwiftUI قرار دارند.

نکات اصلی

  • Generic — مکانیزم برنامه‌نویسی عمومی با پارامترهای نوع
  • توابع با پارامترهای generic بدون تکرار کد با هر نوعی کار می‌کنند
  • محدودیت‌های where دامنه کاربرد generic را به انواعی که شرایط را برآورده می‌کنند محدود می‌کنند
  • Associated Types در پروتکل‌ها به پروتکل‌ها امکان عمومی بودن می‌دهند
  • کامپایلر برای هر نوع خاص کد تخصصی تولید می‌کند

Generic در Swift چیست؟

Generic (نوع عمومی) — یک ساختار نحوی در Swift است که امکان نوشتن کد انعطاف‌پذیر و قابل استفاده مجدد با پارامتری‌سازی نوع را فراهم می‌کند. به جای نوع خاص، یک placeholder در براکت‌های زاویه‌ای نوشته می‌شود — معمولاً ، اما می‌توان از هر نامی استفاده کرد. Generic در پایه SwiftUI (View, some View)، کتابخانه استاندارد (Array, Optional) و Combine قرار دارد.

چرا Generic لازم است

بدون generic باید توابع جداگانه‌ای برای هر نوع نوشته می‌شد: swapInts، swapStrings، swapDates. تابع generic swap هر سه حالت را با یک اعلان جایگزین می‌کند. این کار تکرار را کاهش می‌دهد، خوانایی را افزایش می‌دهد و احتمال خطا در هنگام کپی کد را کاهش می‌دهد.

swift
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var x = 10
var y = 20
swapValues(&x, &y)

کامپایلر Swift برای هر استفاده خاص از generic کد تخصصی تولید می‌کند. این بدان معناست که تابع generic swapValues<Int> در مقایسه با تابع دست‌نویس برای Int هیچ سرباری ندارد. Swift از پاک‌سازی نوع مانند Java استفاده نمی‌کند — generic هم در مرحله کامپیل و هم در زمان اجرا وجود دارند (اگرچه بهینه‌سازی ممکن است آنها را تخصصی کند).

توابع عمومی با پارامتر T

توابع Generic — توابعی هستند که یک یا چند پارامتر نوع را می‌پذیرند. پارامترهای نوع بعد از نام تابع در براکت‌های زاویه‌ای مشخص می‌شوند: . آنها می‌توانند در امضا استفاده شوند: نوع آرگومان، نوع بازگشتی و در داخل بدنه تابع.

چندین پارامتر نوع

یک تابع می‌تواند چندین پارامتر generic داشته باشد. به عنوان مثال، تابع findKey<K, V> یک دیکشنری با کلیدهای K و مقادیر V می‌پذیرد. هر پارامتر نوع منحصر به فرد است و می‌تواند در محدودیت‌ها استفاده شود.

swift
func findKey<K: Hashable, V>(
    for value: V,
    in dictionary: [K: V]
) -> K? where V: Equatable {
    for (key, dictValue) in dictionary {
        if dictValue == value {
            return key
        }
    }
    return nil
}

محدودیت‌های K: Hashable و where V: Equatable تضمین می‌کنند که findKey فقط با دیکشنری‌ای قابل فراخوانی است که کلیدهایش قابل هش هستند و مقادیرش قابل مقایسه. چنین محدودیت‌هایی بوروکراسی نیستند، بلکه ضرورت هستند: بدون Hashable نمی‌توان جستجو بر اساس کلید را ایندکس کرد، بدون Equatable نمی‌توان value را با dictValue مقایسه کرد.

محدودیت‌های نوع با where

where — کلمه کلیدی Swift است که محدودیت‌های اضافی بر پارامترهای generic اعمال می‌کند. برخلاف مشخص کردن ساده پروتکل در براکت‌های زاویه‌ای (<T: Comparable>)، where امکان بیان شرایط پیچیده‌تر را فراهم می‌کند: مطابقت با چندین پروتکل، ارتباط بین پارامترهای نوع و دقیق‌سازی associated types.

نحو where

بند where بعد از امضای تابع یا نوع و قبل از براکت باز نوشته می‌شود. در where می‌توان مشخص کرد که T: Comparable & Hashable (همزمان دو پروتکل)، یا اینکه T.U == Int (مشخص‌سازی associated type).

swift
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
}

extension Container where Item: Comparable {
    func isSorted() -> Bool {
        // Implementation available only if Item: Comparable
        return true
    }
}

توسعه‌دهنده با where متدهایی را فقط برای آن دسته از انواعی که شرط را برآورده می‌کنند اضافه می‌کند. Container<Int> متد isSorted را دریافت می‌کند، اما Container<Any> نه، زیرا Any Comparable نیست. این یک مکانیزم قدرتمند برای قابلیت شرطی است.

Associated Types در پروتکل‌ها

Associated Types (انواع مرتبط) — راهی برای عمومی کردن پروتکل بدون مشخص کردن نوع خاص در مرحله اعلان است. به جای <T> پروتکل یک associatedtype اعلان می‌کند که در پیاده‌سازی مشخص می‌شود. این به ویژه برای مجموعه‌ها مهم است: پروتکل Sequence نمی‌داند که دنباله چه عناصری را در بر خواهد داشت.

ارتباط associated types از طریق where

با استفاده از where می‌توان ارتباطاتی بین associated types پروتکل‌های مختلف برقرار کرد. به عنوان مثال، می‌توان نیاز داشت که Item یک پروتکل با Iterator.Element پروتکل دیگر مطابقت داشته باشد. این کار سازگاری نوع را در سطح کامپیل تضمین می‌کند.

swift
protocol StackProtocol {
    associatedtype Element
    mutating func push(_ item: Element)
    mutating func pop() -> Element?
}

struct IntStack: StackProtocol {
    typealias Element = Int
    private var items: [Int] = []
    
    mutating func push(_ item: Int) { items.append(item) }
    mutating func pop() -> Int? { items.popLast() }
}

IntStack Element را با استفاده از typealias به عنوان Int مشخص می‌کند. کامپایلر بررسی می‌کند که آیا تمام الزامات پروتکل با این نوع برآورده می‌شوند. بدون associated types باید StackProtocol<Int> با پروتکل generic نوشته می‌شد، اما Swift برای یکپارچگی با پل‌های Objective-C و خوانایی بهتر از associated types استفاده می‌کند.

Generic در توسعه‌دهنده‌ها و زیرنویس‌ها

توسعه‌دهنده‌های Generic امکان اضافه کردن متدهایی به نوع generic با محدودیت‌های اضافی را فراهم می‌کنند. این الگوی «انطباق شرطی» است، زمانی که نوع فقط تحت شرایط خاصی قابلیت دریافت می‌کند. زیرنویس‌ها نیز می‌توانند generic باشند.

زیرنویس Generic

زیرنویس می‌تواند عمومی باشد: پارامترهای generic را در براکت‌های زاویه‌ای می‌پذیرد. این برای دسترسی امن به مجموعه‌ها با ایندکس دلخواه مفید است، زمانی که ایندکس می‌تواند از انواع مختلف باشد.

swift
extension Array where Element: Numeric {
    func sum() -> Element {
        return reduce(0, +)
    }
}

extension Array {
    subscript<Indices: Sequence>(indices: Indices) -> [Element]
        where Indices.Element == Int {
        return indices.map { self[$0] }
    }
}

چنین ساختاری ترکیب قدرتمندی ایجاد می‌کند: متد sum فقط در آرایه‌های عددی ظاهر می‌شود و زیرنویس با ایندکس‌های دلخواه با هر دنباله‌ای از ایندکس‌های صحیح کار می‌کند. کامپایلر خود visibility این اعضا را بر اساس شرایط where مدیریت می‌کند.

خطاهای رایج هنگام کار با جنریک‌ها

خطاها در استفاده از generic اغلب به محدودیت‌های نادرست یا تلاش برای استفاده از generic در جایی که نوع خاصی لازم است مربوط می‌شوند. بیایید سه سناریوی رایجی که توسعه‌دهندگان با آن مواجه می‌شوند را بررسی کنیم.

پارامتری‌سازی بیش از حد

اضافه کردن پارامترهای generic در جایی که نوع خاص کافی است — یک ضدالگو است. اگر تابع همیشه با String کار می‌کند، نیازی به <T> کردن آن نیست. این کار کد را بدون سود پیچیده می‌کند. Generic زمانی موجه است که نوع حقیقتاً تغییر می‌کند، نه برای «انعطاف‌پذیری آینده» انتزاعی.

عدم وجود محدودیت where

فراخوانی متد روی پارامتر generic بدون مشخص کردن وجود متد — یک خطاست. <T> نمی‌تواند .count را فراخوانی کند اگر مشخص نشده باشد که T: Collection. همیشه به پارامترهای نوع محدودیت اضافه کنید، در غیر این صورت کامپایلر کد را رد می‌کند.

swift
// ❌ Error: value has no count method
func countElements<T>(value: T) -> Int { value.count }

// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }

خطا قابل درک است: generic <T> می‌تواند هر نوعی باشد و کامپایلر نمی‌داند که آیا count دارد یا خیر. محدودیت Collection مشکل را حل می‌کند. به طور مشابه برای Equatable، Hashable، Numeric — همیشه پروتکل را مشخص کنید اگر قصد فراخوانی متدها یا عملگرها روی پارامتر generic را دارید.

سوالات متداول

Generic در Swift به زبان ساده چیست؟

Generic — راهی برای نوشتن کدی است که با هر نوعی کار می‌کند، بدون تکرار منطق یکسان برای Int، String و انواع دیگر. به جای نوع خاص، از placeholder <T> استفاده می‌شود که در زمان استفاده با نوع واقعی جایگزین می‌شود.

تفاوت Generic با Any چیست؟

Generic اطلاعات مربوط به نوع خاص را در مرحله کامپیل حفظ می‌کند که به کامپایلر امکان بررسی انواع و تولید کد بهینه‌شده را می‌دهد. Any — پاک‌سازی نوع است: هر مقداری می‌تواند منتقل شود، اما نوع از دست می‌رود و نیاز به تبدیل (as?) دارد.

چگونه Generic را با پروتکل محدود کنیم؟

از نحو T: SomeProtocol در براکت‌های زاویه‌ای یا یک بند جداگانه where T: SomeProtocol استفاده کنید. محدودیت‌ها تضمین می‌کنند که پارامتر generic از متدها و ویژگی‌های مشخصی پشتیبانی می‌کند.

Associated Type در پروتکل چیست؟

Associated Type (associatedtype) — یک placeholder برای نوع درون پروتکل است. پروتکل نوع خاص را مشخص نمی‌کند، بلکه یک associatedtype اعلان می‌کند که در پیاده‌سازی مشخص می‌شود: Array دارای Element، Dictionary دارای Key و Value.

آیا Generic از انواع خاص سریع‌تر یا کندتر کار می‌کند؟

Swift کد generic را به نسخه‌های تخصصی برای هر نوع خاص کامپیل می‌کند. در عمل، تابع generic برای Int کندتر از تابع دست‌نویس برای Int نیست. هیچ سرباری برای انتزاع وجود ندارد.

خلاصه

  • Generic — مکانیزم برنامه‌نویسی عمومی با پارامتر نوع <T>
  • توابع و انواع با پارامترهای generic جایگزین بسیاری از پیاده‌سازی‌های تکراری می‌شوند
  • محدودیت‌های where دامنه کاربرد generic را به شرایط ضروری محدود می‌کنند
  • Associated Types به پروتکل‌ها امکان عمومی بودن بدون مشخص‌سازی در مرحله اعلان می‌دهند
  • توسعه‌دهنده‌ها با where فقط در صورت برآورده شدن شرایط قابلیت اضافه می‌کنند
  • کامپایلر کد تخصصی تولید می‌کند — generic سربار اضافه نمی‌کند
  • پارامتری‌سازی بیش از حد — یک ضدالگو است، فقط زمانی از generic استفاده کنید که نوع حقیقتاً تغییر می‌کند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید