-
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 XMLParser에 대해 알아보겠습니다 🙋🏻
네트워크 통신을 하고 응답값으로 대부분 JSON 형식으로 처리를 하는것에 익숙할거에요.
그런데, 간혹 어떤 공공 API들은 XML 형식으로 응답을 주는 경우도 있습니다.
정말 대부분이 JSON으로 주긴하지만, 그래도 XML로 준다해서 포기할 수 없으니 XML 응답값을 어떻게 파싱하고 뷰에 나타낼 수 있는지 한번 XMLParser를 통해서 알아봅시다 😄
그럼 바로 레츠고! 🚀
XMLParser
우선 XMLParser에 대해 개념을 알아본 다음에 실제로 이용해볼께요.
XMLParser는 DTD 선언을 포함해 XML 도큐먼트의 이벤트를 다루는 파서라고 보시면 됩니다.
즉 쉽게 말해서 XML 형식을 우리 Swift 모델로 파싱해주는 역할을 하는 객체라고 볼 수 있어요.
class XMLParser : NSObject
NSObject를 상속받는 간단한 객체에요.
XMLParser는 XML 문서를 처리할 때 발견되는 항목들에 대해 딜리게이트를 사용하여 알려줍니다.
분석하는것과 분석에 대한 오류를 알려주는것 외에는 다른 작업은 수행하지 않아요.
흔히 파서 객체라고도 불리고 콜백에서 사용되지 않는 한, XMLParser는 주어진 인스턴스가 하나의 스레드에서만 사용되는 한 스레드로부터 안전한 클래스입니다.
JSON과 마찬가지로, 해당 파서 객체 초기화 시 URL이나 Data, Stream 모두를 이용할 수 있습니다.
중요한건 파싱을 위해 커스텀한 파서 객체를 만들때, XMLParserDelegate를 채택하여 이용해야 합니다.
XMLParserDelegate는 XML 파서가 파싱된 문서의 내용에 대해 대리자에게 알리기 위해 사용되는 인터페이스입니다.
unowned(unsafe) var delegate: (any XMLParserDelegate)? { get set } protocol XMLParserDelegate
즉, XMLParser에는 delegate가 존재하고 여기에 XMLParserDelegate를 채택한 객체를 넣어주는거이죠.
그렇게 하게됨으로, 다양한 XML 처리 관련 기능들을 XMLParserDelegate의 메서드들에서 하도록 해주죠.
정말 다양하죠!?
딜리게이트까지 파서 객체에 넣어줬다면 이제 파싱을 시작하면 됩니다.
func parse() -> Bool
XMLParser에 존재하는 이 parse 메서드를 호출하여 이벤트 기반 구문 분석을 시작하는것이죠.
간단히 파싱하는 메서드라고 보면됩니다 😃
여기까지가 핵심적인 XMLParser의 역할, 기능과 XMLParserDelegate의 역할입니다.
사실 이렇게 개념만 나열하면 정신없는데, 실제로 코드를 보면서 구현해보면 아주 간단합니다!
바로 코드로 실습해볼까요?
XMLParser를 이용해 XML 문서 파싱하기
우선 공공 API를 이용하는건 파악하기 더 불편하니, 간단히 XML 데이터 응답 문서를 하나 아래와 같은 형식으로 만들어보고 프로젝트에 넣어 사용해볼께요.
<animals> <animal> <name>momo</name> <owner>green</owner> <age>10</age> </animal> <animal> <name>bbobbi</name> <owner>blue</owner> <age>3</age> </animal> <animal> <name>chorong</name> <owner>yellow</owner> <age>15</age> </animal> </animals>
이렇게 동물들의 모델 값을 가지는 동물 리스트 응답 값이 있습니다.
그런 다음 해당 파일은 프로젝트에 위치시킵니다.
다음으로 Swift 모델을 정의해야겠죠?
import Foundation struct Animal: Identifiable { let id = UUID() let name: String let owner: String let age: String }
이렇게 응답 필드값에 맞춰 늘 먹던것처럼 모델을 정의해줍니다.
그리고 이제 중요한 부분인 XMLParser를 이용해 커스텀한 파서 객체를 만듭니다.
import Foundation class AnimalXMLParser: NSObject, XMLParserDelegate { private var animals: [Animal] = [] private var currentElement = "" private var currentName = "" private var currentOwner = "" private var currentAge = "" func parse(data: Data) -> [Animal] { let parser = XMLParser(data: data) parser.delegate = self parser.parse() return animals } // MARK: - XMLParserDelegate func parser( _ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:] ) { currentElement = elementName if currentElement == "animal" { currentName = "" currentOwner = "" currentAge = "" } } func parser(_ parser: XMLParser, foundCharacters string: String) { switch currentElement { case "name": currentName += string case "owner": currentOwner += string case "age": currentAge += string default: break } } func parser( _ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String? ) { if elementName == "animal" { let animal = Animal(name: currentName, owner: currentOwner, age: currentAge) animals.append(animal) } } }
변수들은 각 현재 XML 문서를 읽을때 현재 읽고있는 필드들과 반환할 동물 리스트인 Animal 배열 타입이 필요합니다.
그리고 parse 메서드를 만들어서 우리는 URL대신 Data를 넣어줌으로 인자로 받고 Animal 배열을 반환 받도록 메서드를 만듭니다.
그 안에서 이제 XMLParser 객체를 data를 넣어서 초기화 시키고 딜리게이트를 설정합니다.
그리고 파싱을 시작하기 위해 내장된 parse 메서드를 호출한 후 마지막으로 값을 반환하면 됩니다.
이제 해당 객체 생성 시 XMLParserDelegate를 채택했으니, 필수적으로 파싱에 필요한 기능들을 가진 메서드들에서 구현을 해줍니다.
우선 XML 문서를 읽기 시작하면서 읽는 요소가 animal일 시 초기화를 시키죠.
즉, animal에 담긴 이름부터 나이 필드까지 추가해야함으로 초기화를 시키는것입니다.
그리고 두번째 메서드에서, 문자를 찾아서 값을 넣어주는데요.
여기서는 XML 문서를 읽으며 name 필드를 찾으면 현재 이름 변수에 그 값을 넣어주는식으로 모든 필드들을 매칭시켜둡니다.
그리고 마지막으로 해당 요소가 끝이나면 현재 이름부터 나이까지 저장된 변수 값들로 Animal 인스턴스 객체를 만들어서 Animal 배열에 추가하는것이죠.
이렇게 전체 XML 문서를 읽으며 파싱하는 반복작업을 통하는것입니다.
그럼 실제로 SwiftUI 뷰에서 확인해볼까요?
import SwiftUI struct ContentView: View { @State private var animals: [Animal] = [] var body: some View { NavigationView { List(animals) { animal in VStack(alignment: .leading) { Text(animal.name) Text(animal.owner) Text(animal.age) } } .navigationTitle("Animals") .task { loadXMLData() } } } private func loadXMLData() { if let url = Bundle.main.url(forResource: "animals", withExtension: "xml") { if let data = try? Data(contentsOf: url) { let parser = AnimalXMLParser() animals = parser.parse(data: data) } } } }
간단히 리스트로 동물들을 모두 가져와서 이름부터 나이까지 표현해주는 코드입니다.
loadXMLData 메서드를 뷰가 나타날때 호출하여 가져오고 animals 상태 변수에 넣어주죠.
그럼 정상적으로 XML 문서를 파싱하고 표현해주는지 확인해볼까요~?
아주 정상적으로 잘 파싱하고 나타내주는걸 확인할 수 있습니다.
이렇게 XML 문서도 쉽게 파싱해볼 수 있어요.
다만 JSON보다 좀 번거롭긴하지만요ㅎㅎ
마무리
XML 문서를 파싱하는 경우가 있을까 싶었는데, 생각보다 아직도 많은 공공 API들은 XML로 응답을 주는 경우가 있더라구요.
처음 알았습니다 🥲
해당 예제 코드는 아래 제 깃헙 레포에서 편하게 확인 및 사용해보셔도 좋습니다!
레퍼런스
'Swift' 카테고리의 다른 글
What's new in Swift6 (feat. WWDC 2024) (26) 2024.08.19 Swift 6 - sending parameter and result values (53) 2024.08.15 What's new in Swift 5.10 (64) 2024.03.21 New access modifier - package (78) 2024.03.04 Sequence를 알아보자 🤿 (111) 2024.01.29