دمج iOS SDK
SDK أصلي لـ iOS لتتبع الأحداث وإدارة جلسات المستخدمين وإرسال إشعارات Push وعرض رسائل In-app.
الخصائص
- خفيف: أثر محدود.
- سريع: محسّن للأداء.
- دعم عدم الاتصال: طابور أحداث مبني على SQLite.
- إعادة محاولة تلقائية: تراجع أسي عند الإخفاق.
- دفعات: تجميع فعال للأحداث، 50 حدثًا/5 ثوانٍ.
- رسائل In-app: رسائل أصلية و‑HTML، 5 أنواع، مع حدود تكرار.
- إشعارات Push: Apple Push Notification Service، APNS.
- الخصوصية أولًا: متوافق مع GDPR ويحترم موافقة المستخدم.
- iOS 14+: دعم إصدارات iOS الحديثة.
المتطلبات
- iOS 14.0+.
- Xcode 15.0+.
- Swift 5.9+.
التثبيت
Swift Package Manager
يوزع SDK كحزمة Swift. أضف التالي إلى Package.swift:
dependencies: [
.package(url: "https://github.com/joryio/joryio-ios.git", from: "1.0.0")
]
توفّر الحزمة منتجين (products). اربط ما يحتاجه تطبيقك:
.target(
name: "YourApp",
dependencies: [
.product(name: "Joryio", package: "joryio-ios"), // التتبع والهوية والإشعارات
.product(name: "JoryioUI", package: "joryio-ios"), // + عرض الرسائل داخل التطبيق
]
)
CocoaPods
pod 'Joryio/UI' # كل شيء، بما في ذلك العرض داخل التطبيق
# pod 'Joryio' # التتبع والهوية والإشعارات فقط - دون ربط WebKit
يعتمد JoryioUI على Joryio، لذا فإن ربط منتج الواجهة يمنحك كليهما.
خذ Joryio وحده إذا كان تطبيقك لا يحتاج الرسائل داخل التطبيق، أو إذا اعترضت
مراجعة أمنية على ربط web view. لاحظ أن الإعداد الافتراضي في CocoaPods هو
Joryio الخالي من الواجهة، فلن يعرض pod 'Joryio' رسائل داخل التطبيق حتى
تبدّله بـ pod 'Joryio/UI'.
أو في Xcode:
- File ← Add Package Dependencies.
- أدخل:
https://github.com/joryio/joryio-ios.git. - اختر الإصدار وأضفه إلى target.
بدء سريع
1. هيئ SDK
في AppDelegate.swift:
import Joryio
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize with your SDK key
Joryio.shared.initialize(
sdkKey: "jry_sdk_ios_YOUR_SDK_KEY",
apiHost: "api-eu1.joryio.com"
)
return true
}
اعثر على مفتاح SDK في لوحة Joryio تحت Settings ← Apps ← [Your App] ← SDK Keys.
2. تتبع الأحداث
// Basic event
Joryio.shared.track("Button Tapped")
// Event with properties
Joryio.shared.track("Product Viewed", properties: [
"product_id": "abc123",
"product_name": "Wireless Headphones",
"price": 99.99,
"category": "Electronics"
])
// Screen view
Joryio.shared.trackScreen("ProductDetail", properties: [
"product_id": "abc123"
])
3. تعريف المستخدمين
// Identify a user
Joryio.shared.identify("user-123")
Joryio.shared.setAttributes([
"email": "user@example.com",
"name": "John Doe",
"plan": "premium"
])
// Set attributes later
Joryio.shared.setAttribute("last_purchase", value: Date())
Joryio.shared.incrementAttribute("lifetime_value", by: 99.99)
// On logout
Joryio.shared.reset()
بيانات الجلسة التلقائية
يثري iOS SDK أحداث Session Start ببيانات الجهاز والبيئة:
$device_id$platform، ios$model$os_name/$os_version$app_version/$build_number$bundle_id$screen_width/$screen_height$locale$language/$languages$timezonecountry، ISO-3166-1 alpha-2، مشتقة من IP عند بدء الجلسة.
إشعارات Push
فعّل إشعارات Push لإرسال رسائل مستهدفة عبر Apple Push Notification Service، APNS.
1. الإعداد في AppDelegate
import Joryio
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize SDK
Joryio.shared.initialize(
sdkKey: "jry_sdk_ios_YOUR_KEY",
apiHost: "api-eu1.joryio.com"
)
// Request push permissions
Task {
let granted = await Joryio.shared.requestPushPermissions()
if granted {
print("Push notifications enabled")
}
}
return true
}
// Handle device token registration
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
Joryio.shared.didRegisterForRemoteNotifications(deviceToken: deviceToken)
}
// Handle registration failure
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
Joryio.shared.didFailToRegisterForRemoteNotifications(error: error)
}
// Handle received push notification
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
Joryio.shared.didReceiveRemoteNotification(userInfo, completionHandler: completionHandler)
}
}
2. ضبط APNS في لوحة التحكم
لإرسال إشعارات Push، اضبط بيانات APNS:
- انتقل إلى Settings ← Apps ← [Your App].
- انتقل إلى تبويب Push Notifications.
- ارفع شهادة APNS،
.p12، أو مفتاح مصادقة،.p8. - أدخل Team ID وKey ID، لـ
.p8. - اختر البيئة، Development/Production.
3. خصائص Push
// Check if push is enabled
let isEnabled = await Joryio.shared.isPushEnabled()
// Get device token
if let token = Joryio.shared.getDeviceToken() {
print("Device token: \(token)")
}
// Badge management
Joryio.shared.updateBadgeCount(5)
Joryio.shared.clearBadge()
// Unregister from push
Joryio.shared.unregisterFromPushNotifications()
رسائل In-app
اعرض رسائل In-app مستهدفة للمستخدمين وفق سلوكهم وسماتهم.
رموز التسليم (delivery tokens)
عندما يقدّم الخادم حملة مؤهّلة، فإنه يصدر لها أيضًا رمز تسليم موقّعًا وقصير الأجل. يعيد الـ SDK إرسال هذا الرمز عند الإبلاغ عن ظهور أو نقرة أو إغلاق، ويتحقق الخادم من التوقيع قبل تسجيل أي شيء.
لا يتعيّن عليك فعل أي شيء - الـ SDK يتولى ذلك نيابة عنك. وهو موثّق هنا لأنه يغيّر ما يحدث للعميل الذي لا يرسل رمزًا:
POST /v1/in-app/track (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }
الرمز هو ما يجعل الظهور جديرًا بالثقة: بدونه، يستطيع أي شخص يملك مفتاح الـ SDK - وهو مضمّن في كل تطبيق وكل صفحة - الإبلاغ عن ظهورات ونقرات لحملة لم تُعرض قط، وستحتسبها تقاريرك.
إذا توقّف تسجيل ظهورات in-app، فتأكد من أن التطبيق مبني على إصدار حديث من الـ SDK: الإصدار الذي بُني قبل وجود رموز التسليم لا يرسل رمزًا، وسيرفض الخادم ظهوراته.
مزامنة الحملات التلقائية
يزامن SDK الحملات تلقائيًا من الخادم عندما:
- يدخل التطبيق المقدمة.
- يتلقى إشعار Push.
تُحدد المزامنات بحد أقصى مرة واحدة لكل فاصل مزامنة. استدعِ syncInAppCampaigns() للمزامنة في لحظات أخرى، مثل بعد تعريف مستخدم أو تحديث سمات.
التحكم اليدوي بالحملة
// Manually sync campaigns from server
await Joryio.shared.syncInAppCampaigns()
// Manually trigger campaign evaluation
await Joryio.shared.evaluateInAppCampaigns()
// Reset displayed campaigns (for testing)
Joryio.shared.resetDisplayedCampaigns()
نوعان من المحتوى
تصل كل حملة بأحد شكلين من المحتوى، وتعرض حزمة SDK كلًا منهما بطريقة مختلفة:
| المحتوى | ما هو | كيف يُعرض |
|---|---|---|
| الأصلي (Native) | بيانات منظمة - عنوان ونص وصورة وأزرار | عناصر UIKit حقيقية، بلون التمييز و‑Dynamic Type والوضع الداكن و‑VoiceOver في تطبيقك. بدون web view. |
| HTML | HTML و‑CSS و‑JavaScript كتبها المؤلف | WKWebView داخل الرسالة. |
السماح برسائل HTML
رسائل HTML معطلة افتراضيًا. تُنفذ رسالة HTML شيفرة JavaScript كتبها المؤلف داخل تطبيقك، لذا فإن تفعيلها قرار يتخذه فريق التطبيق - وليس شيئًا يُفعَّل من منصة تسويق:
let config = JoryioConfig(
inApp: InAppConfig(allowHtmlJsInAppMessages: true) // الافتراضي: false
)
Joryio.shared.initialize(
sdkKey: "jry_sdk_ios_YOUR_KEY",
apiHost: "api-eu1.joryio.com",
config: config
)
تركه معطلًا لا يعطل الرسائل داخل التطبيق. تستمر الرسائل الأصلية في الظهور، لأنها بيانات ترسمها عناصر الحزمة نفسها - دون أي مفسّر. تُتجاهل حملات HTML وتُسجَّل.
إذا كانت سياسة الأمان لديك تمنع تنفيذ HTML مكتوب خارجيًا داخل العملية، فاترك الإعداد معطلًا وأنشئ حملاتك كرسائل أصلية.
لا يحتوي tvOS على web view إطلاقًا، لذا لا تُعرض رسائل HTML هناك أبدًا بغض النظر عن هذا الإعداد. أنشئ رسائل أصلية لأهداف tvOS.
عرض الرسائل بنفسك
تتولى الحزمة رسم الرسائل الأصلية نيابةً عنك، لكن يمكنك تولّي الأمر بالكامل - وهو
المنفذ نفسه الذي يسميه غيرنا custom view factory. نفِّذ InAppMessagePresenter
ثم عيّنه:
Joryio.shared.inAppPresenter = MyPresenter()
لا يعمل الاكتشاف التلقائي إلا حين يكون inAppPresenter بقيمة nil، فيحل عارضك
محل المُصيِّر المدمج بدل أن يتنازع معه.
يستقبل العارض الحملة وcontent جاهز بالفعل: يُصيَّر Liquid في الخادم وتصل الحقول
الأصلية نصًا عاديًا. سجِّل ما تعرضه عبر
Joryio.shared.trackInAppImpression(campaignId, action:).
لا تضبط هذا بنفسك، لكن معرفته مفيدة حين لا يظهر شيء. تعثر الحزمة على مُصيِّرها المدمج بالاسم وقت التشغيل، والوحدة التي يوجد فيها ذلك الاسم تعتمد على طريقة الدمج:
| طريقة التحزيم | الصنف الذي تبحث عنه الحزمة | |
|---|---|---|
| SwiftPM | JoryioUI هدف مستقل | JoryioUI.DefaultInAppMessagePresenter |
| CocoaPods | Joryio/UI هو subspec، والـ subspecs تتشارك وحدة الـ pod | Joryio.DefaultInAppMessagePresenter |
يُجرَّب الاسمان معًا. وإذا لم تظهر أي رسالة داخل التطبيق فابحث عن تحذير الحزمة
"No in-app presenter found"، فهو يعني أن منتج الواجهة غير مرتبط
(pod 'Joryio/UI' أو منتج JoryioUI في SwiftPM)، لا أن الحملة لم تصل. والحالتان
تبدوان متطابقتين من الخارج.
أما عارضك الخاص فلا يحتاج شيئًا من ذلك: عيّنه ولن يعمل الاكتشاف أصلًا.
أنواع الرسائل
تدعم الحزمة 5 أنواع من الرسائل:
- Modal - وسط الشاشة مع خلفية معتمة
- Banner - أعلى الشاشة
- Slide-Up - إشعار صغير من الأسفل
- Full-Screen - رسالة تملأ الشاشة
- Custom - التطبيق يحدد الموضع
حدود التكرار
تحترم الرسائل قواعد التواصل على مستوى مساحة العمل وحدود التكرار على مستوى الحملة:
- الحد الأقصى لمرات الظهور لكل نافذة زمنية.
- أقل تأخير بين الرسائل.
- حدود تكرار لكل حملة.
خيارات الإعداد
خصص سلوك SDK باستخدام JoryioConfig:
JoryioConfig(
// User Identification
userId: String?, // Initialize with known user ID
anonymousId: String?, // Custom anonymous ID
// Batching & Performance
batchSize: Int, // Default: 50
flushInterval: TimeInterval, // Default: 5.0 seconds
sendImmediately: Bool, // Default: false
maxQueueSize: Int, // Default: 1000
// Session Management
sessionTimeout: TimeInterval, // Default: 1800 (30 minutes)
trackSessionStart: Bool, // Default: true
// Storage
persistQueue: Bool, // Default: true
// Network & Retry
maxRetries: Int, // Default: 3
retryBackoffMs: Double, // Default: 1000.0
requestTimeout: TimeInterval, // Default: 10.0
// Privacy & GDPR
respectDoNotTrack: Bool, // Default: true
optOut: Bool, // Default: false
trackingConsent: TrackingConsent, // Default: .granted
// Debugging
enableDebug: Bool, // Default: false
logLevel: LogLevel // Default: .error
)
الخصائص المتقدمة
إدارة سمات المستخدم
// Set multiple attributes
Joryio.shared.setAttributes([
"age": 28,
"city": "San Francisco",
"premium": true
])
// Set single attribute
Joryio.shared.setAttribute("language", value: "en")
// Increment numeric attribute
Joryio.shared.incrementAttribute("page_views", by: 1)
Joryio.shared.incrementAttribute("total_spent", by: 29.99)
// Remove attribute
Joryio.shared.unsetAttribute("temporary_flag")
عناصر تحكم الخصوصية
// Stop collecting. PERSISTED - survives an app restart.
Joryio.shared.optOut()
// Opt back in
Joryio.shared.optIn()
// Check opt-out status
if Joryio.shared.isUserOptedOut() {
print("User has opted out")
}
// Delete everything the SDK stored on this device.
// SEPARATE from optOut(): "stop collecting" and "delete what you have" are
// different requests. This is the one an erasure request needs. It does NOT
// opt the user out - call optOut() as well if that is also intended.
Joryio.shared.wipeData()
// Get identity info
let (userId, anonymousId) = Joryio.shared.getIdentity()
print("User: \(userId ?? "anonymous"), Anonymous ID: \(anonymousId)")
What optOut() does, precisely:
| stops collection | yes - track, identify and setAttributes all become no-ops |
| survives a restart | yes - the flag is stored on the device and read before anything is collected |
| drops what is already queued | yes - queued events and un-acked attribute writes are discarded, not delivered later |
| tells the server | yes - one final $tracking_opted_out profile attribute, best-effort, sent while sending is still permitted |
| deletes stored data | no - use wipeData() |
The $tracking_opted_out attribute is a record, not enforcement: it lands on
the profile so campaigns can exclude on it. Server-side suppression is a
separate setting.
تم التحديث بالإنجليزية - الترجمة قيد الإعداد.
تفريغ الطابور يدويًا
// Flush immediately (e.g., before app termination)
Joryio.shared.flush()
// Check queue size
let queueSize = Joryio.shared.getQueueSize()
print("Pending events: \(queueSize)")
أفضل الممارسات
1. تتبع أحداثًا ذات معنى
ركز على الأحداث التي تهم نشاطك:
// Good: Specific, actionable events
Joryio.shared.track("Trial Started", properties: ["plan": "premium"])
Joryio.shared.track("Feature Used", properties: ["feature": "export"])
// Avoid: Overly generic events
Joryio.shared.track("Button Tapped") // Too generic
2. تعامل مع دورة حياة المستخدم
// On login
func handleLogin(userId: String, userInfo: UserInfo) {
Joryio.shared.identify(userId)
Joryio.shared.setAttributes([
"email": userInfo.email,
"name": userInfo.name
])
}
// On logout
func handleLogout() {
Joryio.shared.reset()
}
// On signup
func handleSignup(userId: String, userInfo: UserInfo) {
Joryio.shared.alias(userId)
Joryio.shared.identify(userId)
Joryio.shared.setAttributes(userInfo.attributes)
}
3. فرّغ في الأحداث الحرجة
override func applicationWillTerminate(_ application: UIApplication) {
Joryio.shared.flush()
}
استكشاف الأخطاء وإصلاحها
الأحداث لا تظهر
- تأكد من صحة مفتاح SDK:
jry_sdk_ios_*. - فعّل سجل التصحيح:
enableDebug: true. - افحص سجلات console بحثًا عن الأخطاء.
- تحقق من اتصال الشبكة.
- استدعِ
flush()للإرسال فورًا.
Push لا يعمل
- تحقق من رفع شهادة APNS في لوحة التحكم.
- تحقق من تسجيل رمز الجهاز.
- تأكد من entitlements الصحيحة في Xcode.
- اختبر في البيئة الصحيحة، dev مقابل production.
أخطاء البناء
- تأكد من هدف نشر iOS 14.0+.
- نظف مجلد البناء: Cmd+Shift+K.
- حدّث تبعيات Swift Package.
تتبع E-Commerce
يتضمن iOS SDK متتبعًا مدمجًا للتجارة الإلكترونية لأحداث المنتجات والسلة وإتمام الشراء والطلبات. راجع دليل تتبع E-Commerce العابر لـ SDKs لمعرفة API الكاملة مع أمثلة Swift.
الخطوات التالية
- Android SDK: دمج Android SDK.
- دليل إشعارات Push: تعرّف إلى حملات Push.
- دليل حملات In-app: إنشاء رسائل داخل التطبيق.
- Custom Events: تتبع الأحداث المخصصة.
- E-Commerce API: دمج التجارة الإلكترونية من الخادم.
Attribute delivery
setAttributes is durable. A write is queued until the server acknowledges it,
so an attribute set while the device is offline is delivered when connectivity
returns rather than dropped.
- Retried on the next
setAttributes, on foreground, and before an in-app sync. - Batched - a burst of calls becomes one request (800ms window). A single write still goes out promptly.
- Persisted in the platform's encrypted store (iOS Keychain, Android
EncryptedSharedPreferences), so a write survives the process being killed.
Cleared the moment the server acks, and purged by
optOut()andwipeData().
Attributes are also used for in-app targeting. The server profile is authoritative: only writes the server has not yet acknowledged can override it, which is what keeps two devices belonging to the same contact from disagreeing about who that contact is.
تم التحديث بالإنجليزية - الترجمة قيد الإعداد.
Push registration and consent
The SDK registers for remote notifications whether or not the user grants the notification permission. On iOS the two are separate: registering yields a device token without consent, and that token can only ever deliver background (silent) pushes - it cannot display anything the user has not authorised.
This is what lets an in-app message reach a user who declined notifications, and it means a token already exists if they later enable notifications in Settings. Airship and OneSignal behave the same way. Disclose it in your privacy policy.
In-App Messaging
Display targeted in-app messages to users based on their behavior and attributes.
تم التحديث بالإنجليزية - الترجمة قيد الإعداد.