# Feature flags ใน iOS SDK

`Pushwoosh.configure.featureFlags` มอบ [feature flags](/th/product/audience-data-and-segmentation/feature-flags/) ที่กำหนดค่าไว้สำหรับแอปพลิเคชันใน Control Panel ให้กับแอป iOS ของคุณ Pushwoosh จะประเมินกฎของ flag บนเซิร์ฟเวอร์สำหรับอุปกรณ์และผู้ใช้ปัจจุบัน แล้ว SDK จะส่งคืนผลลัพธ์ว่า flag เปิดอยู่สำหรับอุปกรณ์นี้หรือไม่ และใช้ค่าพร็อพเพอร์ตีใดบ้าง

<Aside type="tip" title="ฟีเจอร์เบต้า">
Feature flags อยู่ในช่วงเบต้าและใช้งานได้เฉพาะในแพ็กเกจที่รวมฟีเจอร์นี้ไว้เท่านั้น ในแพ็กเกจอื่น เซิร์ฟเวอร์จะไม่ส่งคืน flag ใดเลย ดังนั้นทุก flag จะใช้ค่าเริ่มต้นที่คุณส่งไว้ในโค้ด
</Aside>

## ข้อกำหนดเบื้องต้น

* Pushwoosh iOS SDK 7.2.10 ขึ้นไป ที่ผสานรวมตามที่อธิบายไว้ใน[ภาพรวม iOS SDK](/th/developer/pushwoosh-sdk/ios-sdk/) feature flags มาพร้อมกับโมดูลที่จำเป็น `PushwooshFramework` และ `PushwooshCore` คุณจึงไม่ต้องเพิ่มโมดูลอื่น โดยจะทำงานในตัวแอป iOS เอง ส่วนส่วนขยายของแอป เช่น Notification Service Extension จะไม่ขอ flag เลย
* มี flag อย่างน้อยหนึ่งรายการที่สร้างไว้ใน **Audience** → **Feature Flags** คุณต้องใช้ **Key** ของ flag นั้น รวมถึงคีย์และประเภทของพร็อพเพอร์ตี ใช้คีย์ให้ตรงตามที่แสดงไว้ทุกประการ รวมถึงตัวพิมพ์เล็กและพิมพ์ใหญ่ ตัวอย่างเช่น `get("New_Checkout")` จะไม่พบ flag ที่มีคีย์ `new_checkout`
* หากแอปส่งคำขอของ SDK ผ่าน reverse proxy URL ของ proxy ต้องลงท้ายด้วย `json/1.3/` มิฉะนั้น SDK จะไม่ขอ flag และทุก flag จะใช้ค่าเริ่มต้นของคุณ

## อ่าน flag

ส่งค่าเริ่มต้นทุกครั้ง ค่านี้จะมีผลจนกว่า SDK จะได้รับ flag สำหรับอุปกรณ์ และมีผลทุกครั้งที่ flag ไม่มีอยู่ ถูกเก็บถาวร หรือแพ็กเกจไม่รวม feature flags

```swift
import PushwooshFramework

let flags = Pushwoosh.configure.featureFlags

if let checkout = flags.get("new_checkout"), checkout.isEnabled {
    showNewCheckout(
        buttonColor: checkout.string(forProperty: "button_color", default: "blue") ?? "blue",
        maxItems: Int(checkout.number(forProperty: "max_items", default: 3))
    )
} else {
    showOldCheckout()
}
```

หากต้องการตรวจสอบเพียงว่า flag เปิดอยู่หรือไม่ ให้ใช้ `isEnabled`:

```swift
let isNewCheckout = Pushwoosh.configure.featureFlags.isEnabled("new_checkout", default: false)
```

ใน Objective-C ให้เรียก `[PushwooshConfig featureFlags]`

## สิ่งที่ flag ส่งคืน

`get(_:)` จะส่งคืนออบเจ็กต์ `PWFeatureFlag` หรือ `nil` เมื่อ SDK ยังไม่มี flag สำหรับอุปกรณ์ หรือ flag ไม่อยู่ในคำตอบ flag ที่ถูกเก็บถาวรจะไม่อยู่ในคำตอบ `get` อ่านเฉพาะ flag ที่จัดเก็บไว้และไม่รอเครือข่ายเลย ออบเจ็กต์มีพร็อพเพอร์ตีดังนี้:

| พร็อพเพอร์ตี | ประเภท | คำอธิบาย |
| :---- | :---- | :---- |
| `key` | String | คีย์ของ flag |
| `isEnabled` | Bool | flag เปิดอยู่สำหรับอุปกรณ์นี้หรือไม่ |
| `ruleId` | String | กฎที่ตัดสินผลลัพธ์ เช่น `r_1a2b3c4d` เป็น `default` เมื่อไม่มีกฎใดตรงกับอุปกรณ์ และเป็นสตริงว่างเมื่อ flag ถูก kill |

อ่านค่าพร็อพเพอร์ตีด้วยเมธอดที่ตรงกับประเภทของพร็อพเพอร์ตี เมื่อ flag ปิดอยู่ พร็อพเพอร์ตีจะมีค่าเริ่มต้นที่ตั้งไว้ใน Control Panel

| เมธอด | ส่งคืน |
| :---- | :---- |
| `string(forProperty:default:)` | ค่าของพร็อพเพอร์ตีประเภท **String** หรือค่าเริ่มต้นของคุณ |
| `number(forProperty:default:)` | ค่าของพร็อพเพอร์ตีประเภท **Number** ในรูปแบบ `Double` หรือค่าเริ่มต้นของคุณ |
| `bool(forProperty:default:)` | ค่าของพร็อพเพอร์ตีประเภท **Boolean** หรือค่าเริ่มต้นของคุณ |
| `jsonObject(forProperty:)` | ค่าของพร็อพเพอร์ตีประเภท **JSON** ที่เก็บออบเจ็กต์ ในรูปแบบ dictionary หรือ `nil` |
| `jsonArray(forProperty:)` | ค่าของพร็อพเพอร์ตีประเภท **JSON** ที่เก็บอาร์เรย์ หรือ `nil` |

เมธอดแต่ละตัวจะส่งคืนค่าเริ่มต้นของคุณ หรือ `nil` สำหรับเมธอด JSON เมื่อไม่มีพร็อพเพอร์ตีนั้น หรือพร็อพเพอร์ตีมีค่าเป็นประเภทอื่น

`isEnabled(_:default:)` จะส่งคืนค่าเริ่มต้นเมื่อ `get(_:)` ส่งคืน `nil`

<span id="keep-flags-up-to-date" />

## ทำให้ flag เป็นปัจจุบันอยู่เสมอ

SDK จะจัดเก็บคำตอบล่าสุดไว้บนอุปกรณ์ได้นานสูงสุด 24 ชั่วโมง flag ที่จัดเก็บไว้จะใช้ได้เฉพาะกับ User ID และ Application Code ของแอปที่ใช้ขอ flag นั้นเท่านั้น SDK จะรีเฟรช flag:

* เมื่อเริ่มเซสชัน กล่าวคือเมื่อแอปเริ่มทำงานเป็นครั้งแรก หรือหลังจากอยู่ในเบื้องหลังอย่างน้อย 30 นาที
* ทุกชั่วโมงขณะที่แอปอยู่เบื้องหน้า
* หลังจากอุปกรณ์ลงทะเบียนรับการแจ้งเตือนแบบ push หากคำขอก่อนหน้าถูกส่งก่อนการลงทะเบียน
* ทันทีหลังจาก User ID เปลี่ยนด้วย `setUserId` flag ที่จัดเก็บไว้ของผู้ใช้ก่อนหน้าจะถูกทิ้ง ดังนั้นผู้ใช้ใหม่จะไม่เห็น flag เหล่านั้นเลย

หากต้องการตอบสนองต่อค่าใหม่ ให้เพิ่ม listener ที่ใช้ `PWFeatureFlagsUpdateListener` เมธอด `featureFlagsDidUpdate()` ของ listener จะถูกเรียกบน main thread หลังจากการรีเฟรชที่ทำให้ flag ใดๆ เปลี่ยนไป SDK เก็บ listener ไว้แบบ weak reference ให้เก็บ strong reference ไปยัง listener ของคุณ มิฉะนั้น listener จะถูกปล่อยและไม่ถูกเรียกเลย:

```swift
final class CheckoutViewController: UIViewController, PWFeatureFlagsUpdateListener {
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        Pushwoosh.configure.featureFlags.addUpdateListener(self)
    }

    override func viewWillDisappear(_ animated: Bool) {
        Pushwoosh.configure.featureFlags.removeUpdateListener(self)
        super.viewWillDisappear(animated)
    }

    func featureFlagsDidUpdate() {
        render()
    }
}
```

หากต้องการขอ flag ด้วยตัวเอง เช่น หลังจากการกระทำที่อาจทำให้ผู้ใช้ย้ายไปอยู่ใน Segment อื่น ให้เรียก `refresh(completion:)` ขณะที่ flag ที่จัดเก็บไว้ยังใช้ได้ เมธอดนี้จะส่งคำขอได้สูงสุดหนึ่งครั้งต่อ 5 นาที และจะตอบด้วย flag ที่จัดเก็บไว้ภายในช่วงเวลานั้น completion จะทำงานบน main thread:

```swift
Pushwoosh.configure.featureFlags.refresh { error in
    if let error = error as NSError? {
        let status = error.code
        // the stored flags, or your defaults, stay in effect
    }
    render()
}
```

เมื่อล้มเหลว error จะมีโดเมน `PWFeatureFlagsErrorDomain` และ `code` ของ error คือสถานะ HTTP โค้ดจะเป็น `0` เมื่อคำขอไม่ได้รับการตอบกลับ HTTP หรือไม่ได้ถูกส่ง และเป็น `200` เมื่อเซิร์ฟเวอร์ตอบกลับแล้วแต่ไม่สามารถใช้คำตอบนั้นได้

หากต้องการทดสอบการรีเฟรชโดยไม่มีขีดจำกัด 5 นาที ให้ตั้งค่าคีย์ Boolean `Pushwoosh_FEATURE_FLAGS_DISABLE_THROTTLE` ใน `Info.plist` เป็น `YES`

หาก Pushwoosh ไม่สามารถจับคู่ Segment ของอุปกรณ์ได้ทันเวลา ระบบจะส่งคืนคำตอบบางส่วน SDK จะไม่จัดเก็บคำตอบนี้ไว้บนอุปกรณ์ และการรีเฟรชครั้งถัดไปจะนำคำตอบฉบับสมบูรณ์มาให้

เมื่อ flag ถูก kill ใน Control Panel อุปกรณ์จะได้รับ flag นั้นในสถานะปิดพร้อมค่าพร็อพเพอร์ตีเริ่มต้นในการรีเฟรชครั้งถัดไป เมื่อ SDK ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้ ก็จะใช้ flag ที่จัดเก็บไว้ต่อไปจนกว่าจะมีอายุครบ 24 ชั่วโมง และหลังจากนั้นจะใช้ค่าเริ่มต้นของคุณ

<span id="log-impressions" />

## บันทึก impression

impression จะแจ้ง Pushwoosh ว่าผู้ใช้เข้าถึงโค้ดที่ flag ควบคุมจริง เพื่อให้คุณเปรียบเทียบผู้ใช้ที่ได้รับฟีเจอร์กับผู้ที่ไม่ได้รับได้ เรียก `logImpression(_:)` ในขณะที่ผู้ใช้เห็นฟีเจอร์หรือเวอร์ชันเก่าของฟีเจอร์ ทั้งในสาขาที่เปิดและสาขาที่ปิด เพราะผู้ใช้ที่ flag ปิดอยู่คือกลุ่มเปรียบเทียบของคุณ:

```swift
let flags = Pushwoosh.configure.featureFlags
let isOn = flags.isEnabled("new_checkout", default: false)
flags.logImpression("new_checkout")
if isOn {
    showNewCheckout()
} else {
    showOldCheckout()
}
```

SDK จะส่ง Event `PW_FeatureFlagImpression` พร้อมแอตทริบิวต์ดังนี้:

| แอตทริบิวต์ | ประเภท | ค่า |
| :---- | :---- | :---- |
| `flag_key` | string | คีย์ของ flag |
| `rule_id` | string | กฎที่ตัดสินผลลัพธ์ เช่นเดียวกับใน `ruleId` |
| `enabled` | boolean | flag เปิดอยู่หรือไม่ |

คุณไม่จำเป็นต้องสร้าง Event ไว้ล่วงหน้า เพราะ Pushwoosh จะเพิ่ม `PW_FeatureFlagImpression` พร้อมแอตทริบิวต์เหล่านี้ลงใน **Audience** → **Events** เมื่อ impression แรกมาถึง SDK จะส่ง impression หนึ่งครั้งต่อ flag กฎ และสถานะในหนึ่งเซสชัน และจะไม่ส่งอะไรเลยเมื่อไม่มี flag สำหรับคีย์นั้น หากการส่งล้มเหลว การเรียก `logImpression` ครั้งถัดไปสำหรับ flag นั้นจะส่ง impression อีกครั้ง

นอกจากนี้ impression ยังช่วยให้นักการตลาดสร้าง Segment ของผู้ใช้ที่เข้าถึงฟีเจอร์ได้ นั่นคือสถานะ **เห็นฟีเจอร์ (30 วัน)** ของ[เงื่อนไข feature flag](/th/product/audience-data-and-segmentation/feature-flags/#find-users-who-reached-a-feature)

## ข้อมูลอ้างอิงเมธอด

`Pushwoosh.configure.featureFlags` มีเมธอดดังนี้:

| เมธอด | คำอธิบาย |
| :---- | :---- |
| `get(_:)` | ส่งคืน `PWFeatureFlag` สำหรับคีย์ หรือ `nil` อ่านเฉพาะ flag ที่จัดเก็บไว้ |
| `isEnabled(_:default:)` | ส่งคืนว่า flag เปิดอยู่หรือไม่ หรือค่าเริ่มต้นเมื่อไม่มี flag |
| `refresh(completion:)` | ขอ flag โดยจำกัดไว้ที่หนึ่งครั้งต่อ 5 นาทีขณะที่ flag ที่จัดเก็บไว้ยังใช้ได้ completion จะได้รับ `nil` หรือ error ใน `PWFeatureFlagsErrorDomain` และ completion เองอาจเป็น `nil` ได้ |
| `addUpdateListener(_:)` | เรียก `featureFlagsDidUpdate()` ของ listener หลังจากการรีเฟรชทุกครั้งที่ทำให้ flag เปลี่ยนไป listener จะถูกเก็บไว้แบบ weak reference |
| `removeUpdateListener(_:)` | ลบ listener ที่เพิ่มไว้ด้วย `addUpdateListener(_:)` |
| `logImpression(_:)` | ส่ง Event `PW_FeatureFlagImpression` สำหรับกฎและสถานะปัจจุบันของ flag |