NotificationCenter — สาระสำคัญ หลักการทำงาน และสถาปัตยกรรมการแจ้งเตือน

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-18 เวลาอ่าน: 10 นาที

NotificationCenter เป็นกลไกระบบใน iOS สำหรับการส่งและรับการแจ้งเตือนระหว่างส่วนประกอบของแอปพลิเคชันโดยไม่ต้องมีการเชื่อมต่อโดยตรงระหว่างผู้ส่งและผู้รับ โดยอิงจากรูปแบบ Observer NotificationCenter ช่วยให้ออบเจกต์สามารถสมัครรับเหตุการณ์และตอบสนองต่อเหตุการณ์เหล่านั้นแบบไม่พร้อมกัน ตามเอกสารของ Apple (2025) NSNotificationCenter รองรับทั้งการส่งการแจ้งเตือนแบบซิงโครนัสผ่าน post(name:object:) และการส่งแบบเลื่อนเวลาผ่าน NotificationQueue ศูนย์การแจ้งเตือนทำงานภายในกระบวนการเดียวและไม่ข้ามขอบเขตของแอปพลิเคชัน

ประเด็นสำคัญ

  • NotificationCenter — การใช้งานรูปแบบ Observer สำหรับการแลกเปลี่ยนเหตุการณ์ระหว่างส่วนประกอบ iOS
  • addObserver สมัครออบเจกต์เพื่อรับการแจ้งเตือนด้วยชื่อเฉพาะและออบเจกต์ผู้ส่ง
  • post(name:object:) ส่งการแจ้งเตือนไปยังผู้สังเกตการณ์ที่สมัครไว้ทั้งหมดแบบซิงโครนัส
  • removeObserver ต้องเรียกใช้ใน deinit มิฉะนั้นจะเกิดข้อขัดข้องเมื่อส่งการแจ้งเตือน
  • NotificationQueue ช่วยให้เลื่อนการแจ้งเตือนสำหรับการส่งแบบไม่พร้อมกัน

NotificationCenter คืออะไร?

NotificationCenter (NSNotificationCenter) เป็นกลไกในตัวของ iOS สำหรับการสื่อสารแบบ耦合กันอย่างหลวม ๆ ระหว่างออบเจกต์ รูปแบบ Observer ช่วยให้ออบเจกต์หนึ่ง (ผู้ส่ง) แจ้งให้ออบเจกต์อื่น ๆ (ผู้สังเกตการณ์) ทราบเกี่ยวกับเหตุการณ์โดยไม่ต้องอ้างอิงถึงโดยตรง NotificationCenter ทำงานกับสามเอนทิตี: Notification.Name (ตัวระบุการแจ้งเตือน), Notification (คอนเทนเนอร์พร้อมข้อมูล) และ NotificationCenter (ตัวจัดส่ง) แต่ละแอปพลิเคชันมี default center ที่ใช้ร่วมกัน

NSNotification และ Notification.Name

Notification.Name เป็นโครงสร้างที่ระบุประเภทการแจ้งเตือน สร้างขึ้นผ่าน extension Name: Notification.Name(“MyNotification”) Notification เป็นออบเจกต์ที่ประกอบด้วย name, object (ผู้ส่ง) และ userInfo (พจนานุกรมพร้อมข้อมูล) การแจ้งเตือนของระบบถูกประกาศเป็นค่าคงที่: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification การแจ้งเตือนแบบกำหนดเองควรจัดกลุ่มผ่าน extension เพื่อหลีกเลี่ยงการชนกันของชื่อ ชื่อควรเป็นแบบโดเมนย้อนกลับ

swift
// การกำหนดการแจ้งเตือนแบบกำหนดเอง
extension Notification.Name {
    static let dataDidUpdate =
        Notification.Name("com.app.dataDidUpdate")
    static let userLoggedOut =
        Notification.Name("com.app.userLoggedOut")
}

// การส่งการแจ้งเตือนพร้อมข้อมูล
let userInfo: [String: Any] = [
    "userId": 123,
    "timestamp": Date()
]
NotificationCenter.default.post(
    name: .dataDidUpdate,
    object: nil,
    userInfo: userInfo
)

การเพิ่มผู้สังเกตการณ์ (addObserver)

ผู้สังเกตการณ์สมัครรับการแจ้งเตือนผ่านเมธอด addObserver(_:selector:name:object:) Selector คือเมธอดที่จะถูกเรียกเมื่อได้รับการแจ้งเตือน พารามิเตอร์ object ช่วยให้กรองการแจ้งเตือนจากผู้ส่งที่เฉพาะเจาะจงได้ ถ้า object เป็น nil ผู้สังเกตการณ์จะได้รับการแจ้งเตือนทั้งหมดที่มีชื่อระบุจากผู้ส่งใด ๆ ตั้งแต่ iOS 9 เป็นต้นมา addObserver ไม่ต้องการการลบด้วยตนเองสำหรับ API แบบ block-based แต่ selector-based ยังคงต้องการ removeObserver

swift
// การสมัครรับการแจ้งเตือน (แบบ selector)
NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleDataUpdate),
    name: .dataDidUpdate,
    object: nil
)

@objc func handleDataUpdate(_ notification: Notification) {
    guard let userId = notification.userInfo?["userId"] as? Int else { return }
    updateUI(for: userId)
}

// การสมัครรับการแจ้งเตือน (แบบ block, iOS 9+)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
    forName: .dataDidUpdate,
    object: nil,
    queue: .main
) { [weak self] notification in
    guard let self else { return }
    self.handleNotification(notification)
}

NotificationCenter ทำงานอย่างไร?

NotificationCenter เก็บตารางการแมป (ชื่อ → ชุดผู้สังเกตการณ์) เมื่อผู้ส่งเรียก post(name:object:) ศูนย์การแจ้งเตือนจะวนซ้ำผู้สังเกตการณ์ทั้งหมดที่สมัครชื่อนั้นแบบซิงโครนัสและเรียก selector หรือบล็อกของพวกเขา คุณสมบัติสำคัญ: post บล็อกเธรดปัจจุบันจนกว่าตัวจัดการทั้งหมดจะเสร็จสมบูรณ์ ถ้าตัวจัดการดำเนินการที่หนักหน่วง จะทำให้ผู้ส่งล่าช้า NotificationQueue แก้ปัญหานี้โดยเลื่อนการส่งการแจ้งเตือน

การส่งแบบซิงโครนัส (post)

เมธอด post(name:object:userInfo:) ส่งการแจ้งเตือนทันทีไปยังผู้สังเกตการณ์ทั้งหมด การเรียกเป็นแบบซิงโครนัส — โค้ดหลัง post จะทำงานหลังจากตัวจัดการทั้งหมดเสร็จสมบูรณ์เท่านั้น ลำดับการเรียกผู้สังเกตการณ์ไม่ได้รับการรับประกันและอาจเปลี่ยนแปลงระหว่างการเรียกใช้ สำหรับการประมวลผลตามลำดับ ให้ใช้ NotificationQueue พร้อม coalescing อย่าเรียก post ภายในตัวจัดการของการแจ้งเตือนเดียวกัน — จะทำให้เกิดการเรียกซ้ำไม่สิ้นสุด

การส่งแบบเลื่อนเวลา (NotificationQueue)

NotificationQueue เพิ่มการแจ้งเตือนไปยังคิวสำหรับการส่งแบบไม่พร้อมกัน รองรับ coalescing (การรวมการแจ้งเตือนที่เหมือนกัน) และการเลือกคิวส่ง (asap, idle, modal) Coalescing มีประโยชน์สำหรับเหตุการณ์ที่เกิดขึ้นบ่อย (ความคืบหน้าการดาวน์โหลด) เมื่อคุณต้องการแจ้งเตือนด้วยค่าเท่านั้น NotificationQueue ใช้รันลูปเพื่อทำงาน ดังนั้นจึงทำงานเฉพาะในเธรดที่มีรันลูปที่ทำงานอยู่

swift
// การส่งแบบเลื่อนเวลาผ่าน NotificationQueue
let notification = Notification(
    name: .dataDidUpdate,
    object: self,
    userInfo: ["progress": 0.5]
)

// Coalescing: การแจ้งเตือนหลายรายการรวมเป็นหนึ่งเดียว
NotificationQueue.default.enqueue(
    notification,
    postingStyle: .whenIdle,
    coalesceMask: .onName,
    forModes: [.common]
)

// การส่งแบบไม่พร้อมกันผ่าน DispatchQueue
DispatchQueue.main.async {
    NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}

Notification vs Delegate vs KVO

iOS มีกลไกหลักสามประการสำหรับการสื่อสารระหว่างออบเจกต์: NotificationCenter, Delegate และ KVO (Key-Value Observing) แต่ละกลไกแก้ปัญหาเรื่องการแจ้งเตือน แต่มีข้อแลกเปลี่ยนที่แตกต่างกันในด้านการ耦合กัน ประสิทธิภาพ และความปลอดภัยของประเภท การเลือกกลไกขึ้นอยู่กับความสัมพันธ์แบบหนึ่งต่อหนึ่งหรือหนึ่งต่อหลาย และความจำเป็นในการถ่ายโอนข้อมูล

ลักษณะNotificationCenterDelegateKVO
การ耦合กันหลวม (ชื่อการแจ้งเตือน)แน่น (โปรโตคอล)ปานกลาง (คีย์)
ความสัมพันธ์หนึ่งต่อหลายหนึ่งต่อหนึ่งหนึ่งต่อหลาย
ความปลอดภัยของประเภทต่ำ (userInfo เป็น Dictionary)สูง (เมธอดของโปรโตคอล)ปานกลาง (Any?)
ประสิทธิภาพปานกลาง (การวนซ้ำตาราง)สูง (เรียกโดยตรง)ต่ำ (NSObject)
ความไม่พร้อมกันซิงโครนัส (post บล็อก)ซิงโครนัสในเธรดของผู้ส่งซิงโครนัสเมื่อเปลี่ยน

เมื่อใดควรเลือก NotificationCenter

NotificationCenter เหมาะสำหรับเหตุการณ์ที่ส่วนประกอบอิสระหลายตัวต้องตอบสนอง ตัวอย่าง: การเปลี่ยนแปลงการตั้งค่าแอปพลิเคชัน การออกจากระบบของผู้ใช้ การรับการแจ้งเตือน push ในพื้นหลัง NotificationCenter ยังเหมาะสำหรับโมดูลที่耦合กันอย่างหลวม ๆ (คุณสมบัติ A ไม่ควรรู้เกี่ยวกับคุณสมบัติ B) ข้อเสียคือขาดความปลอดภัยของประเภท: คีย์ userInfo เป็นสตริง ไม่ใช่ enum

เมื่อใดควรเลือก Delegate หรือ KVO

Delegate เป็นตัวเลือกสำหรับความสัมพันธ์แบบหนึ่งต่อหนึ่งที่มีสัญญาชัดเจน (tableView.delegate) Delegate เร็วกว่าและปลอดภัยกว่าตามประเภท KVO เป็นตัวเลือกสำหรับการสังเกตการเปลี่ยนแปลงของคุณสมบัติเฉพาะของโมเดล (isLoading, progress) KVO ต้องการการสืบทอดจาก NSObject และอาจทำให้เกิดความยุ่งยากในการดีบัก (คีย์สตริงมหัศจรรย์) ใน Swift สมัยใหม่ Combine และลำดับ async แทนที่ทั้งสามแนวทาง

AddObserver: การแจ้งเตือนแบบซิงโครนัสและไม่พร้อมกัน

เมธอด addObserver รองรับสองรูปแบบการสมัคร: selector-based (แบบดั้งเดิม) และ block-based (พร้อม closure) Selector-based ต้องการความเข้ากันได้ของ @objc และการลบผู้สังเกตการณ์ด้วยตนเอง Block-based (iOS 9+) อนุญาตให้ใช้ capture list และถูกจัดการโดย OS โดยอัตโนมัติเมื่อใช้บล็อกโดยไม่มีการอ้างอิงที่แน่นหนา Block-based ยังรองรับคิว — ผู้สังเกตการณ์ได้รับการแจ้งเตือนในคิวที่ระบุ

addObserver แบบ selector-based

วิธีการสมัครแบบดั้งเดิมผ่าน selector เมธอดตัวจัดการต้องถูกทำเครื่องหมายด้วย @objc และรับ Notification แบบเลือกได้ ข้อดี: สามารถใช้ได้โดยคลาสใด ๆ รวมถึง Objective-C ดั้งเดิม ข้อเสีย: ขาดความปลอดภัยของประเภทของ selector ความเสี่ยงในการพิมพ์ชื่อ selector ผิด removeObserver ที่จำเป็นใน deinit ถ้าผู้สังเกตการณ์ถูกลบก่อนออบเจกต์ ตัวจัดการจะไม่ถูกเรียก

addObserver แบบ block-based

API แบบ block-based รับ closure ที่ทำงานเมื่อได้รับการแจ้งเตือน พารามิเตอร์ queue กำหนดว่าบล็อกจะทำงานในคิวใด — คิวหลักสำหรับการอัปเดต UI หรือคิวพื้นหลังสำหรับประมวลผลข้อมูล ค่าที่ส่งคืน NSObjectProtocol ใช้สำหรับลบผู้สังเกตการณ์: NotificationCenter.default.removeObserver(observer) แบบ block-based เป็นที่นิยมใน Swift สมัยใหม่

swift
protocol NotificationToken {
    func dispose()
}

extension NotificationCenter {
    func observe(
        name: NSNotification.Name,
        object: Any? = nil,
        queue: OperationQueue? = .main,
        using block: @escaping (Notification) -> Void
    ) -> NotificationToken {
        let observer = addObserver(forName: name, object: object,
                                   queue: queue, using: block)
        return NotificationTokenWrapper(observer: observer, center: self)
    }
}

// การใช้กับการลบอัตโนมัติ
class ViewModel {
    private var tokens: [NotificationToken] = []

    func startObserving() {
        let token = NotificationCenter.default.observe(
            name: .dataDidUpdate,
            queue: .main
        ) { [weak self] notification in
            self?.handleUpdate(notification)
        }
        tokens.append(token)
    }

    deinit {
        tokens.forEach { $0.dispose() }
    }
}

การจัดการหน่วยความจำและการลบผู้สังเกตการณ์

การรั่วไหลของหน่วยความจำ เป็นหนึ่งในปัญหาหลักเมื่อทำงานกับ NotificationCenter ถ้าผู้สังเกตการณ์ไม่ถูกลบก่อนการจัดสรรคืน เมื่อส่งการแจ้งเตือน ศูนย์จะพยายามเรียกเมธอดบนออบเจกต์ที่ถูกจัดสรรคืนแล้ว ทำให้เกิด EXC_BAD_ACCESS ตั้งแต่ iOS 9 เป็นต้นมา block-based addObserver ใช้การอ้างอิงแบบอ่อน แต่ selector-based ยังคงต้องการ removeObserver ด้วยตนเอง แนวปฏิบัติที่ดีที่สุด: ลบผู้สังเกตการณ์ใน deinit

เมื่อใดควรเรียก removeObserver

Selector-based: เรียก NotificationCenter.default.removeObserver(self) ใน deinit เสมอ ถ้าผู้สังเกตการณ์สมัครรับการแจ้งเตือนหลายรายการ คุณสามารถลบทั้งหมดพร้อมกัน (ไม่มีพารามิเตอร์) หรือเฉพาะรายการตามชื่อ Block-based: ลบผ่าน removeObserver ด้วยโทเค็นที่ส่งคืนจาก addObserver สำหรับ block-based บน iOS 9+ จะไม่เกิดการรั่วไหล แต่การลบยังคงแนะนำเพื่อประสิทธิภาพ: ผู้สังเกตการณ์ที่ถูกจัดสรรคืนจะไม่ถูกวนซ้ำระหว่าง post

swift
class SafeObserver {
    private var observers: [NSObjectProtocol] = []

    func addSubscriptions() {
        let token1 = NotificationCenter.default.addObserver(
            forName: .dataDidUpdate, object: nil,
            queue: .main) { [weak self] _ in
            self?.refreshData()
        }
        let token2 = NotificationCenter.default.addObserver(
            forName: .userLoggedOut, object: nil,
            queue: .main) { [weak self] _ in
            self?.logout()
        }
        observers.append(contentsOf: [token1, token2])
    }

    deinit {
        observers.forEach { NotificationCenter.default.removeObserver($0) }
    }

    private func refreshData() { }
    private func logout() { }
}

การอ้างอิงแบบอ่อนผ่านรูปแบบ Token

รูปแบบ Token ทำให้การจัดการผู้สังเกตการณ์เป็นอัตโนมัติ เมื่อสมัคร ออบเจกต์โทเค็น (NSObjectProtocol) จะถูกส่งคืน ซึ่งจะลบผู้สังเกตการณ์โดยอัตโนมัติเมื่อถูกจัดสรรคืน NotificationTokenWrapper จัดเก็บการอ้างอิงแบบอ่อนไปยัง NotificationCenter และโทเค็นผู้สังเกตการณ์ เรียก removeObserver ใน deinit ทำให้ NotificationCenter ใกล้เคียงกับแนวทาง Combine ที่ AnyCancellable จัดการวงจรชีวิตการสมัคร

NotificationCenter ในสภาพแวดล้อมแบบหลายเธรด

ความปลอดภัยของเธรด: NotificationCenter รับประกันว่า post สามารถเรียกจากเธรดใดก็ได้ และผู้สังเกตการณ์ทั้งหมดจะได้รับการแจ้งเตือนในเธรดเดียวกับที่ post ถูกเรียก นี่เป็นสิ่งสำคัญสำหรับแอปพลิเคชันแบบหลายเธรด: ถ้าการแจ้งเตือนถูกส่งจากเธรดพื้นหลัง ตัวจัดการก็จะทำงานในเธรดพื้นหลังด้วย สำหรับการอัปเดต UI ให้ส่งการจัดการไปยังคิวหลักผ่าน DispatchQueue.main.async

ความปลอดภัยของเธรดของ post และ addObserver

NotificationCenter ปลอดภัยสำหรับเธรดสำหรับการเรียก post และ addObserver จากเธรดที่แตกต่างกัน การซิงโครไนซ์ภายใน ใช้การล็อก ดังนั้นการเรียก post บ่อยครั้งจากหลายเธรดอาจสร้างการแย่งชิง สำหรับสถานการณ์ที่มีโหลดสูง (ความคืบหน้าการดาวน์โหลด 1,000 ไฟล์) ให้ใช้คิวการแจ้งเตือนแยกต่างหากหรือ Combine publisher NotificationQueue ที่มี postingStyle .now เทียบเท่ากับ post โดยตรง

การส่งแบบไม่พร้อมกันผ่าน Combine

NotificationCenter รองรับ Combine publisher ผ่าน NotificationCenter.default.publisher(for:object:) Publisher เปลี่ยนการแจ้งเตือนแต่ละครั้งเป็นเหตุการณ์ Combine ที่สามารถแปลงผ่าน map, filter, debounce และ throttle ซึ่งแก้ปัญหาการส่งแบบซิงโครนัส: Combine ประมวลผลการแจ้งเตือนแบบไม่พร้อมกันบน Scheduler ที่ระบุ NotificationCenter.publisher เป็นสะพานเชื่อมระหว่างกลไกดั้งเดิมและการเขียนโปรแกรมเชิงรับสมัยใหม่

swift
import Combine

class ReactiveViewModel {
    private var cancellables = Set<AnyCancellable>()

    func setupCombineSubscription() {
        NotificationCenter.default
            .publisher(for: .dataDidUpdate)
            .receive(on: DispatchQueue.main)
            .compactMap { $0.userInfo?["progress"] as? Float }
            .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
            .sink { [weak self] progress in
                self?.progressLabel.text = "\(Int(progress * 100))%"
            }
            .store(in: &cancellables)
    }
}

คำถามที่พบบ่อย

NotificationCenter ปลอดภัยสำหรับเธรดหรือไม่?

ใช่ NotificationCenter ปลอดภัยสำหรับเธรดสำหรับการเรียก post และ addObserver จากเธรดใด ๆ อย่างไรก็ตาม ตัวจัดการทำงานในเธรดเดียวกับที่เรียก post สำหรับการอัปเดต UI ให้ใช้ queue: .main ใน block-based addObserver หรือ DispatchQueue.main.async ภายในตัวจัดการ Combine publisher ที่มี receive(on:) ก็แก้ปัญหาเธรดได้เช่นกัน

จะเกิดอะไรขึ้นถ้าไม่ลบผู้สังเกตการณ์?

Selector-based: ข้อขัดข้อง EXC_BAD_ACCESS เมื่อส่งการแจ้งเตือนหลังจากการจัดสรรคืนผู้สังเกตการณ์ Block-based (iOS 9+): ไม่มีการรั่วไหลเนื่องจากการอ้างอิงแบบอ่อน แต่ศูนย์การแจ้งเตือนยังคงเก็บบล็อกไว้ในหน่วยความจำจนกว่าจะมีการเรียก removeObserver อย่างชัดเจน ขอแนะนำให้ลบผู้สังเกตการณ์ใน deinit หรือใช้รูปแบบ Token สำหรับการจัดการอัตโนมัติ

ความแตกต่างระหว่าง NotificationCenter และ KVO คืออะไร?

NotificationCenter เป็นกลไกการกระจายเสียงสำหรับเหตุการณ์ที่กำหนดเองระหว่างส่วนประกอบที่ไม่เกี่ยวข้อง KVO สังเกตการเปลี่ยนแปลงของคุณสมบัติเฉพาะของออบเจกต์เฉพาะ KVO ต้องการการสืบทอด NSObject และแจ้งเตือนโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงคุณสมบัติผ่าน setter NotificationCenter แจ้งเตือนเมื่อเรียก post อย่างชัดเจนเท่านั้น สำหรับการสังเกตโมเดล KVO หรือ Combine เป็นที่นิยมกว่า

มี NotificationCenter กี่แห่งในแอปพลิเคชัน?

หนึ่ง default center ต่อกระบวนการแอปพลิเคชัน สามารถสร้างศูนย์เพิ่มเติมผ่าน NotificationCenter() ได้ แต่ในทางปฏิบัติจะใช้ default ที่ใช้ร่วมกัน แต่ละศูนย์ทำงานอย่างอิสระ — post ในหนึ่งศูนย์จะไม่ถูกส่งไปยังผู้สังเกตการณ์ของอีกศูนย์ สำหรับการแยกโมดูล ให้ใช้เนมสเปซ Name แยกต่างหากผ่านชื่อการแจ้งเตือนแบบโดเมนย้อนกลับ

Combine แทนที่ NotificationCenter หรือไม่?

บางส่วน Combine มี NotificationCenter.Publisher ซึ่งห่อ NotificationCenter ในสตรีมเชิงรับ Combine แก้ปัญหาการซิงโครนัส (ผ่าน receive(on:)) เพิ่มตัวดำเนินการแปลง และการจัดการการสมัครอัตโนมัติ (AnyCancellable) อย่างไรก็ตาม NotificationCenter ยังคงอยู่สำหรับการแจ้งเตือนระบบ iOS (UIApplication, UIKeyboard) และโค้ดดั้งเดิม Combine คือการปรับปรุง ไม่ใช่การแทนที่

สรุป

  • NotificationCenter — การใช้งานรูปแบบ Observer สำหรับการสื่อสารแบบ耦合กันอย่างหลวม ๆ แบบหนึ่งต่อหลายใน iOS
  • post ส่งการแจ้งเตือนแบบซิงโครนัสไปยังผู้สังเกตการณ์ทั้งหมดในเธรดปัจจุบัน บล็อกผู้ส่ง
  • addObserver รองรับการสมัครแบบ selector-based (กับ @objc) และ block-based (กับ capture list และคิว)
  • removeObserver จำเป็นใน deinit สำหรับการสมัครแบบ selector-based มิฉะนั้นจะเกิดข้อขัดข้อง
  • NotificationQueue ให้การส่งแบบเลื่อนเวลาพร้อม coalescing สำหรับเหตุการณ์ที่เกิดขึ้นบ่อย
  • ความปลอดภัยของเธรด รับประกันการทำงานจากเธรดใด ๆ แต่ตัวจัดการทำงานในเธรดของผู้ส่ง
  • ใช้รูปแบบ Token หรือ Combine publisher สำหรับการจัดการการสมัครที่ปลอดภัยและทันสมัย

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม