التوثيق · التكامل مع iOS

4. تهيئة SDK

4.1 AppDelegate

أنشئ فئة فرعية من AKSBaseAppDelegate — وهي فئة أساسية تعتمد نمط أسلوب القالب (Template Method)، وتتولى إعداد Firebase وتهيئة AdMob وتهيئة SDK والإعداد الأولي للتهيئة عن بُعد، مع مجموعة من نقاط التخصيص المعلنة بـ open لربط منطق تطبيقك بها.

swift
import UIKit
import AppsKit
import AKSKit

class AppDelegate: AKSBaseAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        AppsKitSDKAdsManagerIOS.instance.initialize(userHasGivenConsent: true, isCCPAConsent: true)

        // super.application(...) does, in order:
        //   FirebaseApp.configure() -> MobileAds.shared.start() -> setupAdBridge()
        //   -> AppsKitSDK init/setDevMode/setTestMode/saveDefaultConfigJson
        //   -> configureSDK() -> manageRemoteConfigs(...)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    /// Called by the base class during the sequence above. Optional — only needed if your
    /// app has its own ad-loading abstraction layer that needs to be wired up to AKS. If you
    /// call `AppsKitSDKAdsManagerIOS.instance` directly from your own Swift code (§5), leave
    /// this empty.
    override func setupAdBridge() {}

    override func isDevMode() -> Bool { return false } // wire to your own debug flag
    override func isTestMode() -> Bool { return false }

    override func defaultConfigJson() -> String? {
        return "<encrypted-config-json-from-aks-portal>"
    }

    /// Called after dev/test mode flags are applied — use this for anything that needs
    /// to happen after the base setup but before remote config is ready (e.g. initializing
    /// AppLovin MAX directly).
    override func configureSDK() {}

    override func onConfigsReadyToUse() {
        // optional — fires once Firebase Remote Config has been fetched/activated
    }
}

القائمة الكاملة لنقاط التخصيص المعلنة بـ open في AKSBaseAppDelegate: ‏setupAdBridge()، ‏isDevMode() -> Bool (القيمة الافتراضية false)، ‏isTestMode() -> Bool (القيمة الافتراضية false)، ‏defaultConfigJson() -> String? (القيمة الافتراضية nil)، ‏timeToFetchRemoteConfigInSeconds() -> Int (القيمة الافتراضية 3600)، ‏onConfigsReadyToUse()، ‏configureSDK().

4.2 التهيئة دون الوراثة من AKSBaseAppDelegate

الوراثة من AKSBaseAppDelegate ليست إلزامية — فهي فئة تغليف لتسهيل التكامل. إذا كان تطبيقك يستخدم بالفعل UIApplicationDelegate خاصًا به ولا يمكنك الاستغناء عنه، فاستدعِ أساليب AppsKitSDK/AKSKit نفسها التي تستدعيها هذه الفئة داخليًا، مباشرةً من application(_:didFinishLaunchingWithOptions:) في تطبيقك:

swift
import UIKit
import AppsKit   // for makeDefaultIOSAdHandler() — see below
import AKSKit
import FirebaseCore
import FirebaseRemoteConfig
import GoogleMobileAds

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        FirebaseApp.configure()
        MobileAds.shared.start()

        // If your app has its own ad-loading abstraction layer to wire up to AKS, do it
        // around here — most apps don't need this; see §5 to call ad formats directly.

        let sdkInstance = AppsKitSDK.shared
        sdkInstance.setIOSAdHandler(iosAdHandler: makeDefaultIOSAdHandler())
        sdkInstance.initialize(
            context: PlatformContext(delegate: nil),
            application: PlatformApplication(application: UIApplication.shared)
        )

        sdkInstance.setDevMode(value: false) // wire to your own debug flag
        sdkInstance.setTestMode(value: false)
        sdkInstance.setTimeToFetchRemoteConfigInSeconds(intervalToFetchRemoteConfigInSeconds: 3600) // optional, defaults to 3600

        sdkInstance.saveDefaultConfigJson(encryptedJson: "<encrypted-config-json-from-aks-portal>")
        sdkInstance.manageRemoteConfigs(onRemoteConfigReady: RemoteConfigReadyBridge {
            // optional — your own hook into the fetched remote config
        })

        AdsManager.shared.setAppExitTime(appExitTime: 0)
        AKSUtils.shared.cleanCachedData()

        return true
    }
}

// manageRemoteConfigs(onRemoteConfigReady:) takes an OnRemoteConfigAvailable, not a closure —
// AKSBaseAppDelegate has a private adapter for this internally; outside it, write your own:
private class RemoteConfigReadyBridge: NSObject, OnRemoteConfigAvailable {
    private let onReady: () -> Void
    init(_ onReady: @escaping () -> Void) { self.onReady = onReady }
    func onRemoteConfigReadyToFetch(remoteConfig: RemoteConfig) {
        DispatchQueue.main.async { self.onReady() }
    }
}

يتطلب setIOSAdHandler(iosAdHandler:) تنفيذًا فعليًا لـ IOSPlatformAdHandler — والتنفيذ الذي تستخدمه AKSBaseAppDelegate داخليًا (لتهيئة وساطة AdMob/IronSource/MAX وإرسال التقارير إلى AppsFlyer/Adjust) هو نوع داخلي في SDK، لذا أُتيح لهذه الحالة تحديدًا عبر makeDefaultIOSAdHandler().

ما تفقده عند عدم الوراثة من AKSBaseAppDelegate هو آلية «إعلان فتح التطبيق على مستوى التطبيق» التلقائية (§4.6) — فالأساليب requestSplashAppOpenAd/disableAppOpenSplashCallback/showAppOpenAdIfAvailable هي أساليب مثيل تابعة لـ AKSBaseAppDelegate نفسها، ولا يتيحها كائن AppsKitSDK وحده. كذلك، فإن تتبّع الانتقال بين المقدمة والخلفية الذي يوجّه عملها موجود في تجاوزات applicationWillEnterForeground/applicationDidBecomeActive/applicationWillResignActive داخل AKSBaseAppDelegate. إذا كنت لا تزال تريد عرض إعلان فتح التطبيق، فاستدعِ AppsKitSDKAdsManagerIOS.instance.loadAppOpen/showAppOpen بنفسك (§5، إعلانات فتح التطبيق) عند النقطة المناسبة في تسلسل بدء تشغيل تطبيقك.

4.3 نقطة دخول التطبيق في SwiftUI

إذا كان تطبيقك يستخدم بروتوكول App في SwiftUI بدلًا من لوحة مشاهد (storyboard)، فاربطه بـ AppDelegate باستخدام @UIApplicationDelegateAdaptor — فهذا هو ما يؤدي فعليًا إلى تنفيذ application(_:didFinishLaunchingWithOptions:):

swift
import SwiftUI

@main
struct YourApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

يُنفَّذ تسلسل التهيئة بالكامل الوارد في §4.1 بشكل متزامن داخل didFinishLaunchingWithOptions، قبل عرض WindowGroup — لذا يكون جسر الإعلانات وSDK جاهزين قبل ظهور أي واجهة مستخدم.

4.4 الموافقة وتهيئة الإعلانات

swift
AppsKitSDKAdsManagerIOS.instance.initialize(userHasGivenConsent: true, isCCPAConsent: true)

يضبط نموذج التكامل المرجعي كلا العَلَمين على القيمة الثابتة true — ولا يتضمن أي آلية مدمجة للحصول على الموافقة عبر UMP/CMP. هذا نقص في التكامل، وليس توصية: إذا كان تطبيقك خاضعًا لـ GDPR/CCPA، فنفّذ آلية الموافقة الخاصة بك (تتوفر UMP SDK من Google بالفعل كتبعية غير مباشرة عبر Google-Mobile-Ads-SDK، ‏§1.2 — وما عليك سوى استدعائها)، أو استخدم منصة مخصصة لإدارة الموافقة (CMP)، ثم مرّر قرار المستخدم الفعلي إلى هذا الاستدعاء بدلًا من القيمة الثابتة true. واستخدم معها مطالبة ATT الواردة في §3.3 إذا كنت تحتاج أيضًا إلى IDFA.

4.5 إزالة الإعلانات / التحكم وفق عمليات الشراء داخل التطبيق (IAP)

swift
AppsKitSDK.shared.setRemoveAdsStatus(status: true)   // call after a successful purchase/restore
AppsKitSDK.shared.getRemoveAdsStatus()               // check current status

يتحقق كل استدعاء لتحميل الإعلانات أو عرضها من هذه الحالة داخليًا بالفعل — فلا تضف شروط تحكم بنفسك. لاحظ نمط الوصول في Swift: تُتاح جميع أنواع المديرين هذه كفئات تتضمن موصّل وصول ثابتًا .shared ‏(AppsKitSDK.shared، ‏AKSLogManager.shared، ‏PreferencesManager.shared، وغيرها) — وستجد هذا النمط في جميع أجزاء SDK.

4.6 إعلانات فتح التطبيق على مستوى التطبيق

تتتبّع AKSBaseAppDelegate تلقائيًا الانتقالات بين المقدمة والخلفية (applicationWillEnterForeground/applicationDidBecomeActive/applicationWillResignActive)، وتعرض إعلان فتح التطبيق عند العودة إلى التطبيق دون تشغيله من جديد إذا كان الخيار isAppOpenAtApplicationLvl مفعّلًا في إعدادات بوابة AKS. تتوفر ثلاثة أساليب لتسلسل شاشة البداية:

swift
// Call from your splash screen — requests and shows an app-open ad
(appDelegate as? AKSBaseAppDelegate)?.requestSplashAppOpenAd(callbacks: myCallbacks, waitTimeInSeconds: 5)

// Stop the splash screen's callback from firing again on subsequent app-open events
(appDelegate as? AKSBaseAppDelegate)?.disableAppOpenSplashCallback()

// Manually trigger "show if one's already loaded and ready" — used internally on resume
(appDelegate as? AKSBaseAppDelegate)?.showAppOpenAdIfAvailable()