Guideline 2.3.1: ฟีเจอร์ซ่อนเร้นและการอัปเดต OTA
โดย Mehdi Azizi
เผยแพร่เมื่อ , 9 นาที
Guideline 2.3.1: Apple ปฏิเสธฟีเจอร์ที่ซ่อนอยู่ ยังไม่เปิดใช้ หรือไม่ได้เขียนไว้ในโน้ตรีวิว ดูว่าอะไรเปลี่ยนจากระยะไกลได้ และอะไรต้องทำ build ใหม่
Guideline 2.3.1 ห้ามมีฟีเจอร์ที่ซ่อนอยู่ ฟีเจอร์ที่ฝังไว้รอเปิดทีหลัง หรือฟีเจอร์ที่ไม่ได้บอกไว้ ทุกอย่างที่แอปของคุณทำต้องมองเห็นได้ทั้งสำหรับผู้ใช้และผู้รีวิว ฟีเจอร์ใหม่ทุกอย่างต้องอธิบายอย่างเจาะจงในโน้ตรีวิว การอัปเดตจากระยะไกลแก้ไขสิ่งที่ Apple เห็นแล้วได้ แต่เพิ่มสิ่งที่ Apple ไม่เคยเห็นไม่ได้เด็ดขาด
ผมเผยแพร่แอป iOS ด้วย Expo และทุกแอปจะฝัง EAS Update ไว้ตั้งแต่ build แรกเพื่อส่งการแก้บั๊ก กฎของผมคือ ไม่ปล่อยอัปเดตจากระยะไกลเลยระหว่างที่มีเวอร์ชันอยู่ในการรีวิว คู่มือนี้อ้างอิงจากข้อความของ Apple ข้อตกลงไลเซนส์ เอกสารของ Expo และกรณีถูกปฏิเสธที่มีคนเผยแพร่ไว้
Guideline 2.3.1 ว่าไว้อย่างไร
2.3.1 เป็นข้อแรกของหมวด 2.3 "Accurate Metadata" ใน App Review Guidelines ภายใต้ส่วนที่ 2 "Performance" ข้อความเริ่มว่า "Don't include any hidden, dormant, or undocumented features in your app; your app's functionality should be clear to end users and App Review." พูดง่ายๆ คือ ห้ามมีอะไรซ่อน อะไรที่ฝังไว้รอ หรืออะไรที่ไม่ได้บอกไว้
ส่วนต่อมาครอบคลุมการส่งทุกครั้ง: "All new features, functionality, and product changes must be described with specificity in the Notes for Review section of App Store Connect (generic descriptions will be rejected) and accessible for review." ฟีเจอร์ใหม่ทุกอย่างต้องอธิบายอย่างเจาะจงในโน้ตรีวิว และผู้รีวิวต้องเข้าถึงได้ ดังนั้นแค่เขียนว่า "แก้บั๊กและปรับปรุงประสิทธิภาพ" นั้นไม่พอ
ย่อหน้าเดียวกันยังครอบคลุมการตลาดที่ทำให้เข้าใจผิดด้วย เช่น สัญญาว่ามีเนื้อหาหรือบริการที่ไม่มีอยู่จริง หรือแสดงราคาปลอม ไม่ว่าจะใน App Store หรือที่อื่น
ส่วนนี้ช่วยผมได้ตอนทำ Nolera ตอนอ่านหน้าแอปทวนก่อนปล่อย ผมเจอสองประโยคที่พิสูจน์ไม่ได้ คือ "อินเทอร์เฟซภาษาฝรั่งเศสเท่านั้น" ซึ่งไม่จริงแล้ว และ "แพ็กที่คนเลือกมากที่สุด" ทั้งที่แอปยังไม่เคยเผยแพร่ โน้ตถึงผู้รีวิวก็อธิบายสภาพผลิตภัณฑ์ที่ล้าสมัยไปแล้ว นี่คือสิ่งที่ส่วนนี้มุ่งเป้าพอดี ส่วน 2.3.10 จะเน้นที่หน้าแอปเป็นหลัก
ข้อความปฏิเสธ 2.3.1 หมายความว่าอะไร
การปฏิเสธจะมีหัวข้อว่า "Guideline 2.3.1 - Performance" บางครั้งตามด้วย "Hidden Features" นี่คือสองข้อความที่นักพัฒนา React Native เผยแพร่ไว้:
- มีนาคม 2024 ในทิกเก็ต GitHub ของ CodePush: "The app contains hidden features." โดยไม่มีรายละเอียดอื่น นักพัฒนาขอคำอธิบายเพิ่ม และได้รับข้อความเสริมภายใต้ 2.5.2: "Your app, extension, or linked framework appears to contain code designed explicitly with the capability to change the app's behavior or functionality after App Review approval." ทิกเก็ตไม่ได้บอกว่าแอปผ่านในภายหลังหรือไม่
- กุมภาพันธ์ 2023 ในอีกทิกเก็ตหนึ่ง Apple ชี้ไปที่ "CodePush-related remote code downloading/updating support" ในไบนารี
ผมตีความว่า ผู้รีวิวไม่ได้เห็นอัปเดตของคุณ แต่เห็นโค้ดที่สามารถรับอัปเดตได้ ทั้งสองกรณีเกี่ยวกับ CodePush ผมยังไม่เจอกรณีปฏิเสธล่าสุดที่เปิดเผยต่อสาธารณะซึ่งชี้ไปที่ EAS Update แต่นั่นก็ไม่ได้พิสูจน์อะไร
การอัปเดตจากระยะไกล: สิ่งที่ 2.5.2 และข้อตกลงไลเซนส์อนุญาต
กฎ 2.5.2 ซึ่งผมพูดถึงในคู่มือเรื่องโหมดทำงานเบื้องหลังด้วย ห้ามแอป "download, install, or execute code which introduces or changes features or functionality of the app"
ข้อ 3.3.1(B) ของ Apple Developer Program License Agreement ห้ามโค้ดที่รันได้ซึ่งดาวน์โหลดมา แต่ระบุว่า "Interpreted code may be downloaded to an Application" โดยมีสามเงื่อนไข: โค้ดนั้นไม่เปลี่ยนจุดประสงค์หลักของแอป ไม่หลบเลี่ยงการเซ็นโค้ด sandbox หรือระบบความปลอดภัยของระบบปฏิบัติการ และไม่สร้างร้านค้าสำหรับแอปอื่น
JavaScript ของแอป Expo เป็นโค้ดแบบ interpreted ในความเห็นผม EAS Update จึงอยู่ในกรอบนี้ เอกสารของ Expo ย้ำว่าอัปเดตต้องเป็นไปตามกฎของ App Store และ "changes to your app's behavior need to be reviewed" สำหรับบั๊กร้ายแรง Expo แนะนำให้ส่งการแก้ไขจากระยะไกล แล้วส่งเวอร์ชันใหม่ที่รวมการแก้ไขนั้นไปด้วย
เส้นของผมคือ แก้และปรับสิ่งที่มีอยู่แล้ว ได้ เปลี่ยนว่าแอปคืออะไรหรือขายอะไร ไม่ได้
อะไรเปลี่ยนจากระยะไกลได้ และอะไรต้องทำ build ใหม่
นี่คือตารางที่ผมใช้ สรุปจากหน้าของ Apple และ Expo ที่อ้างถึงในบทความนี้ แถว "ฟรีกลายเป็นเสียเงิน" และสองแถวสุดท้ายเป็นการตีความของผมเอง
| การเปลี่ยนแปลง | ช่องทาง | เหตุผล |
|---|---|---|
| บั๊ก JavaScript | อัปเดตจากระยะไกล | Expo ออกแบบมาให้ใช้แบบนี้ |
| ข้อความ คำแปล สไตล์ | อัปเดตจากระยะไกล | Expo ออกแบบมาให้ใช้แบบนี้ |
| ราคาการสมัครสมาชิก | App Store Connect | ไม่มีการแก้โค้ด |
| ข้อความโปรโมต | App Store Connect | ไม่ต้องส่งใหม่ |
| การซื้อประเภทที่เคยผ่านแล้ว | รีวิวเฉพาะการซื้อนั้น | กฎของ App Store Connect |
| การซื้อแรกของประเภทนั้น | เวอร์ชันใหม่ | กฎของ App Store Connect |
| ฟีเจอร์ใหม่ | เวอร์ชันใหม่ | 2.3.1 และ 2.5.2 |
| ฟรีกลายเป็นเสียเงิน | เวอร์ชันใหม่ | ต้องแก้หน้าแอปและโน้ต |
| สิทธิ์การเข้าถึง, Info.plist | build ใหม่ | ไฟล์อยู่ในไบนารี |
| โมดูล native, SDK ของ Expo | build ใหม่ | โค้ด native เปลี่ยน |
| สวิตช์ที่ปิดฟีเจอร์ | การตั้งค่าจากระยะไกล | ยอมรับได้ถ้าชั่วคราว |
| สวิตช์ที่เปิดเผยฟีเจอร์ | ห้าม | ฟีเจอร์ไม่เคยผ่านรีวิว |
ข้อความโปรโมตเปลี่ยนได้ "without requiring an updated submission" ตามหน้าของ Apple เรื่องข้อมูลเวอร์ชัน การซื้อแรกของแต่ละประเภทต้องส่งไปพร้อมเวอร์ชันใหม่ ส่วนครั้งต่อๆ ไปส่งเดี่ยวได้ ตามหน้าช่วยเหลือเรื่องการส่งการซื้อ Expo จัดโค้ด native สิทธิ์การเข้าถึง และเวอร์ชัน SDK ไว้ในกลุ่มที่ต้องใช้ไบนารีใหม่ เหมือนกรณี ITMS-90683 สวิตช์ที่ปิดฟีเจอร์ที่พังไม่ได้เพิ่มอะไรใหม่ ตราบใดที่ไม่ได้เอาสิ่งที่สมาชิกจ่ายเงินไปแล้วออก
สวิตช์ระยะไกลแบบไม่โดนปฏิเสธ
สวิตช์ระยะไกล หรือ feature flag ไม่ได้ผิดกฎในตัวเอง ทุกอย่างขึ้นอยู่กับว่ามันซ่อนอะไรไว้ กฎของผมคือ:
- ทุกอย่างที่สวิตช์แสดงได้ต้องมีอยู่และเปิดไว้สำหรับผู้รีวิว และระบุชื่อไว้ในโน้ตรีวิว
- การทดสอบ A/B ต้องเปรียบเทียบสองแบบที่ผู้รีวิวเห็นได้ ไม่ใช่แบบที่ผ่านรีวิวกับแบบที่ไม่เคยมีใครเห็น
- ไม่มีโค้ดที่ตรวจจับการรีวิวเพื่อทำงานต่างออกไป นี่คือนิยามของฟีเจอร์ซ่อนเร้นเลย
- paywall ที่ควบคุมจากระยะไกลต้องแสดงเฉพาะสินค้าที่ผ่านการอนุมัติแล้ว และทุกแบบต้องเป็นไปตามกฎ 3.1.2
โหมดเดโมและบัญชีทดสอบ
Guideline 2.1 กำหนดให้มีบัญชีเดโมถ้าแอปของคุณมีระบบล็อกอิน และเซิร์ฟเวอร์ต้องเปิดอยู่ ถ้าข้อกำหนดทางกฎหมายหรือความปลอดภัยทำให้คุณให้บัญชีไม่ได้ โหมดเดโมในตัวแอปใช้แทนได้ แต่ต้อง "with prior approval by Apple" เท่านั้น และต้องแสดงทุกฟีเจอร์ หน้าของ Apple เรื่องข้อมูลเวอร์ชันเสริมว่า บัญชีต้องไม่หมดอายุ และบัญชีเพิ่มเติมให้ใส่ในโน้ต ซึ่งจำกัดไว้ที่ 4,000 ไบต์
ความเกี่ยวข้องกับ 2.3.1 คือ โหมดเดโมหรือบัญชีทดสอบที่เปิดหน้าจอซึ่งปกติมองไม่เห็นต้องได้รับการอธิบาย ท่าทางลับ รหัส หรือบัญชีพิเศษที่ไม่ได้แจ้ง ถือเป็นฟีเจอร์ซ่อนเร้น ระบุทุกช่องทางเข้าพิเศษไว้ในโน้ต พร้อมบอกว่ามันเปิดอะไร
กฎของผม: ไม่ปล่อยอัปเดตจากระยะไกลระหว่างรีวิว
ทำไมผมใส่ EAS Update ในทุกแอป: ใน Solys แอปนาฬิกาปลุกของผม มีบั๊ก JavaScript ที่ทำให้แอปเงียบไปเลยทันทีที่เสียงเอฟเฟกต์เล่นพลาดแค่ครั้งเดียว การแก้มีแค่สามบรรทัด แต่ต้อง build ใหม่ทั้งหมดและผ่านรีวิวถึงจะส่งถึงผู้ใช้ได้ ตั้งแต่นั้นมา ทุกแอปของผมฝัง expo-updates ไว้ตั้งแต่ build แรก มันเป็นโมดูล native ไบนารีที่คอมไพล์โดยไม่มีมันจะไม่ได้รับอะไรเลยตลอดไป นี่คือการตั้งค่าของผมใน app.json:
{
"expo": {
"runtimeVersion": { "policy": "fingerprint" },
"updates": { "fallbackToCacheTimeout": 0, "checkAutomatically": "ON_LOAD" }
}
}
เมื่อใช้ fingerprint อัปเดตจะไปถึงเฉพาะไบนารีที่โค้ด native ตรงกัน (runtime versions) แต่ระหว่างที่มีเวอร์ชันรอหรือกำลังรีวิว ผมไม่ปล่อยอะไรเลย ด้วยเหตุผลสองข้อ
- ผู้รีวิวจะไม่เห็นการแก้ไขนั้น เมื่อตั้ง
fallbackToCacheTimeoutเป็น 0 การติดตั้งใหม่จะเริ่มด้วยโค้ดที่ฝังมา แล้วค่อยใช้อัปเดตในการเปิดครั้งถัดไป (เอกสาร expo-updates) ผู้รีวิวติดตั้ง เปิดครั้งเดียว แล้วทดสอบไบนารี Nolera เคยโดนปฏิเสธทั้งที่ผมส่งการแก้ไขจากระยะไกลไปแล้ว ผมเล่าเรื่องนี้ไว้ในคู่มือ 2.1 - สิ่งที่ผู้รีวิวทดสอบต้องเป็นสิ่งเดียวกับที่ผู้ใช้ได้รับ การเปลี่ยนพฤติกรรมระหว่างรีวิวทำให้เวอร์ชันที่ถูกตรวจกับเวอร์ชันที่ส่งถึงผู้ใช้ไม่ตรงกัน
โน้ตรีวิวที่เจาะจง
2.3.1 ปฏิเสธคำอธิบายกว้างๆ นี่คือเทมเพลตที่ผมจะใช้ เป็นภาษาอังกฤษ:
What's new in this version:
- [Feature]: [where to find it, how to test it].
Remote configuration:
- "[flag]" controls [feature]. It is ON for review.
Over-the-air updates:
- The app uses expo-updates for bug fixes only.
No update will be published during this review.
Demo account: see Sign-in information. It never expires.
ตอบกลับการปฏิเสธ 2.3.1 อย่างไร
ตอบใน App Store Connect ซึ่งเป็นที่ที่คุณตอบข้อความของ App Review
- ข้อความยังคลุมเครือ: ถามว่าหมายถึงฟีเจอร์ไหน เหมือนนักพัฒนาในทิกเก็ตปี 2024
- ฟีเจอร์จริงถูกอธิบายไม่ดี: บอกเส้นทางเข้าถึงที่แน่นอนพร้อมวิดีโอสั้นๆ แล้วเติมโน้ตให้ครบ
- สวิตช์เปิดเผยสิ่งที่ไม่เคยผ่านรีวิว: เอาออก หรือเปิดไว้และอธิบาย แล้ว build ใหม่
- โค้ดอัปเดตคือต้นเหตุ: อธิบายว่าใช้สำหรับแก้บั๊ก ภายใต้กรอบของข้อ 3.3.1(B) ในทิกเก็ตปี 2023 Apple ขอให้เอาออก
เช็กลิสต์ 2.3.1 ก่อนส่ง
- ฟีเจอร์ใหม่ทุกอย่างในเวอร์ชันนี้อธิบายไว้ในโน้ต พร้อมเส้นทางเข้าถึง
- สวิตช์ระยะไกลทุกตัวเปิดไว้สำหรับรีวิวและระบุชื่อในโน้ต
- ไม่มีโค้ดที่เปลี่ยนพฤติกรรมเมื่อเป็นผู้รีวิว
- ช่องทางเข้าพิเศษถูกอธิบายไว้: โหมดเดโม บัญชีทดสอบ ท่าทาง
- หน้าแอปและ paywall ไม่สัญญาสิ่งที่ build ไม่ได้ทำ
- ไม่มีการปล่อยอัปเดตจากระยะไกลระหว่างรีวิว
ตรวจเอกสารของคุณก่อนส่ง
Review Kit รวมเช็กลิสต์ก่อนส่งของผม เทมเพลตโน้ตสำหรับผู้ทดสอบ และ skill ของ Claude Code ที่ช่วยอ่านทวนโปรเจกต์ของคุณ ถ้าอยากได้คนช่วยดู จองเวลาหนึ่งชั่วโมงกับผม เราจะอ่านโน้ตรีวิวและการตั้งค่าอัปเดตของคุณด้วยกันผ่านวิดีโอคอล ขั้นตอนทั้งหมดอยู่ในหน้าแอปถูก Apple ปฏิเสธ
กำลังเตรียมปล่อยแอปแรกอยู่ใช่ไหม ทำแบบประเมินฟรี มันจะเรียงขั้นตอนให้เป็นลำดับ และส่งเช็กลิสต์กันโดน Apple ปฏิเสธของผมไปให้คุณ
คำถามที่พบบ่อย
"Guideline 2.3.1 - Performance - Hidden Features" หมายความว่าอะไร?
Apple มองว่าแอปของคุณมีฟีเจอร์ที่ซ่อนอยู่ ฝังไว้รอเปิด หรือไม่ได้เขียนไว้ในโน้ตรีวิว ข้อความนี้อาจหมายถึงโค้ดที่เปลี่ยนแอปได้หลังรีวิวด้วย บางครั้งมาพร้อมการปฏิเสธ 2.5.2
Apple อนุญาตให้ใช้ EAS Update ไหม?
ข้อตกลงไลเซนส์อนุญาตโค้ดแบบ interpreted ที่ดาวน์โหลดมา ตราบใดที่ไม่เปลี่ยนจุดประสงค์หลักของแอป แก้ไข ได้ เพิ่มฟีเจอร์ที่ไม่ผ่านรีวิว ไม่ได้
ใช้ feature flag ในแอป iOS ได้ไหม?
ได้ ถ้าทุกอย่างที่มันแสดงได้ผ่านการรีวิวแล้ว เปิดสวิตช์ทุกตัวไว้สำหรับรีวิวและอธิบายไว้ในโน้ต
ต้องแจ้งโหมดเดโมให้ผู้รีวิวรู้ไหม?
ต้อง โหมดเดโมในตัวแอปต้องได้รับอนุญาตจาก Apple ล่วงหน้า และต้องแสดงทุกฟีเจอร์
ปล่อยอัปเดตจากระยะไกลระหว่างรีวิวได้ไหม?
ไม่มีข้อไหนห้ามไว้ตรงๆ แต่ผู้รีวิวจะไม่เห็นมันในการเปิดครั้งแรก และมันทำให้ไม่ตรงกับเวอร์ชันที่ถูกรีวิว ผมเลยไม่ทำเลยทุกครั้ง

