Swift: الإدخال والإخراج والتصحيح في Swift
كتابة الكود نصف المهمة ��قط. يعلمك هذا الدرس كيفية استخدام print والتأكيدات ونقاط التوقف لإيجاد وإصلاح الأخطاء في كودك.
1. ما ستتعلمه
- وسائط print المتقدمة: separator وterminator
- الفرق بين debugPrint وdump ومتى تستخدم كل منهما
- تأكيدات assert وفحوص precondition
- تنسيق السلاسل والإخراج المحاذي
- المعاينة المباشرة في Playground وأساسيات تصحيح نقاط التوقف
2. قصة حقيقية: مبتدئ تعلم ذاتيًا
(1) نقطة الألم: كود صحيح منطقيًا، نتائج خاطئة
Mike هو مبتدئ في Swift يتعلم ذاتيًا يعمل على برنامج لحساب متوسط الدرجات:
let scores = [85, 92, 78, 90, 88]
var total = 0
for i in 0...5 {
total += scores[i]
}
let average = total / 5
print("Average: \(average)")
تعطل البرنامج فورًا. لم يستطع Mike معرفة السبب. قضى 30 دقيقة في البحث عبر الإنترنت، مجربًا حلولاً مختلفة. كل تعديل يتطلب إعادة التشغيل، لكنه ما زال لا يستطيع تحديد المشكلة.
(2) الحل: التصحيح باستخدام print
اقترح صديق استخدام print لإخراج المتغيرات الوسيطة لتحديد المشكلة:
let scores = [85, 92, 78, 90, 88]
print("Array length: \(scores.count)") // Output: 5
var total = 0
for i in 0..<scores.count { // Use count instead of hardcoding
print("Processing index \(i): value = \(scores[i])")
total += scores[i]
}
print("Total: \(total)")
let average = total / 5
print("Average: \(average)")
المخرج��ت:
TEXT 📖 للعرض فقطArray length: 5 Processing index 0: value = 85 Processing index 1: value = 92 Processing index 2: value = 78 Processing index 3: value = 90 Processing index 4: value = 88 Total: 433 Average: 86
بإخراج المتغيرات الوسيطة، اكتشف Mike فورًا أن فهارس المصفوفة هي 0-4، لكنه استخدم 0...5 (الذي يتضمن الفهرس 5).
(3) النتيجة: كفاءة التصحيح بعد إتقان print
| البُعد | قبل (التخمين) | بعد (تصحيح print) |
|---|---|---|
| وقت تحديد المشكلة | 30+ دقيقة | 1-2 دقيقة |
| دقة الإصلاح | 50% (غالبًا خاطئ) | 95% |
| فهم الكود | "لا أعرف لماذا تعطل" | "أعرف الحالة في كل خطوة" |
| الثقة في حل المشكلات الجديدة | 3/10 | 8/10 |
3. استخدام print المتقدم
print هي أداة التصحيح الأكثر استخدامًا في Swift، لكن وسائطها تفعل أكثر من مجرد إخراج قيمة.
graph TB
A[دالة print] --> B["items: القيم المراد إخراجها"]
A --> C["separator: المحدد"]
A --> D["terminator: حرف الن��اية"]
A --> E["to: هدف الإخراج"]
B --> F[قيم متعددة مفصولة بفواصل]
C --> G["الافتراضي: مسافة"]
C --> H["مخصص: | أو ، إلخ"]
D --> I["الافتراضي: سطر جديد"]
D --> J["مخصص: سلسلة فارغة"]
| الوسيط | النوع | الافتراضي | ��لغرض |
|---|---|---|---|
items |
Any... |
مطلوب | المحتوى المراد إخراجه |
separator |
String |
" " |
المحدد بين العناصر المتعددة |
terminator |
String |
"\n" |
حرف السطر الجديد النهائي |
to |
TextOutputStream |
nil |
هدف الإخراج (الافتراضي: وحدة التحكم) |
(1) separator وterminator
// Default: space-separated, newline-terminated
print("Hello", "Swift", "World")
// Hello Swift World
// Custom separator
print("Hello", "Swift", "World", separator: ", ")
// Hello, Swift, World
// Custom terminator (no newline)
print("Loading", terminator: "...")
print("Done")
// Loading...Done
// Combined usage
print("A", "B", "C", separator: " | ", terminator: ".\n")
// A | B | C.
(2) debugPrint وdump
debugPrint يخرج معلومات التصحيح (مع علامات اقتباس ومعلومات النوع)؛ dump يخرج الهياكل التفصيلية:
let name = "Alice"
let numbers = [1, 2, 3]
print(name) // Alice
debugPrint(name) // "Alice"
dump(name) // - "Alice"
print(numbers) // [1, 2, 3]
debugPrint(numbers) // [1, 2, 3]
dump(numbers)
// ▿ 3 elements
// - 0 : 1
// - 1 : 2
// - 2 : 3
| الدالة | الغرض | إخراج السلسلة | إخراج المصفوفة |
|---|---|---|---|
print |
إخراج عادي | Alice | [1, 2, 3] |
debugPrint |
إخراج تصحيح (يظهر معلومات النوع) | "Alice" | [1, 2, 3] |
dump |
إخراج هيكل تفصيلي | - "Alice" | عنصر واحد في كل سطر |
▶ مثال: إخراج سجل منسق
// ============================================
// Simulate system log output
// Demonstrates advanced print parameters and debugPrint
// ============================================
let event = "USER_LOGIN"
let user = "Alice"
let statusCode = 200
let duration = 0.045
// 1. Use separator to format the log
print("[\(event)]", user, "Status: \(statusCode)", separator: " | ", terminator: "")
print(" (\(duration)s)")
// [USER_LOGIN] | Alice | Status: 200 (0.045s)
// 2. Output tabular data
print()
print("=== Report ===")
print("Item", "Price", "Qty", separator: " | ")
print("-----", "-----", "---", separator: " | ")
print("Book", "12.99", "3", separator: " | ")
print("Pen", "1.50", "10", separator: " | ")
print("Bag", "49.99", "1", separator: " | ")
// 3. debugPrint for development debugging
let input: String? = "test"
debugPrint("Debug: input = \(input)")
// "Debug: input = Optional(\"test\")"
المخرجات:
TEXT 📖 للعرض فقط[تسجيل_دخول_مستخدم] | Alice | الحالة: 200 (0.045ث) === تقرير === العنصر | السعر | الكمية ----- | ----- | --- كتاب | 12.99 | 3 قلم | 1.50 | 10 حقيبة | 49.99 | 1 تصحيح: input = Optional("اختبار")
4. التأكيدات والشروط المسبقة
التأكيدات والشروط المسبقة هي أدوات البرمجة الدفاعية المضمنة في Swift، تلتقط أخطاء المنطق مبكرًا أثناء التطوير.
graph LR
A[فحوص وق�� التشغيل] --> B[assert]
A --> C[precondition]
B --> D[وضع التصحيح فقط]
B --> E[التقاط المشكلات أثناء التطوير]
C --> F[التصحيح + الإصدار]
C --> G[أخطاء غير قابلة للاسترداد]
| الدالة | نشطة في | ا��غرض | مثال |
|---|---|---|---|
assert |
التصحيح فقط | فحوص الاتساق الداخلي أثناء التطوير | assert(age > 0) |
assertionFailure |
التصحيح فقط | تشغيل تأكيد غير مشروط | assertionFailure("لا يجب الوصول هنا") |
precondition |
جميع الأوضاع | فحوص الشروط المسبقة | precondition(!name.isEmpty) |
preconditionFailure |
جميع الأوضاع | إنهاء غير مشروط | preconditionFailure("خطأ فادح") |
(1) تأكيدات assert للتصحيح
تأخذ assert مفعولها فقط في وضع التصحيح؛ تُزال في وضع الإصدار، لذا لا يوجد تأثير على الأداء:
func calculateDiscount(price: Double, percent: Double) -> Double {
assert(price > 0, "Price must be greater than 0")
assert(percent >= 0 && percent <= 100, "Discount must be between 0-100")
let discount = price * percent / 100.0
return price - discount
}
let finalPrice = calculateDiscount(price: 100.0, percent: 20)
print(finalPrice) // 80.0
// The following would trigger assertion failures in Debug mode:
// calculateDiscount(price: -10, percent: 20) // ❌ assert fails
// calculateDiscount(price: 100, percent: 150) // ❌ assert fails
(2) فحوص precondition
تأخذ precondition مفعولها في وضعي التصحيح والإصدار — تنهي البرنامج فورًا عندما لا يمكن استيفاء الشرط:
func sendEmail(to address: String, message: String) {
precondition(address.contains("@"), "Invalid email address: \(address)")
precondition(!message.isEmpty, "Message cannot be empty")
print("Sending email to \(address): \(message)")
}
sendEmail(to: "alice@example.com", message: "Hello!")
// Sending email to alice@example.com: Hello!
// The following would trigger precondition failures (all modes):
// sendEmail(to: "invalid", message: "Hi")
▶ مثال: التحقق من صحة الوسائط
// ============================================
// User registration parameter checks
// Demonstrates assert and precondition usage
// ============================================
import Foundation
func registerUser(name: String, age: Int, email: String) {
// precondition: Public API contract (active in all modes)
precondition(name.count >= 2, "Username must be at least 2 characters")
precondition(age >= 18, "User must be at least 18 years old")
precondition(email.contains("@"), "Invalid email format")
// assert: Internal logic check (Debug only)
let emailParts = email.split(separator: "@")
assert(emailParts.count == 2, "Email should contain exactly one @ symbol")
let domain = String(emailParts[1])
assert(domain.contains("."), "Email domain is invalid")
// Actual registration logic
print("Registration successful: \(name), age \(age)")
print("Confirmation email sent to: \(email)")
}
// Valid call
registerUser(name: "Alice", age: 28, email: "alice@example.com")
print("---")
// Calls that would trigger precondition failures (commented out to avoid crashing)
// registerUser(name: "A", age: 20, email: "test@test.com")
المخرجات:
TEXT 📖 للعرض فقطRegistration successful: Alice, age 28 Confirmation email sent to: alice@example.com ---
5. تصحيح Playground
تقدم Playgrounds قدرات تصحيح أقوى من print، بما في ذلك المعاينات المباشرة ونقاط التوقف.
(1) المعاينة المباشرة في Playground
يعرض الشريط الجانبي في Playgrounds نتيجة كل سطر في الوقت الفعلي:
// Run in Playground — the sidebar shows each step's result
let name = "Alice" // "Alice"
var score = 0 // 0
score += 85 // 85
score += 92 // 177
let average = score / 2 // 88
graph TB
A[تصحيح Playground] --> B[لوحة ال��تائج المباشرة]
A --> C[تصحيح نقاط التوقف]
A --> D[سجل القيم]
B --> E[النتائج معروضة لكل س��ر]
C --> F[إيقاف / خطوة / متابعة]
D --> G[منحنيات تغير قيم المتغيرات]
| الميزة | كيفية الاستخدام | الغرض |
|---|---|---|
| المعاينة المباشرة | تحرير الكود يعرض النتائج تلقائيًا على اليمين | رؤية نتيجة كل خطوة بسرعة |
| نقاط التوقف | انقر رقم السطر لإضافة نقطة توقف | إيقاف التنفيذ، التتبع خطوة بخطوة |
| سجل القيم | حرك فوق متغير | رؤية كيف يتغير المتغير مع الوقت |
| معاينة التعبير | حدد الكود | تقييم سريع لتعبير محدد |
(2) تصحيح نقاط التوقف
انقر على الجانب الأيسر من رقم السطر لتعيين نقطة توقف. عندما يصل البرنامج إلى ذلك السطر، ��توقف مؤقتًا حتى تتمكن من فحص جميع قيم المتغيرات الحالية:
func calculateTotal(items: [Double], tax: Double) -> Double {
var subtotal = 0.0
// Set a breakpoint on this line
for item in items {
subtotal += item
}
let taxAmount = subtotal * tax
let total = subtotal + taxAmount
return total
}
let cart = [29.99, 49.99, 15.00]
let final = calculateTotal(items: cart, tax: 0.08)
print("Total: $\(final)")
▶ مثال: تصحيح Playground عمليًا
// ============================================
// Practice Playground debugging techniques
// Paste this code into a Playground and run it
// ============================================
import Foundation
// 1. Set a breakpoint on the line below to observe variable values
let data: [String: Any] = [
"product": "Swift Book",
"price": 39.99,
"quantity": 3,
"inStock": true
]
// 2. Step through the following code
let productName = data["product"] as? String ?? "Unknown"
let price = data["price"] as? Double ?? 0.0
let quantity = data["quantity"] as? Int ?? 0
let inStock = data["inStock"] as? Bool ?? false
print("Product: \(productName)")
print("Price: $\(price)")
print("Quantity: \(quantity)")
print("In Stock: \(inStock)")
// 3. Observe the conditional check results
if inStock {
let totalCost = price * Double(quantity)
print("Total Cost: $\(totalCost)")
} else {
print("Item is out of stock")
}
// 4. Use dump to inspect complex data
print("\n=== Debug Info ===")
dump(data)
المخرجات:
TEXT 📖 للعرض فقطالمنتج: كتاب Swift السعر: $39.99 الكمية: 3 متوفر: true التكلفة الإجمالية: $119.97 === معلومات التصحيح === ▿ 4 أزواج مفتاح/قيمة ▿ (عنصران) - المفتاح: "المنتج" - القيمة: "كتاب Swift" ▿ (عنصران) - المفتاح: "السعر" - القيمة: 39.99 ▿ (عنصران) - المفتاح: "الكمية" - القيمة: 3 ▿ (عنصران) - المفتاح: "متوفر" - القيمة: true
6. مثال كامل: أداة تصحيح تحليل الدرجات
// ============================================
// Grade analysis tool
// Combines print debugging + assertion checks + formatted output
// ============================================
import Foundation
// 1. Student grade data
let studentName = "Alice"
let scores = [85.0, 92.0, 78.0, 90.0, 88.0]
// 2. Debug assertions to validate data
assert(scores.count > 0, "Score list cannot be empty")
for score in scores {
assert(score >= 0 && score <= 100, "Score must be between 0-100: \(score)")
}
// 3. Calculate statistics
var total = 0.0
var highest = scores[0]
var lowest = scores[0]
// Set a breakpoint here to observe the loop process
for (index, score) in scores.enumerated() {
print("[DEBUG] Index \(index): \(score)")
total += score
if score > highest { highest = score }
if score < lowest { lowest = score }
}
let average = total / Double(scores.count)
// 4. Formatted output
print(String(repeating: "=", count: 35))
print("Student: \(studentName)")
print(String(repeating: "-", count: 35))
print("Subject", "Score", "Grade", separator: " | ")
print(String(repeating: "-", count: 35))
for (index, score) in scores.enumerated() {
let grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "D"
print("Subject \(index + 1)", score, grade, separator: " | ")
}
print(String(repeating: "-", count: 35))
print("Total: \(total)")
print("Average: \(String(format: "%.1f", average))")
print("Highest: \(highest)")
print("Lowest: \(lowest)")
print(String(repeating: "=", count: 35))
// 5. Precondition to ensure reasonable results
precondition(average >= 0 && average <= 100, "Average is outside expected range")
precondition(highest >= lowest, "Highest score should not be lower than lowest")
المخرجات:
TEXT 📖 للعرض فقط[DEBUG] Index 0: 85.0 [DEBUG] Index 1: 92.0
❓ أسئلة شائعة
print يخرج بتنسيق قابل للقراءة البشرية (مثلاً Alice)، بينما debugPrint يخرج بتنسيق موجه للتصحيح (مثلاً "Alice" مع علامات اقتباس). يمكن للأنواع المخصصة تنفيذ بروتوكولي CustomStringConvertible وCustomDebugStringConvertible للتحكم في كلا الإخراجين.assert تأخذ مفعولها فقط في وضع التصحيح (-Onone). في وضع الإصدار، يتخطى المترجم تقييم وتنفيذ التأكيدات. لذلك، لا تضع أبدًا منطقًا ذا آثار جانبية داخل assert.precondition تأخذ مفعولها في جميع الأوضاع وهي للأخطاء الفادحة غير القابلة للاسترداد. أمثلة: فحص حدود المصفوفة مسبقًا، وسيط مطلوب يكون nil، فرع لا يجب الوصول إليه منطقيًا. استخدم precondition للتحقق من صحة وسائط API العامة.📖 ملخص
separatorفيprintيخصص المحدد بين ��لعناصر؛terminatorيتحكم في حرف النهايةdebugPrintيظهر الإخراج مع معلومات النوع؛dumpيظهر الهياكل التفصيليةassertيفحص اتساق المنطق الداخلي في وضع التصحيحpreconditionيفحص الشروط المسبقة غير القابلة للاسترداد في جميع الأوضاع- الشريط الجانبي لـ Playground يعرض نتيجة كل سطر في الوقت الفعلي
- تصحيح نقاط التوقف يتيح لك تتبع الكود سطرًا بسطر ومراقبة تغيرات المتغيرات
📝 تمارين
- مبتدئ: استخدم
printلتوليد جدول ضرب بسيط (1-3)، متحكمًا ��ي التنسيق بـseparatorوterminatorلإخراج نمط جدولي. - متوسط: اكتب دالة
divide(_ a: Double, by b: Double) -> Double، مستخدمًاpreconditionللتحقق من أن المقسوم عليه ليس 0 وassertللتحقق من أن النتيجة في نطاق معقول. ثم استخدمprintمع التنسيق لإخراج النتيجة. - متقدم: حاكِ برنامج سحب من الصراف الآلي. استخدم
assertللتحقق من أن مبلغ السحب من مضاعفات 100، وpreconditionللتحقق من كفاية الرصيد. استخدمprintمعseparatorوterminatorلتنسيق تفاصيل المعاملة بما في ذلك الوقت والمبلغ والرصيد.