MMP แมปสคีมา SKAdNetwork โดยอัตโนมัติได้อย่างไร? พาร์ทเนอร์การวัดผลโมบายล์ (MMP) หรือแบ็กอัปการระบุแหล่งที่มา จะทำงานอัตโนมัติในการแมปสคีมา SKAdNetwork โดยแปลงอีเวนต์ในแอปและระดับรายได้ให้เป็นสคีมาการกำหนดค่า JSON แบบไดนามิกที่มีการจัดเวอร์ชันบนคอนโซลส่วนกลาง SDK มือถือจะดึงข้อมูลการกำหนดค่านี้ตอนเปิดแอปและประเมินกฎ conversion ในเครื่องขณะรันไทม์ ซึ่งช่วยให้การเปลี่ยนแปลงกฎ conversion ที่รองรับมีผลบังคับใช้ได้ทันทีโดยไม่ต้องออกรุ่นไบนารีแอปใหม่
สคีมา Conversion Value ของ SKAdNetwork คือชุดกฎระดับผู้จำหน่ายหรือระดับแอปพลิเคชัน ที่แมปพฤติกรรมผู้ใช้ในแอป เช่น ธุรกรรมรายได้, ขั้นตอนการเริ่มต้นใช้งาน (onboarding), หรือการมีส่วนร่วมกับฟีเจอร์ เข้ากับค่าละเอียด 6 บิตของ Apple (0 ถึง 63) และค่าหยาบ 3 ระดับ (
low,medium,high) สถาปัตยกรรมการแมปแบบไดนามิกจะแจกจ่ายไฟล์กำหนดค่าที่มีการจัดเวอร์ชันจากแบ็กอัปบนคลาวด์ไปยัง SDK ของไคลเอนต์ ซึ่งช่วยลดความจำเป็นในการฮาร์ดโค้ดลอจิก conversion ไว้ภายในไบนารีแอปพลิเคชัน iOS ที่คอมไพล์แล้ว
| คำศัพท์ | คำจำกัดความ |
|---|---|
| SKAdNetwork | เฟรมเวิร์กในระดับแพลตฟอร์มของ Apple สำหรับการระบุแหล่งที่มาแคมเปญการตลาดที่รักษาความเป็นส่วนตัว |
| Conversion Value Schema | การกำหนดค่าที่กำหนดโดยผู้จำหน่ายหรือแอป เพื่อแมปเหตุการณ์สำคัญในแอปกับค่าแบบละเอียดและแบบหยาบ |
| Dynamic Schema Mapping | การแจกจ่ายและการประเมินกฎ conversion แบบอัตโนมัติขณะรันไทม์ผ่าน SDK |
| Window Locking | พารามิเตอร์ API (lockWindow: true) ที่ทำให้หน้าต่าง conversion ที่ใช้งานอยู่สิ้นสุดเร็วกว่ากำหนด |
สถาปัตยกรรมการแมป Conversion Value ของ SKAdNetwork แบบอัตโนมัติ
การแยกระหว่างเลเยอร์แพลตฟอร์มของ Apple และเลเยอร์สคีมาของผู้จำหน่าย
ในการออกแบบเอ็นจิ้น Conversion Value ที่แข็งแกร่ง ทีมวิศวกรต้องแยกระหว่างกฎเฟรมเวิร์กดั้งเดิมของ Apple ออกจากนามธรรมของสคีมาในระดับผู้จำหน่าย:
- เลเยอร์แพลตฟอร์มของ Apple: ควบคุมพริมิติฟหลักของระบบปฏิบัติการ รวมถึงหน้าต่าง conversion สามช่วงตามลำดับ (วันที่ 0–2, วันที่ 3–7, และวันที่ 8–35 หลังจากเปิดใช้งานครั้งแรก), ค่าละเอียด 6 บิต (0–63), ค่าหยาบ (
low,medium,high), ระดับข้อมูล postback, และ APISKAdNetwork.updatePostbackConversionValue - เลเยอร์สคีมาของผู้จำหน่าย: ครอบคลุมถึงกฎธุรกิจที่กำหนดโดยแอปพลิเคชัน เช่น การจัดกลุ่มรายได้, ความก้าวหน้าของฟันเนลการเริ่มต้นใช้งาน, การจัดสรรบิตต์ฟลาส, การซิงโครไนซ์ JSON จากระยะไกล, และการประเมินกฎฝั่งไคลเอนต์
┌────────────────────────────────────────┐
│ Vendor Schema Layer │
│ [MMP / Analytics Console] ──► [Publishes Versioned JSON Configuration] │
│ │ │
│ [Client Mobile SDK] ──► [Evaluates In-App Events Locally in Memory] │
└──────────────────────────────────────┬─┘
│ (Calculates Fine, Coarse, & Lock)
▼
┌────────────────────────────────────────┐
│ Apple Platform Layer │
│ [StoreKit Framework] ──► [SKAdNetwork.updatePostbackConversionValue] │
│ [Operating System] ──► [Manages Conversion Windows & Timers] │
│ [System] ──► [Prepares and Sends Signed Postback] │
└────────────────────────────────────────┘

ข้อเสียของการฮาร์ดโค้ดลอจิก Conversion
การฮาร์ดโค้ดลอจิก conversion ไว้โดยตรงภายในเป้าหมายแอปพลิเคชัน iOS จะสร้างข้อจำกัดในการดำเนินงานที่สำคัญ:
- การพึ่งพาการตรวจสอบของ App Store: การปรับเปลี่ยนเกณฑ์รายได้, น้ำหนักของอีเวนต์, หรือทริกเกอร์ล็อกหน้าต่าง จะต้องผ่านวงจรการออกรุ่นไบนารีที่สมบูรณ์
- ความกระจัดกระจายของเวอร์ชัน: เวอร์ชันแอปในอดีตหลายเวอร์ชันที่ใช้งานจริงจะส่งความหมายของ conversion ที่ขัดแย้งกัน ส่งผลให้โมเดลการรายงานปลายทางเสียหาย
- ความยืดหยุ่นในการปรับแต่งที่จำกัด: ทีมเติบโตทางธุรกิจไม่สามารถปรับเปลี่ยนกลยุทธ์ conversion ระหว่างแคมเปญที่เน้นการมีส่วนร่วมและแคมเปญที่เน้นการสร้างรายได้ให้สอดคล้องกับประสิทธิภาพการตลาดแบบเรียลไทม์ได้
ไปพไลน์การส่งมอบการกำหนดค่าแบบไดนามิก
สถาปัตยกรรมการแมปแบบอัตโนมัติช่วยแยกตรรกะ conversion ออกจากไบนารีที่คอมไพล์แล้วผ่านไปพไลน์หลายขั้นตอน:
การกำหนดค่าคอนโซล: นักการตลาดและนักวิเคราะห์กำหนดค่าน้ำหนักอีเวนต์, ระดับสกุลเงิน, และกฎการล็อกหน้าต่างบนแดชบอร์ดส่วนกลาง
ดูเพิ่มเติม: SKAdNetwork ──> สถาปัตยกรรมการระบุแหล่งที่มาของมือถือ
การออกแบบสคีมา Conversion Value แบบไดนามิกข้ามหน้าต่าง SKAN 4.0
การแบ่งส่วนสคีมาแบบหลายหน้าต่าง (Multi-Window Schema Partitioning)
SKAdNetwork 4.0 จัดโครงสร้างการวัดผล conversion ออกเป็นสามหน้าต่างตามลำดับ ซึ่งยึดโยงกับการเปิดใช้งานแอปครั้งแรก:
- หน้าต่างที่ 1 (วันที่ 0–2): 48 ชั่วโมงแรกหลังจากเปิดใช้งานครั้งแรก
- หน้าต่างที่ 2 (วันที่ 3–7): ชั่วโมงที่ 48 ถึง 168 หลังจากเปิดใช้งานครั้งแรก
- หน้าต่างที่ 3 (วันที่ 8–35): ชั่วโมงที่ 168 ถึง 840 หลังจากเปิดใช้งานครั้งแรก
เอ็นจิ้นสคีมาแบบไดนามิกจะทำการแบ่งส่วนกฎข้ามหน้าต่างเหล่านี้ โดยทำการคำนวณค่าที่เหมาะสมตามเวลาที่ผ่านไปนับตั้งแต่เปิดใช้งานแอปพลิเคชันครั้งแรก
หน้าต่างที่ 1 (วันที่ 0–2): การจัดโครงสร้างค่าแบบละเอียดและแบบหยาบ
หน้าต่างที่ 1 เป็นหน้าต่าง conversion เดียวที่มีสิทธิ์เปิดเผยค่า conversion แบบละเอียด การกำหนดค่าสำหรับหน้าต่างที่ 1 จะกำหนดการแมปที่ทำงานร่วมกันสองแบบ:
- การแมปแบบละเอียด (0–63): กฎความละเอียดสูงที่บันทึกระดับการสร้างรายได้เริ่มต้น, ขั้นตอนการเริ่มต้นใช้งาน, หรือคะแนนการมีส่วนร่วมแบบคอมโพสิต
- การแมปแบบหยาบ (
low,medium,high): สถานะสำรองความละเอียดต่ำที่จะเปิดเผยเมื่อระดับข้อมูล postback ที่กำหนดไม่อนุญาตให้มีการรายงานแบบละเอียด
หน้าต่างที่ 2 (วันที่ 3–7) และ 3 (Day 8–35): การติดตามวงจรชีวิตแบบหยาบ
Postback ครั้งที่สองและสามจะไม่เปิดเผยค่า conversion แบบละเอียด แต่สำหรับระดับข้อมูลที่มีสิทธิ์ จะเปิดเผยเฉพาะค่าแบบหยาบเท่านั้น
สคีมาสำหรับหน้าต่างที่ 2 และ 3 จะมุ่งเน้นไปที่การรักษาผู้ใช้ระยะยาวและเหตุการณ์สำคัญในการสร้างรายได้:
- การแมปแบบหยาบในหน้าต่างที่ 2: ประเมินการรักษาผู้ใช้ช่วงกลางฟันเนล (เช่น
low= ใช้งานในวันที่ 3–7;medium= ทำครบ 3 เซสชัน;high= ซื้อซ้ำหรือทดลองใช้งานเปลี่ยนเป็นแบบชำระเงินแล้ว) - การแมปแบบหยาบในหน้าต่างที่ 3: ประเมินการรักษาผู้ใช้ระยะยาวและการต่ออายุการสมัครสมาชิก (เช่น
low= ยังใช้งานอยู่ในวันที่ 8–35;medium= ถึงเป้าหมายเลเวล;high= สมาชิกแบบชำระเงินที่ยังใช้งานอยู่)
นักพัฒนาที่กำหนดค่าสคีมา conversion สามารถศึกษา เอกสารประกอบการแมป conversion ของ SKAN เพื่อดูแนวทางทางเทคนิคเกี่ยวกับโครงสร้างกฎแบบหลายหน้าต่าง

โมเดลการเข้ารหัสที่กำหนดโดยผู้จำหน่าย: การจัดกลุ่มรายได้, ฟันเนล, และลอจิกบิตส์
โมเดลการเข้ารหัสเหล่านี้แสดงถึงรูปแบบการออกแบบในระดับผู้จำหน่ายและระดับแอปพลิเคชัน มากกว่าประเภทสคีมาที่ Apple กำหนด
สคีมาที่อิงตามรายได้
สคีมาการสร้างรายได้จะจัดสรรค่าแบบละเอียดที่มีอยู่ให้ครอบคลุมยอดซื้อสะสม:
- การจัดกลุ่มแบบเส้นตรง (Linear Bucketing): แบ่งช่วงรายได้ออกเป็นช่วงเท่าๆ กัน (เช่น 64 บักเก็ต เพิ่มขึ้นทีละ $1.50 สูงสุดถึง $96.00) เหมาะสำหรับแอปพลิเคชันที่มีขนาดธุรกรรมที่คาดการณ์ได้
- การจัดกลุ่มแบบลอการิทึม (Logarithmic Bucketing): จัดสรรบักเก็ตที่มีความละเอียดสูงให้กับรายการซื้อที่มีต้นทุนต่ำ ขณะเดียวกันก็ขยายช่วงบักเก็ตสำหรับการทำธุรกรรมที่มีมูลค่าสูง (เช่น ค่า 1–20 ครอบคลุม $0.99–$19.99; ค่า 21–50 ครอบคลุม $20.00–$100.00; ค่า 51–63 ครอบคลุม $100.00–$1000.00+)
- การจัดกลุ่มตามเปอร์เซ็นต์ไทล์ (Percentile-Based Bucketing): แมปการกระจายการซื้อของผู้ใช้ในอดีตเข้ากับกลุ่ม cohorts ตามเส้นโค้งการสร้างรายได้จริง
สคีมาความก้าวหน้าของฟันเนลและทิศทางของค่า
ใน SKAdNetwork 3 และรุ่นก่อนหน้า Apple กำหนดให้ค่า conversion ต้องเพิ่มขึ้นอย่างต่อเนื่อง (monotonic) แต่ใน SKAdNetwork 4.0 Apple ได้ยกเลิกข้อจำกัดนี้ ทำให้ค่า conversion ในหน้าต่างที่ 1 สามารถเพิ่มขึ้นหรือลดลงข้ามการเรียก API ครั้งต่อๆ ไปได้
อย่างไรก็ตาม สคีมาการระบุแหล่งที่มาจำนวนมากยังคงบังคับให้เกิดความก้าวหน้าแบบต่อเนื่องโดยเจตนา เพื่อใช้เป็นแนวปฏิบัติในการออกแบบระดับผู้จำหน่ายเพื่อให้แน่ใจว่าค่าที่สูงขึ้นแสดงถึงผลลัพธ์ทางธุรกิจที่ดีขึ้นเรื่อยๆ:
- ค่า
0: ติดตั้งและเปิดแอปแล้ว - ค่า
10: ลงทะเบียนสำเร็จ - ค่า
25: จบการสอนใช้งาน (Onboarding) - ค่า
30: เพิ่มวิธีการชำระเงินแล้ว - ค่า
45: เพิ่มสินค้าลงตะกร้าแล้ว - ค่า
63: ชำระเงินครั้งแรกสำเร็จ
สคีมาหมวดหมู่แบบบิตส์ (Bitwise Categorical Schemas)
สคีมาแบบบิตส์จะปฏิบัติต่อจำนวนเต็ม 6 บิต (
| ตำแหน่งบิต | น้ำหนักไบนารี | พฤติกรรมในแอปที่แมป |
|---|---|---|
| บิต 0 ( |
1 (0b000001) |
ผู้ใช้ลงทะเบียนเสร็จสิ้น |
| บิต 1 ( |
2 (0b000010) |
ผู้ใช้เปิดใช้งานการแจ้งเตือนแบบพุช |
| บิต 2 ( |
4 (0b000100) |
ผู้ใช้เพิ่มสินค้าลงในรายการโปรด |
| บิต 3 ( |
8 (0b001000) |
ผู้ใช้แชร์ลิงก์แนะนำเพื่อน |
| บิต 4 ( |
16 (0b010000) |
ผู้ใช้ทำการซื้อสินค้าในแอปสำเร็จ |
| บิต 5 ( |
32 (0b100000) |
ผู้ใช้สมัครสมาชิกช่วงทดลองใช้งานพรีเมียม |
เพย์โหลด JSON ที่มีการจัดเวอร์ชันด้านล่างแสดงตัวอย่างเอกสารการกำหนดค่าแบบไดนามิกหลายหน้าต่าง:
{
"schema_version": "4.0.1",
"app_id": "1234567890",
"currency": "USD",
"windows": {
"window_1": {
"mode": "hybrid_revenue_and_funnel",
"fine_mapping": [
{ "event": "app_open", "min_revenue_cents": 0, "fine_value": 0, "lock": false },
{ "event": "registration_complete", "min_revenue_cents": 0, "fine_value": 10, "lock": false },
{ "event": "tutorial_complete", "min_revenue_cents": 0, "fine_value": 20, "lock": false },
{ "event": "purchase", "min_revenue_cents": 99, "fine_value": 30, "lock": false },
{ "event": "purchase", "min_revenue_cents": 999, "fine_value": 45, "lock": false },
{ "event": "purchase", "min_revenue_cents": 4999, "fine_value": 63, "lock": true }
],
"coarse_mapping": {
"low": { "events": ["app_open", "registration_complete"] },
"medium": { "events": ["tutorial_complete"] },
"high": { "events": ["purchase"] }
}
},
"window_2": {
"mode": "coarse_retention_and_monetization",
"coarse_mapping": {
"low": { "events": ["app_open"], "lock": false },
"medium": { "events": ["session_milestone"], "lock": false },
"high": { "events": ["repeat_purchase"], "lock": true }
}
},
"window_3": {
"mode": "coarse_long_tail_ltv",
"coarse_mapping": {
"low": { "events": ["app_open"], "lock": false },
"medium": { "events": ["level_milestone"], "lock": false },
"high": { "events": ["subscription_active"], "lock": true }
}
}
}
}
การกำหนดค่า SDK แบบไดนามิก: การนำเข้าและการประเมินการกำหนดค่าระยะไกลขณะรันไทม์
กลไกการประเมินกฎฝั่งไคลเอนต์
SDK การระบุแหล่งที่มาจะประเมินกฎ conversion ในเครื่องภายในรันไทม์ของแอปพลิเคชัน:
- ไม่มีการดึงการกำหนดค่าระยะไกลแบบซิงโครนัสบนเส้นทางอีเวนต์: การกระทำในแอปจะทริกเกอร์การประเมินในหน่วยความจำภายในเครื่องเทียบกับชุดกฎที่ใช้งานอยู่ โดยเรียกใช้ StoreKit API ทันทีโดยไม่บล็อกการทำงานของแอปพลิเคชัน
- การลดทอนข้อมูลให้น้อยที่สุด: สำหรับเส้นทางอัปเดต conversion ของ SKAdNetwork ที่แสดงอยู่ที่นี่ อินพุตอีเวนต์ดิบสามารถประเมินได้ในเครื่อง และมีเพียงค่า conversion ผลลัพธ์เท่านั้นที่จะต้องส่งต่อไปยัง StoreKit ทั้งนี้ไม่ได้อธิบายหรือจำกัดโฟลว์ข้อมูลการวิเคราะห์อื่นๆ ที่ใช้งานโดย SDK ในตัวของมันเอง
การจัดการสถานะออฟไลน์และการคงอยู่ของข้อมูลในเครื่อง
เมื่อแอปพลิเคชันเปิดใช้งานในขณะออฟไลน์หรือในสภาพแวดล้อมเครือข่ายที่ไม่เสถียร:
- SDK จะเริ่มต้นแสตมป์เวลาการเปิดใช้งานครั้งแรกอย่างอิสระในการจัดเก็บถาวรในเครื่อง
- SDK จะโหลดสคีมาการกำหนดค่าที่ตรึงไว้จากการจัดเก็บถาวรในเครื่อง โดยตรวจสอบว่าเพย์โหลดที่แคชไว้ตรงกับเวอร์ชันสคีมาที่ตรึงไว้หรือไม่
- หากเกิดอีเวนต์ในแอปขึ้นในขณะออฟไลน์ SDK จะประเมินอีเวนต์เหล่านั้นเทียบกับชุดกฎที่แคชไว้และเรียกใช้ StoreKit update API ทันที
- การเตรียมการและการส่ง postback ยังคงจัดการโดยระบบและเป็นแบบอะิงโครนัส โดยแอปไม่ต้องส่ง postback ด้วยตนเอง
การใช้งานด้วย Swift ด้านล่างแสดงตัวอย่างเอ็นจิ้นการประเมินสคีมาแบบหลายหน้าต่าง ซึ่งคำนวณค่าแบบละเอียดและแบบหยาบ จัดการสถานะการล็อกเฉพาะหน้าต่าง คงการกำหนดค่าสคีมาที่ตรึงไว้ และคอมมิตการอัปเดตสถานะก็ต่อเมื่อการดำเนินการของ StoreKit สำเร็จเท่านั้น:
import Foundation
import StoreKit
// MARK: - Schema Configuration Models
struct SKANSchemaConfig: Codable {
let schemaVersion: String
let appId: String
let currency: String
let windows: SchemaWindows
enum CodingKeys: String, CodingKey {
case schemaVersion = "schema_version"
case appId = "app_id"
case currency, windows
}
}
struct SchemaWindows: Codable {
let window1: Window1Config
let window2: WindowCoarseConfig
let window3: WindowCoarseConfig
enum CodingKeys: String, CodingKey {
case window1 = "window_1"
case window2 = "window_2"
case window3 = "window_3"
}
}
struct Window1Config: Codable {
let mode: String
let fineMapping: [FineRule]
let coarseMapping: CoarseRuleGroup
enum CodingKeys: String, CodingKey {
case mode
case fineMapping = "fine_mapping"
case coarseMapping = "coarse_mapping"
}
}
struct FineRule: Codable {
let event: String
let minRevenueCents: Int
let fineValue: Int
let lock: Bool
enum CodingKeys: String, CodingKey {
case event
case minRevenueCents = "min_revenue_cents"
case fineValue = "fine_value"
case lock
}
}
struct WindowCoarseConfig: Codable {
let mode: String
let coarseMapping: [String: CoarseRule]
enum CodingKeys: String, CodingKey {
case mode
case coarseMapping = "coarse_mapping"
}
}
struct CoarseRuleGroup: Codable {
let low: CoarseRule
let medium: CoarseRule
let high: CoarseRule
}
struct CoarseRule: Codable {
let events: [String]?
let lock: Bool?
}
// MARK: - Multi-Window SKAN 4.0 Schema Engine
final class SKANSchemaEngine {
static let shared = SKANSchemaEngine()
private init() {}
private var activeSchema: SKANSchemaConfig?
private var firstLaunchDate: Date?
private var lockedWindows = Set<Int>()
private var lastRecordedFineValue: Int = 0
private var pinnedSchemaVersion: String?
/// Initializes the first-launch timestamp anchor independently of remote configuration fetches
func initializeLifecycleAnchor() {
let defaults = UserDefaults.standard
if let storedLaunch = defaults.object(forKey: "skan_first_launch_date") as? Date {
self.firstLaunchDate = storedLaunch
} else {
let now = Date()
self.firstLaunchDate = now
defaults.set(now, forKey: "skan_first_launch_date")
}
let lockedArray = defaults.array(forKey: "skan_locked_windows") as? [Int] ?? []
self.lockedWindows = Set(lockedArray)
self.lastRecordedFineValue = defaults.integer(forKey: "skan_last_fine_value")
self.pinnedSchemaVersion = defaults.string(forKey: "skan_pinned_schema_version")
// Restore previously cached schema payload if it matches the pinned version
if let pinnedVersion = self.pinnedSchemaVersion,
let cachedData = defaults.data(forKey: "skan_cached_schema_payload"),
let cachedSchema = try? JSONDecoder().decode(SKANSchemaConfig.self, from: cachedData),
cachedSchema.schemaVersion == pinnedVersion {
self.activeSchema = cachedSchema
}
}
/// Loads active schema, persisting the pinned payload to maintain consistency across the 35-day lifecycle
func configure(schema: SKANSchemaConfig) {
let defaults = UserDefaults.standard
if let pinned = pinnedSchemaVersion {
// If already pinned, accept only schemas matching the pinned version
if pinned == schema.schemaVersion {
self.activeSchema = schema
if let data = try? JSONEncoder().encode(schema) {
defaults.set(data, forKey: "skan_cached_schema_payload")
}
}
} else {
// Pin the initial schema version for this lifecycle
self.activeSchema = schema
self.pinnedSchemaVersion = schema.schemaVersion
defaults.set(schema.schemaVersion, forKey: "skan_pinned_schema_version")
if let data = try? JSONEncoder().encode(schema) {
defaults.set(data, forKey: "skan_cached_schema_payload")
}
}
}
/// Determines the active conversion window based on elapsed time from first launch
private var currentWindowIndex: Int {
guard let firstLaunch = firstLaunchDate else { return 0 }
let elapsedHours = Date().timeIntervalSince(firstLaunch) / 3600.0
switch elapsedHours {
case 0.0..<48.0:
return 1
case 48.0..<168.0:
return 2
case 168.0...840.0:
return 3
default:
return 0 // Window closed (>35 days)
}
}
/// Evaluates an in-app event against the active schema for the current window
func trackEvent(name: String, revenueCents: Int = 0) {
guard #available(iOS 16.1, *),
let schema = activeSchema else { return }
let window = currentWindowIndex
guard window >= 1 && window <= 3, !lockedWindows.contains(window) else { return }
var targetFineValue: Int?
var targetCoarseValue: SKAdNetwork.CoarseConversionValue?
var shouldLock = false
var matchedRule = false
if window == 1 {
// Window 1: Evaluate fine-grained rules with highest-threshold precedence
let matchingFineRules = schema.windows.window1.fineMapping
.filter { $0.event == name && revenueCents >= $0.minRevenueCents }
.sorted { $0.minRevenueCents < $1.minRevenueCents }
if let highestRule = matchingFineRules.last {
targetFineValue = highestRule.fineValue
if highestRule.lock { shouldLock = true }
matchedRule = true
}
// Window 1: Evaluate coarse-grained rules explicitly
if schema.windows.window1.coarseMapping.high.events?.contains(name) == true {
targetCoarseValue = .high
matchedRule = true
} else if schema.windows.window1.coarseMapping.medium.events?.contains(name) == true {
targetCoarseValue = .medium
matchedRule = true
} else if schema.windows.window1.coarseMapping.low.events?.contains(name) == true {
targetCoarseValue = .low
matchedRule = true
}
} else {
// Windows 2 & 3: Evaluate coarse rules only
let coarseConfig = (window == 2) ? schema.windows.window2 : schema.windows.window3
if let highRule = coarseConfig.coarseMapping["high"], highRule.events?.contains(name) == true {
targetCoarseValue = .high
if highRule.lock == true { shouldLock = true }
matchedRule = true
} else if let medRule = coarseConfig.coarseMapping["medium"], medRule.events?.contains(name) == true {
targetCoarseValue = .medium
if medRule.lock == true { shouldLock = true }
matchedRule = true
} else if let lowRule = coarseConfig.coarseMapping["low"], lowRule.events?.contains(name) == true {
targetCoarseValue = .low
if lowRule.lock == true { shouldLock = true }
matchedRule = true
}
}
// If no explicit rule matched for this event, do not trigger a StoreKit update
guard matchedRule else { return }
let fineToSubmit = targetFineValue ?? (window == 1 ? lastRecordedFineValue : 0)
let clampedFine = max(0, min(63, fineToSubmit))
let coarseToSubmit = targetCoarseValue ?? .low
// Dispatch StoreKit conversion update
// Note: StoreKit ignores the fineValue parameter after Window 1
SKAdNetwork.updatePostbackConversionValue(
clampedFine,
coarseValue: coarseToSubmit,
lockWindow: shouldLock
) { [weak self] error in
guard let self = self else { return }
if let error = error {
print("StoreKit conversion update failed: \(error.localizedDescription)")
} else {
// Commit local state only after StoreKit successfully accepts the update
DispatchQueue.main.async {
if window == 1 {
self.lastRecordedFineValue = clampedFine
UserDefaults.standard.set(clampedFine, forKey: "skan_last_fine_value")
}
if shouldLock {
self.lockedWindows.insert(window)
UserDefaults.standard.set(Array(self.lockedWindows), forKey: "skan_locked_windows")
}
print("SKAN 4.0 update succeeded: Window=\(window), Fine=\(clampedFine), Coarse=\(coarseToSubmit.rawValue), Locked=\(shouldLock)")
}
}
}
}
}

การทำให้อยู่ในรูปแบบอัตโนมัติของการดำเนินการ lockWindow เพื่อเร่งการเตรียม Postback
กลไกการทำงานของพารามิเตอร์ lockWindow
เมื่อแอปเรียกใช้ updatePostbackConversionValue(_:coarseValue:lockWindow:) พร้อมกับกำหนด lockWindow: true การอัปเดตนั้นจะกลายเป็นการอัปเดต conversion value ครั้งสุดท้ายสำหรับหน้าต่างที่ใช้งานอยู่ ระบบปฏิบัติการจะเตรียม postback ทันทีและจะไม่สนใจการอัปเดต conversion value เพิ่มเติมตลอดช่วงเวลาที่เหลือของหน้าต่างนั้น
Default Window 1 (No Lock):
[First Launch] ─────────────── 48 Hours Open ───────────────► [Closes] ──► Delay (24-48h) ──► Postback 1
Locked Window 1 (Purchase at Hour 6):
[First Launch] ── 6h (Lock: true) ──► [Conversion Locked / Postback Prepared] ──► Delay (24-48h) ──► Postback 1 Sent Sooner
ข้อดีข้อเสียเชิงกลยุทธ์ในการล็อกหน้าต่างอัตโนมัติ
- การส่ง Postback ที่เร็วขึ้น: การสรุปผล conversion เร็วกว่ากำหนดช่วยให้การหน่วงเวลา postback แบบสุ่มของ Apple เริ่มต้นได้ทันที ส่งผลให้ข้อมูล conversion ถูกส่งไปยังเครือข่ายโฆษณาได้เร็วขึ้น
- ความเป็นอิสระของหน้าต่าง: การล็อกหน้าต่างปัจจุบันจะไม่เลื่อนเวลาเริ่มต้นของหน้าต่างถัดไปให้เร็วขึ้น หน้าต่างที่ 2 จะยังคงเริ่มต้นในวันที่ 3 โดยไม่คำนึงว่าหน้าต่างที่ 1 จะถูกล็อกเมื่อใด
- การตัดทอนช่วงเวลาการสังเกตการณ์: เมื่อหน้าต่างถูกล็อกแล้ว ระบบจะละเว้นการเรียกอัปเดต conversion value เพิ่มเติมสำหรับช่วงเวลาที่เหลือของหน้าต่าง conversion นั้น อีเวนต์ในแอปอาจยังคงเกิดขึ้นต่อไปได้ แต่จะไม่สามารถเปลี่ยนแปลงสถานะ conversion SKAdNetwork ของหน้าต่างนั้นได้อีก
การประสานสคีมา SKAN กับ AdAttributionKit
สถาปัตยกรรมการระบุแหล่งที่มาที่กำลังพัฒนาของ Apple
ปัจจุบัน Apple แนะนำให้ใช้ AdAttributionKit สำหรับแคมเปญการตลาดแอปข้าม App Store และตลาดแอปทางเลือกอื่นๆ ส่วน SKAdNetwork ยังคงมีความสำคัญสำหรับการบูรณาการที่มีอยู่และความสามารถในการทำงานร่วมกัน ดังนั้น เอ็นจิ้นการแมปแบบไดนามิกจึงควรแยกเลเยอร์กฎธุรกิจออกจาก conversion API เฉพาะของแต่ละเฟรมเวิร์ก:
- มิติข้อมูลค่าที่ใช้ร่วมกัน: ทั้งสองเฟรมเวิร์กประเมินค่าละเอียด 6 บิต (0 ถึง 63) และค่าหยาบ 3 ระดับ (
low,medium,high) - เลเยอร์ API ที่แตกต่างกัน: SKAdNetwork ใช้
SKAdNetwork.updatePostbackConversionValueในขณะที่ AdAttributionKit ใช้Postback.updateConversionValue - พฤติกรรมการเชื่อมโยง: หากการบูรณาการรองรับทั้งสองเฟรมเวิร์ก Apple แนะนำให้เรียกใช้ conversion-update API ของทั้งสองเฟรมเวิร์ก พร้อมทั้งคำนึงถึงพฤติกรรมการเชื่อมโยงระหว่าง SKAdNetwork กับ AdAttributionKit ตามที่มีการบันทึกไว้
เมทริกซ์การตัดสินใจเปรียบเทียบ: ลอจิกฝั่งไคลเอนต์แบบฮาร์ดโค้ดเทียบกับการกำหนดค่าแบบไดนามิก
| มิติการประเมิน | ลอจิกฝั่งไคลเอนต์แบบฮาร์ดโค้ด | การกำหนดค่าสคีมาแบบไดนามิก |
|---|---|---|
| ความเร็วในการปรับเปลี่ยนสคีมา | ต้องผ่านการตรวจสอบของ App Store (ใช้เวลาหลายวันถึงหลายสัปดาห์) | อัปเดตระยะไกลสำหรับกฎที่รองรับได้โดยไม่ต้องออกรุ่นไบนารีใหม่ |
| ความคล่องตัวในการทดสอบและวนซ้ำ | มีแรงเสียดทานสูง / มีภาระงานทางวิศวกรรมสูง | การทดลองสคีมาแบบควบคุม พร้อมกฎที่แยกตามเวอร์ชันและกลุ่ม cohorts |
| การประสานงานหลายหน้าต่าง | State Machine แบบ manual ที่ซับซ้อนใน Swift | เอ็นจิ้นที่ตระหนักถึงวงจรชีวิตแบบอัตโนมัติ |
| การล็อกหน้าต่างอัตโนมัติ | ทริกเกอร์กฎแบบตายตัวและไม่ยืดหยุ่น | กฎการล็อกแบบไดนามิกที่ทริกเกอร์ด้วยอีเวนต์ |
| ความเท่าเทียมกันข้ามเฟรมเวิร์ก | โค้ดกระจัดกระจายไปตามเฟรมเวิร์กต่างๆ | เมทริกซ์การกำหนดค่าบนคลาวด์แบบรวมศูนย์ |
คำถามที่พบบ่อย (FAQ)
จะเกิดอะไรขึ้นหากผู้ใช้ทริกเกอร์หลายอีเวนต์ที่แมปกับ conversion value ที่แตกต่างกัน?
สคีมาอัตโนมัติสามารถอัปเดต conversion value ได้หรือไม่หากแอปออฟไลน์อยู่?
สคีมาอัตโนมัติจัดการกับการแปลงสกุลเงินสำหรับผู้ใช้ทั่วโลกอย่างไร?
สรุปและกรอบการตัดสินใจ
การทำให้การแมป Conversion Value ของ SKAdNetwork เป็นแบบอัตโนมัติช่วยแยกการทดลองเกี่ยวกับการเติบโตออกจากวงจรการออกรุ่นไบนารีมือถือ ด้วยการแจกจ่ายสคีมาแบบไดนามิกจากแดชบอร์ดการระบุแหล่งที่มาส่วนกลางและประเมินสคีมาเหล่านั้นในเครื่องภายใน SDK ทีมวิศวกรจึงสามารถปรับแต่งบักเก็ตรายได้, ปรับปรุงขั้นตอนสำคัญของฟันเนล, และกำหนดค่าการล็อกหน้าต่างอัตโนมัติ ซึ่งช่วยให้การเปลี่ยนแปลงกฎ conversion ที่รองรับมีผลบังคับใช้ได้โดยไม่ต้องส่งไบนารีแอปพลิเคชันกลับไปยัง App Store Connect
การกำหนดเส้นทางลิงก์เชิงลึกในระดับแอปพลิเคชัน (deep-link routing) สามารถทำงานควบคู่ไปกับเฟรมเวิร์กการระบุแหล่งที่มาที่รักษาความเป็นส่วนตัวของ Apple ในฐานะเลเยอร์การวัดผลและการเริ่มต้นใช้งานแยกต่างหาก แพลตฟอร์มอย่างเช่น OpoInstall มีโครงสร้างพื้นฐานสำหรับการกำหนดเส้นทางบริบทแบบเฟิร์สปาร์ตี้และการทำ deferred deep linking ซึ่งช่วยให้ทีมงานสามารถรักษาเจตนาของผู้ใช้ข้ามฟันเนล conversion จากเว็บสู่แอปได้
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการกำหนดค่าการระบุแหล่งที่มาที่ปฏิบัติตามกฎความเป็นส่วนตัวและไปพไลน์ deep linking สามารถศึกษาได้จาก เอกสารประกอบของ OpoInstall
วัสดุอ้างอิงที่เกี่ยวข้อง
-
แนวคิด: Conversion Value Schemas, Dynamic Schema Mapping, Revenue Bucketing, Window Locking, Monotonicity
-
เทคโนโลยี: Apple SKAdNetwork, Apple AdAttributionKit, StoreKit Framework, OpoInstall Mobile SDK
-
มาตรฐาน: IETF RFC 8259 JSON Specification
-
API: StoreKit
updatePostbackConversionValueAPI, AdAttributionKitPostback.updateConversionValueAPI
เอกสารประกอบอย่างเป็นทางการ
Share this article



