Search

SF Symbols 퀴즈 앱(기간: 7일)

About

SwiftSoup를 이용해서 웹 사이트의 텍스트를 크롤링 해옴
SF Symbols의 이미지를 랜덤으로 표시하고 5개의 문항 중 정답 선택하면 다음 문제로 넘어가고 오답이면 진동 + 화면 흔들리는 애니메이션 + 빨간색 배경색이 등장 후 사라짐

사용된 오픈소스라이브러리

SwiftSoup
SnapKit
Then

주요 코드

[ JSON 인코딩을 활용한 데이터의 로컬 저장 처리 ]
func saveSymbolsToCache(_ symbols: [Symbol]) { do { let filePath = getDocumentsDirectory() .appendingPathComponent(symbolsCacheFile) let data = try JSONEncoder().encode(symbols) try data.write(to: filePath, options: .atomic) } catch { print("Failed to save symbols: \(error)") } }
Swift
복사
[Symbol] 배열을 받아서 JSON 형태로 인코딩하고 로컬 디바이스의 ‘Documents’ 디렉토리에 저장한다.
JSONEncoder.encode(symbols) 를 통해 ‘Symbols’ 배열을 JSON 데이터로 변환하고 이 데이터를 ‘filePath’ 위치에 ‘automic’ 옵션으로 쓴다.
JSONEncoder 는 Swift의 표전 라이브러리에 포함된 클래스로, ‘Codable’ 프로토콜을 준수하는 객체들을 JSON 데이터로 변환하는 데 사용됨.
.atomic 옵션은 파일을 쓸 때 데이터의 무결성을 보장하기 위해 사용함. 이 옵션을 사용하면 데이터가 먼저 임시 파일에 쓰이고 전체 데이터가 성공적으로 쓰여지면 그때 최종 목적지 파일로 이동시키는 방식. 이는 중간에 오류가 발생하더라도 원본 파일이 손상되지 않도록 해주는 장점이 있음. 하지만 임시 파일을 사용하기 때문에 약간의 추가적인 디스크 공간과 처리 시간이 필요함.
‘catch’문을 통해 오류가 발생하면 그 오류를 콘솔에 출력한다.
이 함수에서 발생하는 오류로 두 가지 상황이 있음.
1.
JSONEncoder().encode(symbols) 에서 ‘Symbol’ 배열을 JSON으로 변환할 때 발생할 수 있는데 이는 ‘Symbol’이 ‘Codable’ 프로토콜을 제대로 준수하지 않을 때 일어날 수 있음.
2.
data.write(to: filePath, options: .atomic) 에서 파일 시스템 관련 오류. 예를 들어, 디스크 공간 부족이나 권한 문제 등이 있을 수 있음. 이런 오류에 대비하기 위해선 더 자세한 오류 처리 로직을 구현하거나 사용자에게 명확한 피드백을 제공하는 방법이 있음.
Codable이란? Swift에서 데이터 모델을 JSON이나 다른 외부 표현 형식으로 쉽게 변환할 수 있도록 하는 프로토콜이다. 실제로는 ‘Encodable’과 ‘Decodable’ 두 프로토콜의 조합인 타입 별칭이다. ‘Encodable’은 Swift 타입을 외부 표현(ex. JSON)으로 변환할 수 있게 해주고 ‘Decodable’은 외부 표현에서 Swift 타입으로 변환할 수 있게 해준다. 따라서 서버에서 JSON 형태의 데이터를 받아와 Swift의 구조체나 클래스로 변환할 때 매우 유용하다.
[ 비동기 실행 및 HTML 파싱 ]
func fetchSymbols(completion: @escaping ([Symbol]?, Error?) -> Void) { DispatchQueue.global(qos: .background).async { let urlAddress = "https://developer.apple.com/sf-symbols/release-notes/" guard let url = URL(string: urlAddress) else { completion( nil, NSError( domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"] ) ) return } do { let html = try String(contentsOf: url, encoding: .utf8) let doc: Document = try SwiftSoup.parse(html) let firstItems: Elements = try doc.select("ul.column-list list-1").select("li") let secondItems: Elements = try doc.select("ul.column-list list-2").select("li") let thirdItems: Elements = try doc.select("ul.no-bullet").select("li") var symbols: [Symbol] = [] let simpleItems = [firstItems, secondItems] for items in simpleItems { for listItem in items { let name = try listItem.text().trimmingCharacters(in: .whitespaces) if !name.isEmpty { symbols.append(Symbol(name: name)) } } } for listItem in thirdItems { let text = try listItem.text() let components = text.components(separatedBy: "->").map { $0.trimmingCharacters(in: .whitespaces) } if let name = components.last, name != "" { symbols.append(Symbol(name: name)) } } DispatchQueue.main.async { completion(symbols, nil) } } catch { DispatchQueue.main.async { completion(nil, error) } } } }
Swift
복사
DispatchQueue.global(qos: .background).async 를 사용하여 백그라운드 스레드에서 실행되도록 한다. 이는 메인 스레드를 차단하지 않고 작업을 수행할 수 있도록 함으로써 사용자 인터페이스가 부드럽게 작동할 수 있도록 해준다.
guard let url = URL(string: urlAddress) else { … } 를 통해 주어진 문자열로 URL 객체를 생성한다. URL이 유효하지 않은 경우 콜백으로 오류를 반환한다.
let doc: Document = try SwiftSoup.parse(html) 에서 ‘SwiftSoup’라는 HTML을 분석하고 조작할 수 있는 라이브러리를 통해 HTML 문서를 파싱한다.
doc.select("ul.column-list list-1").select("li") 에서 ‘.select’ 메서드를 사용하여 특정 HTML 요소를 선택하고 그 안의 텍스트를 추출하여 ‘Symbol’ 객체 배열로 만든다.
작업이 완료되면 DispatchQueue.main.async { … } 를 통해 메인 스레드로 전환하고 콜백을 통해 심볼 배열 또는 발생한 오류를 반환한다.