Generic (برنامهنویسی عمومی) — مکانیزمی در Swift است که امکان نوشتن نوعها و توابع با نوع پارامتری
نکات اصلی
Generic (نوع عمومی) — یک ساختار نحوی در Swift است که امکان نوشتن کد انعطافپذیر و قابل استفاده مجدد با پارامتریسازی نوع را فراهم میکند. به جای نوع خاص، یک placeholder در براکتهای زاویهای نوشته میشود — معمولاً
بدون generic باید توابع جداگانهای برای هر نوع نوشته میشد: swapInts، swapStrings، swapDates. تابع generic swap هر سه حالت را با یک اعلان جایگزین میکند. این کار تکرار را کاهش میدهد، خوانایی را افزایش میدهد و احتمال خطا در هنگام کپی کد را کاهش میدهد.
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 هم در مرحله کامپیل و هم در زمان اجرا وجود دارند (اگرچه بهینهسازی ممکن است آنها را تخصصی کند).
توابع Generic — توابعی هستند که یک یا چند پارامتر نوع را میپذیرند. پارامترهای نوع بعد از نام تابع در براکتهای زاویهای مشخص میشوند:
یک تابع میتواند چندین پارامتر generic داشته باشد. به عنوان مثال، تابع findKey<K, V> یک دیکشنری با کلیدهای K و مقادیر V میپذیرد. هر پارامتر نوع منحصر به فرد است و میتواند در محدودیتها استفاده شود.
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 — کلمه کلیدی Swift است که محدودیتهای اضافی بر پارامترهای generic اعمال میکند. برخلاف مشخص کردن ساده پروتکل در براکتهای زاویهای (<T: Comparable>)، where امکان بیان شرایط پیچیدهتر را فراهم میکند: مطابقت با چندین پروتکل، ارتباط بین پارامترهای نوع و دقیقسازی associated types.
بند where بعد از امضای تابع یا نوع و قبل از براکت باز نوشته میشود. در where میتوان مشخص کرد که T: Comparable & Hashable (همزمان دو پروتکل)، یا اینکه T.U == Int (مشخصسازی associated type).
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 (انواع مرتبط) — راهی برای عمومی کردن پروتکل بدون مشخص کردن نوع خاص در مرحله اعلان است. به جای <T> پروتکل یک associatedtype اعلان میکند که در پیادهسازی مشخص میشود. این به ویژه برای مجموعهها مهم است: پروتکل Sequence نمیداند که دنباله چه عناصری را در بر خواهد داشت.
با استفاده از where میتوان ارتباطاتی بین associated types پروتکلهای مختلف برقرار کرد. به عنوان مثال، میتوان نیاز داشت که Item یک پروتکل با Iterator.Element پروتکل دیگر مطابقت داشته باشد. این کار سازگاری نوع را در سطح کامپیل تضمین میکند.
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 را در براکتهای زاویهای میپذیرد. این برای دسترسی امن به مجموعهها با ایندکس دلخواه مفید است، زمانی که ایندکس میتواند از انواع مختلف باشد.
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 زمانی موجه است که نوع حقیقتاً تغییر میکند، نه برای «انعطافپذیری آینده» انتزاعی.
فراخوانی متد روی پارامتر generic بدون مشخص کردن وجود متد — یک خطاست. <T> نمیتواند .count را فراخوانی کند اگر مشخص نشده باشد که T: Collection. همیشه به پارامترهای نوع محدودیت اضافه کنید، در غیر این صورت کامپایلر کد را رد میکند.
// ❌ 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 — راهی برای نوشتن کدی است که با هر نوعی کار میکند، بدون تکرار منطق یکسان برای Int، String و انواع دیگر. به جای نوع خاص، از placeholder <T> استفاده میشود که در زمان استفاده با نوع واقعی جایگزین میشود.
Generic اطلاعات مربوط به نوع خاص را در مرحله کامپیل حفظ میکند که به کامپایلر امکان بررسی انواع و تولید کد بهینهشده را میدهد. Any — پاکسازی نوع است: هر مقداری میتواند منتقل شود، اما نوع از دست میرود و نیاز به تبدیل (as?) دارد.
از نحو T: SomeProtocol در براکتهای زاویهای یا یک بند جداگانه where T: SomeProtocol استفاده کنید. محدودیتها تضمین میکنند که پارامتر generic از متدها و ویژگیهای مشخصی پشتیبانی میکند.
Associated Type (associatedtype) — یک placeholder برای نوع درون پروتکل است. پروتکل نوع خاص را مشخص نمیکند، بلکه یک associatedtype اعلان میکند که در پیادهسازی مشخص میشود: Array دارای Element، Dictionary دارای Key و Value.
Swift کد generic را به نسخههای تخصصی برای هر نوع خاص کامپیل میکند. در عمل، تابع generic برای Int کندتر از تابع دستنویس برای Int نیست. هیچ سرباری برای انتزاع وجود ندارد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید