๐Ÿ“ฑ Core NFC

NFC ํƒœ๊ทธ ์ฝ๊ธฐ/์“ฐ๊ธฐ ๋ฐ NDEF ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ํ”„๋ ˆ์ž„์›Œํฌ

iOS 11+NDEF ์ฝ๊ธฐ/์“ฐ๊ธฐ

โœจ Core NFC๋ž€?

Core NFC๋Š” iPhone์˜ NFC ์นฉ์„ ์‚ฌ์šฉํ•˜์—ฌ NFC ํƒœ๊ทธ๋ฅผ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. NDEF(NFC Data Exchange Format) ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์–ด URL, ํ…์ŠคํŠธ, ์—ฐ๋ฝ์ฒ˜ ์ •๋ณด ๋“ฑ์„ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ํƒœ๊ทธ์— ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Apple Pay๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  NFC ํ™œ์šฉ ์‚ฌ๋ก€์— ์‚ฌ์šฉ๋˜๋ฉฐ, iPhone 7 ์ด์ƒ์—์„œ ์ง€์›๋ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ํ•ต์‹ฌ ๊ธฐ๋Šฅ: NDEF ํƒœ๊ทธ ์ฝ๊ธฐ ยท ํƒœ๊ทธ ์“ฐ๊ธฐ(iOS 13+) ยท URL/ํ…์ŠคํŠธ ํŒŒ์‹ฑ ยท ๋‹ค์ค‘ ๋ ˆ์ฝ”๋“œ ์ง€์› ยท ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ๊ทธ ์ฝ๊ธฐ ยท ISO 7816/ISO 15693 ์ง€์›

๐Ÿ“– 1. NDEF ํƒœ๊ทธ ์ฝ๊ธฐ

NFCNDEFReaderSession์„ ์‚ฌ์šฉํ•˜์—ฌ NFC ํƒœ๊ทธ๋ฅผ ์Šค์บ”ํ•˜๊ณ  NDEF ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค.

NFCReaderManager.swift โ€” NFC ํƒœ๊ทธ ์ฝ๊ธฐ
import CoreNFC
import SwiftUI

@Observable
class NFCReaderManager: NSObject {
    var session: NFCNDEFReaderSession?
    var detectedMessages: [NFCNDEFMessage] = []
    var isScanning = false
    var lastError: String?

    // NFC ์ง€์› ์—ฌ๋ถ€ ํ™•์ธ
    var isNFCAvailable: Bool {
        NFCNDEFReaderSession.readingAvailable
    }

    // ํƒœ๊ทธ ์ฝ๊ธฐ ์‹œ์ž‘
    func startScanning() {
        guard isNFCAvailable else {
            lastError = "์ด ๊ธฐ๊ธฐ๋Š” NFC๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค"
            print("โŒ NFC ๋ฏธ์ง€์›")
            return
        }

        detectedMessages.removeAll()
        lastError = nil

        // NDEF Reader Session ์ƒ์„ฑ
        session = NFCNDEFReaderSession(
            delegate: self,
            queue: nil,
            invalidateAfterFirstRead: false
        )

        // ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋  ๋ฉ”์‹œ์ง€
        session?.alertMessage = "NFC ํƒœ๊ทธ๋ฅผ iPhone ์ƒ๋‹จ์— ๊ฐ€๊นŒ์ด ๋Œ€์ฃผ์„ธ์š”"

        // ์Šค์บ” ์‹œ์ž‘
        session?.begin()
        isScanning = true

        print("๐Ÿ” NFC ์Šค์บ” ์‹œ์ž‘")
    }

    // ์Šค์บ” ์ค‘์ง€
    func stopScanning() {
        session?.invalidate()
        isScanning = false
        print("โน๏ธ NFC ์Šค์บ” ์ค‘์ง€")
    }

    // NDEF ๋ ˆ์ฝ”๋“œ ํŒŒ์‹ฑ
    func parseNDEFRecords(from message: NFCNDEFMessage) -> [String] {
        var results: [String] = []

        for record in message.records {
            // Type Name Format
            let tnf = record.typeNameFormat

            switch tnf {
            case .nfcWellKnown:
                // URI ๋ ˆ์ฝ”๋“œ
                if let url = record.wellKnownTypeURIPayload() {
                    results.append("๐Ÿ”— URL: \(url.absoluteString)")
                }
                // Text ๋ ˆ์ฝ”๋“œ
                else if let text = String(data: record.payload, encoding: .utf8) {
                    results.append("๐Ÿ“ Text: \(text)")
                }

            case .absoluteURI:
                if let text = String(data: record.payload, encoding: .utf8) {
                    results.append("๐ŸŒ URI: \(text)")
                }

            case .media:
                let mimeType = String(data: record.type, encoding: .utf8) ?? "unknown"
                results.append("๐Ÿ“„ Media Type: \(mimeType)")

            case .empty:
                results.append("โšช๏ธ Empty record")

            @unknown default:
                results.append("โ“ Unknown record type")
            }

            // Payload ํฌ๊ธฐ
            results.append("  Size: \(record.payload.count) bytes")
        }

        return results
    }
}

๐Ÿ“ก 2. NDEF Reader ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ

ํƒœ๊ทธ ๋ฐœ๊ฒฌ ๋ฐ ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

NFCReaderManager+Delegate.swift โ€” Reader ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ
import CoreNFC

extension NFCReaderManager: NFCNDEFReaderSessionDelegate {
    // ํƒœ๊ทธ ๋ฐœ๊ฒฌ
    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        print("๐Ÿ“ฑ ํƒœ๊ทธ ๋ฐœ๊ฒฌ! ๋ฉ”์‹œ์ง€ ๊ฐœ์ˆ˜: \(messages.count)")

        // ๋ฉ”์‹œ์ง€ ์ €์žฅ
        detectedMessages.append(contentsOf: messages)

        // ๊ฐ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ
        for (index, message) in messages.enumerated() {
            print("  ๋ฉ”์‹œ์ง€ \(index + 1): \(message.records.count)๊ฐœ ๋ ˆ์ฝ”๋“œ")

            for (recordIndex, record) in message.records.enumerated() {
                print("    ๋ ˆ์ฝ”๋“œ \(recordIndex + 1):")

                // URL ํŒŒ์‹ฑ
                if let url = record.wellKnownTypeURIPayload() {
                    print("      ๐Ÿ”— URL: \(url.absoluteString)")
                }

                // Text ํŒŒ์‹ฑ
                if let text = parseTextPayload(record.payload) {
                    print("      ๐Ÿ“ Text: \(text)")
                }
            }
        }

        // ์„ฑ๊ณต ๋ฉ”์‹œ์ง€
        session.alertMessage = "โœ… \(messages.count)๊ฐœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค"
    }

    // ํƒœ๊ทธ ๋ฐœ๊ฒฌ (๊ณ ๊ธ‰ API)
    func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
        guard tags.count == 1 else {
            session.alertMessage = "โš ๏ธ ํƒœ๊ทธ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋งŒ ๊ฐ€๊นŒ์ด ๋Œ€์ฃผ์„ธ์š”"
            session.restartPolling()
            return
        }

        let tag = tags.first!

        // ํƒœ๊ทธ ์—ฐ๊ฒฐ
        session.connect(to: tag) { error in
            if let error = error {
                session.invalidate(errorMessage: "โŒ ํƒœ๊ทธ ์—ฐ๊ฒฐ ์‹คํŒจ: \(error.localizedDescription)")
                return
            }

            print("โœ… ํƒœ๊ทธ ์—ฐ๊ฒฐ ์„ฑ๊ณต")

            // NDEF ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ
            tag.queryNDEFStatus { status, capacity, error in
                if let error = error {
                    session.invalidate(errorMessage: "โŒ ์ƒํƒœ ํ™•์ธ ์‹คํŒจ: \(error.localizedDescription)")
                    return
                }

                print("๐Ÿ“Š ํƒœ๊ทธ ์ƒํƒœ: \(status.rawValue), ์šฉ๋Ÿ‰: \(capacity) bytes")

                switch status {
                case .notSupported:
                    session.invalidate(errorMessage: "โŒ NDEF๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํƒœ๊ทธ์ž…๋‹ˆ๋‹ค")

                case .readOnly:
                    self.readNDEFMessage(from: tag, session: session)

                case .readWrite:
                    self.readNDEFMessage(from: tag, session: session)

                @unknown default:
                    session.invalidate(errorMessage: "โ“ ์•Œ ์ˆ˜ ์—†๋Š” ํƒœ๊ทธ ์ƒํƒœ")
                }
            }
        }
    }

    // NDEF ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ
    private func readNDEFMessage(from tag: NFCNDEFTag, session: NFCNDEFReaderSession) {
        tag.readNDEF { message, error in
            if let error = error {
                session.invalidate(errorMessage: "โŒ ์ฝ๊ธฐ ์‹คํŒจ: \(error.localizedDescription)")
                return
            }

            if let message = message {
                self.detectedMessages.append(message)
                session.alertMessage = "โœ… \(message.records.count)๊ฐœ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค"
                print("๐Ÿ“– NDEF ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ ์„ฑ๊ณต")
            }

            session.invalidate()
        }
    }

    // ์„ธ์…˜ ๋ฌดํšจํ™”
    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        isScanning = false

        if let nfcError = error as? NFCReaderError {
            switch nfcError.code {
            case .readerSessionInvalidationErrorUserCanceled:
                print("โน๏ธ ์‚ฌ์šฉ์ž๊ฐ€ ์Šค์บ”์„ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค")
            case .readerSessionInvalidationErrorSessionTimeout:
                print("โฑ๏ธ ์„ธ์…˜ ์‹œ๊ฐ„ ์ดˆ๊ณผ")
                lastError = "์Šค์บ” ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค"
            case .readerSessionInvalidationErrorFirstNDEFTagRead:
                print("โœ… ์ฒซ ๋ฒˆ์งธ ํƒœ๊ทธ ์ฝ๊ธฐ ์™„๋ฃŒ")
            default:
                print("โŒ NFC ์—๋Ÿฌ: \(nfcError.localizedDescription)")
                lastError = nfcError.localizedDescription
            }
        } else {
            print("โŒ ์—๋Ÿฌ: \(error.localizedDescription)")
            lastError = error.localizedDescription
        }
    }

    // Text Payload ํŒŒ์‹ฑ
    private func parseTextPayload(_ payload: Data) -> String? {
        guard payload.count > 1 else { return nil }

        // Status byte: bit 7 = UTF-16 ์—ฌ๋ถ€, bit 0-5 = ์–ธ์–ด ์ฝ”๋“œ ๊ธธ์ด
        let statusByte = payload[0]
        let isUTF16 = (statusByte & 0x80) != 0
        let langCodeLength = Int(statusByte & 0x3F)

        guard payload.count > langCodeLength + 1 else { return nil }

        // ํ…์ŠคํŠธ ์ถ”์ถœ
        let textData = payload.subdata(in: (1 + langCodeLength)..<payload.count)
        let encoding: String.Encoding = isUTF16 ? .utf16 : .utf8

        return String(data: textData, encoding: encoding)
    }
}

โœ๏ธ 3. NFC ํƒœ๊ทธ ์“ฐ๊ธฐ

NDEF ๋ฉ”์‹œ์ง€๋ฅผ ํƒœ๊ทธ์— ์”๋‹ˆ๋‹ค (iOS 13+).

NFCWriterManager.swift โ€” NFC ํƒœ๊ทธ ์“ฐ๊ธฐ
import CoreNFC

@Observable
class NFCWriterManager: NSObject {
    var session: NFCNDEFReaderSession?
    var messageToWrite: NFCNDEFMessage?
    var writeSuccess = false

    // URL์„ ํƒœ๊ทธ์— ์“ฐ๊ธฐ
    func writeURL(_ url: URL) {
        guard NFCNDEFReaderSession.readingAvailable else {
            print("โŒ NFC ๋ฏธ์ง€์›")
            return
        }

        // NDEF Payload ์ƒ์„ฑ
        guard let payload = NFCNDEFPayload.wellKnownTypeURIPayload(url: url) else {
            print("โŒ Payload ์ƒ์„ฑ ์‹คํŒจ")
            return
        }

        messageToWrite = NFCNDEFMessage(records: [payload])

        // ์„ธ์…˜ ์‹œ์ž‘
        session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
        session?.alertMessage = "์“ฐ๊ธฐ๋ฅผ ์›ํ•˜๋Š” NFC ํƒœ๊ทธ๋ฅผ ๊ฐ€๊นŒ์ด ๋Œ€์ฃผ์„ธ์š”"
        session?.begin()

        print("โœ๏ธ URL ์“ฐ๊ธฐ ์‹œ์ž‘: \(url.absoluteString)")
    }

    // ํ…์ŠคํŠธ๋ฅผ ํƒœ๊ทธ์— ์“ฐ๊ธฐ
    func writeText(_ text: String) {
        guard NFCNDEFReaderSession.readingAvailable else { return }

        // Text Payload ์ƒ์„ฑ
        guard let payload = createTextPayload(text: text, languageCode: "en") else {
            print("โŒ Text Payload ์ƒ์„ฑ ์‹คํŒจ")
            return
        }

        messageToWrite = NFCNDEFMessage(records: [payload])

        session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
        session?.alertMessage = "์“ฐ๊ธฐ๋ฅผ ์›ํ•˜๋Š” NFC ํƒœ๊ทธ๋ฅผ ๊ฐ€๊นŒ์ด ๋Œ€์ฃผ์„ธ์š”"
        session?.begin()

        print("โœ๏ธ ํ…์ŠคํŠธ ์“ฐ๊ธฐ ์‹œ์ž‘: \(text)")
    }

    // ์—ฌ๋Ÿฌ ๋ ˆ์ฝ”๋“œ๋ฅผ ํƒœ๊ทธ์— ์“ฐ๊ธฐ
    func writeMultipleRecords(url: URL, text: String) {
        guard NFCNDEFReaderSession.readingAvailable else { return }

        var records: [NFCNDEFPayload] = []

        // URL Payload
        if let urlPayload = NFCNDEFPayload.wellKnownTypeURIPayload(url: url) {
            records.append(urlPayload)
        }

        // Text Payload
        if let textPayload = createTextPayload(text: text, languageCode: "en") {
            records.append(textPayload)
        }

        messageToWrite = NFCNDEFMessage(records: records)

        session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
        session?.alertMessage = "์“ฐ๊ธฐ๋ฅผ ์›ํ•˜๋Š” NFC ํƒœ๊ทธ๋ฅผ ๊ฐ€๊นŒ์ด ๋Œ€์ฃผ์„ธ์š”"
        session?.begin()

        print("โœ๏ธ ๋‹ค์ค‘ ๋ ˆ์ฝ”๋“œ ์“ฐ๊ธฐ ์‹œ์ž‘")
    }

    // Text Payload ์ƒ์„ฑ
    private func createTextPayload(text: String, languageCode: String) -> NFCNDEFPayload? {
        guard let textData = text.data(using: .utf8) else { return nil }
        guard let langData = languageCode.data(using: .utf8) else { return nil }

        // Status byte: UTF-8, ์–ธ์–ด ์ฝ”๋“œ ๊ธธ์ด
        var payload = Data()
        payload.append(UInt8(langData.count))
        payload.append(langData)
        payload.append(textData)

        return NFCNDEFPayload(
            format: .nfcWellKnown,
            type: "T".data(using: .utf8)!,
            identifier: Data(),
            payload: payload
        )
    }
}

extension NFCWriterManager: NFCNDEFReaderSessionDelegate {
    // ํƒœ๊ทธ ๋ฐœ๊ฒฌ (์“ฐ๊ธฐ์šฉ)
    func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
        guard tags.count == 1, let tag = tags.first else {
            session.alertMessage = "โš ๏ธ ํƒœ๊ทธ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์Šต๋‹ˆ๋‹ค"
            session.restartPolling()
            return
        }

        session.connect(to: tag) { error in
            if let error = error {
                session.invalidate(errorMessage: "โŒ ์—ฐ๊ฒฐ ์‹คํŒจ: \(error.localizedDescription)")
                return
            }

            // ํƒœ๊ทธ ์ƒํƒœ ํ™•์ธ
            tag.queryNDEFStatus { status, capacity, error in
                if let error = error {
                    session.invalidate(errorMessage: "โŒ ์ƒํƒœ ํ™•์ธ ์‹คํŒจ: \(error.localizedDescription)")
                    return
                }

                guard status == .readWrite else {
                    session.invalidate(errorMessage: "โŒ ์ฝ๊ธฐ ์ „์šฉ ํƒœ๊ทธ์ž…๋‹ˆ๋‹ค")
                    return
                }

                // ๋ฉ”์‹œ์ง€ ์“ฐ๊ธฐ
                guard let message = self.messageToWrite else {
                    session.invalidate(errorMessage: "โŒ ์“ธ ๋ฉ”์‹œ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
                    return
                }

                tag.writeNDEF(message) { error in
                    if let error = error {
                        session.invalidate(errorMessage: "โŒ ์“ฐ๊ธฐ ์‹คํŒจ: \(error.localizedDescription)")
                    } else {
                        session.alertMessage = "โœ… ์“ฐ๊ธฐ ์„ฑ๊ณต!"
                        self.writeSuccess = true
                        print("โœ… NFC ํƒœ๊ทธ ์“ฐ๊ธฐ ์™„๋ฃŒ")
                        session.invalidate()
                    }
                }
            }
        }
    }

    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        // ์“ฐ๊ธฐ ๋ชจ๋“œ์—์„œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ
    }

    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        if let nfcError = error as? NFCReaderError,
           nfcError.code != .readerSessionInvalidationErrorUserCanceled {
            print("โŒ ์„ธ์…˜ ์ข…๋ฃŒ: \(error.localizedDescription)")
        }
    }
}

๐Ÿ“ฑ 4. SwiftUI ํ†ตํ•ฉ

NFC ๋ฆฌ๋”/๋ผ์ดํ„ฐ UI๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

NFCReaderView.swift โ€” NFC UI
import SwiftUI

struct NFCReaderView: View {
    @State private var readerManager = NFCReaderManager()
    @State private var writerManager = NFCWriterManager()
    @State private var urlText = "https://developer.apple.com"
    @State private var writeText = "Hello NFC!"

    var body: some View {
        NavigationStack {
            Form {
                // ์ฝ๊ธฐ ์„น์…˜
                Section("ํƒœ๊ทธ ์ฝ๊ธฐ") {
                    Button {
                        readerManager.startScanning()
                    } label: {
                        Label("NFC ํƒœ๊ทธ ์Šค์บ”", systemImage: "wave.3.right")
                    }
                    .disabled(!readerManager.isNFCAvailable || readerManager.isScanning)

                    if readerManager.isScanning {
                        HStack {
                            ProgressView()
                            Text("์Šค์บ” ์ค‘...")
                                .foregroundStyle(.secondary)
                        }
                    }
                }

                // ์ฝ์€ ๋ฉ”์‹œ์ง€
                if !readerManager.detectedMessages.isEmpty {
                    Section("์Šค์บ” ๊ฒฐ๊ณผ") {
                        ForEach(readerManager.detectedMessages.indices, id: \.self) { index in
                            let message = readerManager.detectedMessages[index]
                            VStack(alignment: .leading, spacing: 8) {
                                Text("๋ฉ”์‹œ์ง€ \(index + 1)")
                                    .font(.headline)

                                ForEach(readerManager.parseNDEFRecords(from: message), id: \.self) { record in
                                    Text(record)
                                        .font(.caption)
                                        .foregroundStyle(.secondary)
                                }
                            }
                        }
                    }
                }

                // ์“ฐ๊ธฐ ์„น์…˜
                Section("ํƒœ๊ทธ ์“ฐ๊ธฐ") {
                    TextField("URL", text: $urlText)
                        .keyboardType(.URL)
                        .autocapitalization(.none)

                    Button {
                        if let url = URL(string: urlText) {
                            writerManager.writeURL(url)
                        }
                    } label: {
                        Label("URL ์“ฐ๊ธฐ", systemImage: "link")
                    }
                    .disabled(!readerManager.isNFCAvailable)

                    TextField("ํ…์ŠคํŠธ", text: $writeText)

                    Button {
                        writerManager.writeText(writeText)
                    } label: {
                        Label("ํ…์ŠคํŠธ ์“ฐ๊ธฐ", systemImage: "text.cursor")
                    }
                    .disabled(!readerManager.isNFCAvailable)
                }

                // ์ƒํƒœ
                Section {
                    Text("NFC ์ง€์›: \(readerManager.isNFCAvailable ? "โœ…" : "โŒ")")
                        .foregroundStyle(.secondary)

                    if let error = readerManager.lastError {
                        Text("์—๋Ÿฌ: \(error)")
                            .foregroundStyle(.red)
                            .font(.caption)
                    }
                }
            }
            .navigationTitle("NFC ๋ฆฌ๋”/๋ผ์ดํ„ฐ")
        }
    }
}

๐Ÿ’ก HIG ๊ฐ€์ด๋“œ๋ผ์ธ

๐ŸŽฏ ์‹ค์ „ ํ™œ์šฉ

๐Ÿ“š ๋” ์•Œ์•„๋ณด๊ธฐ

โšก๏ธ ์„ฑ๋Šฅ ํŒ: NFC ์Šค์บ”์€ 60์ดˆ ํ›„ ์ž๋™ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค. invalidateAfterFirstRead๋ฅผ false๋กœ ์„ค์ •ํ•˜๋ฉด ์—ฌ๋Ÿฌ ํƒœ๊ทธ๋ฅผ ์—ฐ์†์œผ๋กœ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์“ฐ๊ธฐ ์ž‘์—… ์ „์—๋Š” ๋ฐ˜๋“œ์‹œ ํƒœ๊ทธ์˜ ์šฉ๋Ÿ‰๊ณผ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.