التوثيق · التكامل مع 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()