some View — ساختار نحوی کلیدی Swift است که بدون آن کار SwiftUI غیرممکن است. به گزارش Apple Swift Book, 2024، some View یک نوع ناپیدا (opaque type) است که نوع مشخص مقدار بازگشتی را پنهان میکند، در عین حال تایپگیری دقیق را در مرحله کامپایل حفظ میکند. این ساختار به پروتکل View اجازه میدهد تا امضای body یکسانی داشته باشد، بدون افشای جزئیات پیادهسازی.
نکات اصلی
some View — نحوه نوشتن نوع ناپیدا (opaque type) است که در Swift 5.1 معرفی شد. به عنوان نوع بازگشتی ویژگی body پروتکل View استفاده میشود. نوشتن some View به این معنی است: «تابع یا ویژگی یک نوع مشخص خاصی را که با پروتکل View مطابقت دارد بازمیگرداند، اما کد فراخوان نمیداند و نباید بداند کدام نوع دقیقاً».
مفهوم نوع ناپیدا طرف مقابل برنامهنویسی عمومی (جنریک) است. اگر جنریک به کد فراخوان اجازه میدهد نوع را تعیین کند، opaque type به پیادهسازی اجازه میدهد نوع را تعیین کند، آن را از فراخوان پنهان میکند. این به توسعهدهنده آزادی تغییر پیادهسازی داخلی را بدون تغییر قرارداد میدهد.
بر اساس Swift Evolution SE-0244، انواع ناپیدا برای پشتیبانی از SwiftUI و الگوی پروتکلهای با نوع مرتبط (PAT) اضافه شدند که بدون این ساختار نمیتوان از آنها به عنوان نوع بازگشتی استفاده کرد.
بدون some View امضای body غیرممکن بود: پروتکل View یک نوع مرتبط Body دارد که با View مطابقت دارد. اگر body صرفاً View (به عنوان پروتکل) بازمیگرداند، Swift نمیتوانست با پروتکلهای دارای Self requirements در موقعیت بازگشت کار کند. some View این مشکل را با ارائه یک نوع مشخص اما پنهان حل میکند.
نوع ناپیدا (opaque type) — نوع خاصی است که برای کامپایلر مانند یک نوع مشخص و برای توسعهدهنده مانند یک نوع انتزاعی رفتار میکند. وقتی کامپایلر some View را میبیند، پیادهسازی را تحلیل میکند و نوع دقیق بازگشتی را تعیین میکند. این نوع ثابت میشود و برای تولید کد بدون ارسال پویا استفاده میشود.
struct SimpleView: View {
var body: some View {
Text("سلام")
}
}
// کامپایلر میبیند: body -> Text، نه some View
اصل کار: کامپایلر Swift نوع مشخص را از پیادهسازی استخراج میکند. در مثال بالا، بدنه body فقط شامل Text است، بنابراین کامپایلر میداند که body دقیقاً Text را بازمیگرداند، اگرچه امضا به صورت some View نوشته شده است. این دو بهینهسازی را فراهم میکند: فراخوانی مستقیم بدون جدول متدهای مجازی و امکان درونخطی کردن.
اگر پیادهسازی body تغییر کند (مثلاً به جای Text، VStack شامل Text و Button بازگردانده شود)، کامپایلر نوع مشخص را دوباره تعیین میکند. اما برای کد فراخوان (SwiftUI) امضا یکسان میماند — some View. این همان طرف مقابل جنریک است: کد فراخوان به تغییرات پیادهسازی وابسته نیست.
یکی از قوانین کلیدی opaque type: تابع یا ویژگی که some View را بازمیگرداند، باید همیشه همان نوع مشخص را بازگرداند. نمیتوان در یک شاخه if Text و در شاخه دیگر Image بازگرداند. این محدودیت توسط کامپایلر بررسی میشود و تضمینی برای کد فراخوان است.
struct BadView: View {
var flag: Bool
var body: some View {
if flag {
Text("درست") // خطا: Text vs VStack
} else {
VStack {
Text("نادرست")
Image(systemName: "xmark")
}
}
}
}
برای حل این مشکل از @ViewBuilder استفاده میشود که شاخههای مختلف را در یک کانتینر شرطی ConditionalContent میپیچد. حاشیهنویسی @ViewBuilder روی body — یک روش استاندارد در SwiftUI است، اگرچه اگر body فقط یک عبارت داشته باشد میتواند ضمنی باشد.
AnyView — نوعی است که پیادهسازی مشخص View را پاک میکند (type erasure). هر View را در یک پوشش واحد میپیچد و اجازه میدهد Viewهای انواع مختلف در یک کانتینر ذخیره شوند. برخلاف some View، AnyView در زمان اجرا کار میکند و سربار بستهبندی و باز کردن بسته را اضافه میکند.
| معیار | some View | AnyView |
|---|---|---|
| زمان تعیین | کامپایل | اجرا |
| عملکرد | فراخوانی مستقیم، بدون سربار | بستهبندی در existential container |
| انعطافپذیری انواع | یک نوع مشخص | هر نوع View |
| تغییر پویا | پشتیبانی نمیشود | در زمان اجرا پشتیبانی میشود |
| اولویت استفاده | همیشه وقتی ممکن است | فقط وقتی some View ممکن نیست |
| پشتیبانی از پروتکلهای PAT | بله | بله |
چه زمانی از AnyView استفاده کنیم: فقط در شرایطی که some View به دلیل نیاز به تغییر پویای نوع در زمان اجرا ممکن نیست. مثلاً هنگام بازگرداندن View از دیکشنری یا در ساختار بازگشتی که نوع مشخص باید در هر سطح تغییر کند. AnyView باید به حداقل برسد، زیرا هر بستهبندی بهینهسازیهای SwiftUI را غیرفعال میکند.
باور اشتباه: AnyView مشکل انواع مختلف در body را حل نمیکند — این مشکل توسط @ViewBuilder حل میشود. AnyView نوع را پاک میکند، اما به کامپایلر در استخراج نوع واحد کمک نمیکند. از @ViewBuilder برای منطق شرطی و از AnyView فقط برای ارسال پویا استفاده کنید.
@ViewBuilder — یک result builder است که مخصوص کار با some View ایجاد شده است. امکان استفاده از منطق شرطی (if/else، switch) و عبارات متعدد در بدنه body را فراهم میکند، در عین حال نوع بازگشتی واحد را حفظ میکند. ViewBuilder به طور خودکار عبارات متعدد را در TupleView و شاخههای شرطی را در ConditionalContent میپیچد.
struct ProfileView: View {
let user: User?
@ViewBuilder
var body: some View {
if let user {
UserCard(user: user)
Text("آنلاین")
.font(.caption)
} else {
ProgressView("Loading...")
}
}
}
چگونه کار میکند: @ViewBuilder بلوک کد را تحلیل میکند و فراخوانی متناسب buildBlock، buildOptional یا buildEither را تولید میکند. برای منطق شرطی، ConditionalContent ایجاد میشود — یک نوع مشترک که انواع مشخص داخل شاخهها را پنهان میکند، اما خود برای کامپایلر یک نوع واحد است. این مشکل انواع مشخص مختلف را حل میکند.
بدون @ViewBuilder، ویژگی body شامل عبارات متعدد یا منطق شرطی باعث خطای کامپایل میشد. به همین دلیل SwiftUI به طور ضمنی @ViewBuilder را روی body اعمال میکند، و برای ویژگیها و توابع سفارشی باید آن را صریحاً اضافه کرد.
@ViewBuilder میتواند تودرتو باشد: یک ViewBuilder درون دیگری. این امکان ایجاد سلسلهمراتب پیچیده با شرایط در سطوح مختلف را فراهم میکند. با این حال، تودرتویی عمیق خوانایی را کاهش میدهد، بنابراین توصیه میشود شرایط تودرتو را به کامپوننتهای View جداگانه منتقل کنید.
مثال 1: بازگرداندن View سفارشی از یک ویژگی محاسبهشونده. ویژگی میتواند some View را بازگرداند و ترکیب داخلی را پنهان کند. این امکان بازسازی کد را بدون تغییر رابط عمومی فراهم میکند.
struct ArticleView: View {
var body: some View {
CardView {
HeaderView()
ContentView()
FooterView()
}
}
}
struct CardView<Content: View>: View {
let content: Content
var body: some View {
content
.padding(16)
.background(.white)
.cornerRadius(12)
.shadow(radius: 4)
}
}
مثال 2: ارسال View به عنوان بسته از طریق @ViewBuilder. این الگو در کانتینرهای استاندارد SwiftUI (VStack، HStack، List) استفاده میشود و میتواند در کامپوننتهای سفارشی پیادهسازی شود.
struct CustomContainer<Content: View>: View {
@ViewBuilder let content: () -> Content
var body: some View {
VStack(alignment: .leading) {
content()
}
.padding(20)
}
}
مثال 3: تابع کارخانهای که some View بازمیگرداند. امکان ایجاد View بر اساس پارامترها بدون افشای پیادهسازی را فراهم میکند. این به ویژه برای کتابخانهها و کامپوننتهای قابل استفاده مجدد مفید است.
func makeIcon(for status: Status) -> some View {
switch status {
case .success:
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
case .error:
Image(systemName: "xmark.circle.fill")
.foregroundColor(.red)
case .pending:
ProgressView()
}
}
پرسشهای متداول
some View — یک نوع ناپیدا (opaque type) است، به این معنی که یک نوع مشخص خاص مطابق با پروتکل View بازگردانده میشود. نوع مشخص توسط کامپایلر تعیین میشود، اما از کد فراخوان پنهان است. این امر تایپگیری دقیق را بدون افشای جزئیات پیادهسازی تضمین میکند.
some View در مرحله کامپایل با سربار صفر تعیین میشود. AnyView از پاک کردن نوع (type erasure) در زمان اجرا با هزینه اضافی بستهبندی در existential container استفاده میکند. تا حد امکان از some View استفاده کنید، AnyView — فقط برای تغییر پویای نوع.
نوع ناپیدا برای تمام مسیرهای بازگشت به یک نوع مشخص واحد نیاز دارد. if/else با انواع مختلف این الزام را نقض میکند. @ViewBuilder با پیچیدن شاخهها در ConditionalContent مشکل را حل میکند — یک نوع واحد که تفاوتهای پیادهسازیهای مشخص را پنهان میکند.
some View عملکرد را کاهش نمیدهد — کامپایلر نوع دقیق را میداند و کد مستقیم تولید میکند. برعکس، any View (به عنوان پروتکل) نیاز به ارسال پویا دارد. some View — یک مکانیزم بهینهسازی است که در طراحی SwiftUI تعبیه شده است.
بله، some — یک ساختار عمومی Swift 5.1 است که به SwiftUI محدود نیست. میتوان آن را با هر پروتکلی استفاده کرد: some Equatable، some Codable، some Collection. این برای پنهان کردن انواع تودرتوی پیچیده مانند [String: [Int]] مفید است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید