ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS] 유용하지만 잘 모르는 Foundation Class
    프로그래밍 2019.07.04 14:01

    본 글 요약 : Useful obscure Foundation types in Swift

    NSScanner

    NSScanner를 사용하면 문자열에서 숫자 또는 문자들을 순서대로 찾아낼 수 있다.

    public func extractIntsFrom(string: String) -> [Int] {
        var result: [Int] = []
        let scanner = Scanner(string: string)
        // 숫자가 아닌 것은 전부 건너뜀
        scanner.charactersToBeSkipped = CharacterSet.decimalDigits.inverted
        var pointer: Int = 0
        while scanner.isAtEnd == false {
            if scanner.scanInt(&pointer) {
                result.append(pointer)
            }
        }
        return result
    }
    let string = "1M very L337 700"
    let ints = extractIntsFrom(string: string)
    // [1, 337, 700]

    scanString, scanDouble, scanHexInt 같은 다른 것들도 많다.

    NSCountedSet

    어떤 요소의 양을 측정하는 것은 추적하는 것은 쉽지 않다. 2개의 문자열이 같은 요소를 포함하고 있는지 확인하는 아나그램 문제처럼:

    func isAnagram(_ first: String, _ second: String) -> Bool {
        guard first.count == second.count else {
            return false
        }
        var dict = [Character: Int]()
        for character in first {
            dict[character, default: 0] += 1
        }
        for character in second {
            dict[character, default: 0] -= 1
            if dict[character] == 0 {
                dict[character] = nil
            }
        }
        return dict.isEmpty
    }

    위 코드처럼 Int 값의 Dictionary를 이용하면 어렵지 않지만, NSCountedSet은 더 쉽다.

    func isAnagram(_ first: String, _ second: String) -> Bool {
        guard first.count == second.count else {
            return false
        }
        let countedSet = NSCountedSet(array: Array(first))
        for character in second {
            countedSet.remove(c)
        }
        return countedSet.count == 0
    }

    CFBinaryHeap

    스위프트 기본 라이브러리는 데이터 구조체가 부족한데, CFTree, CFBinaryHeap 형태의 것들이 있다. 힙은 우선 순위 큐 구조를 구현하는데 효과적이다. 코드량이 좀 되지만 CFBinaryHeap 이 해결점이 될 수 있다. CFBinaryHeap는 Foundation 소속이 아니라 C CoreFoundation이라서 포인터를 다루지 못하면 사용할 수 없다. MCBinaryHeap 같은 랩퍼 클래스를 사용하거나 직접 잘~ 구현하자.

    let array = [8,3,5,4,1]
    let heap = MCBinaryHeap(array: array)
    heap?.popMinimumObject() // 1
    heap?.popMinimumObject() // 3
    heap?.popMinimumObject() // 4
    heap?.popMinimumObject() // 5
    heap?.popMinimumObject() // 8

    NSCache

    NSCache는 딕셔너리와 비슷하게 동작하는 콜렉션 타입이지만, 2가지 중요한 점이 다르다. 첫째, 들어가는 키값을 복사하지 않는다는 것이고, 두번째는 시스템 메모리가 바닥나면 요소들(entries)을 자동으로 제거한다는 것이다.

    요소들을 제거하는 실제 정책들은 모르지만, 생성 과정에 자원을 많이 소비하는 오브젝트들이 있다면, 일반적인 딕셔너리 캐시보다 NSCache 사용이 더 시스템에 친근할 것이다.

    final class ImageDownloader {
        let client: HTTPClient
        let cache = NSCache<NSString, NSData>()
    
        init(client: HTTPClient) {
            self.client = client
        }
    
        func load(imageUrl: URL, intoImageView imageView: UIImageView) {
           let key = imageUrl.absoluteString as NSString
            func apply(data: NSData) {
                let image = UIImage(data: data as Data)
                imageView.image = image
            }
            if let cachedData = cache.object(forKey: key) {
                apply(data: cachedData)
                return
            } else {
                client.data(from: imageUrl) { data in
                    cache.setObject(data as NSData, forKey: key)
                    apply(data: data as NSData)
                }
            }
        }
    }

    NSOrderedSet

    이름에서도 알 수 있듯이, NSOrderedSet은 일반적인 Set처럼 동작하면서, 요소들이 정렬되어 있다.

    let set = NSMutableOrderedSet()
    set.add(1)
    set.add(4)
    set.add(1)
    set.add(1)
    set.add(1)
    set.add(6)
    set.add(4)
    set.add(6)
    for a in set {
        print(a)
        // 1, 4, 6
    }

    오래된 타입이라서 타입 제네릭이 없이 모두 Any타입으로 동작한다. 스위프트 형태로 랩 클래스를 작성해서 쓰면, Swifty 할 것이다.

    class OrderedSet<T: Hashable>: Sequence {
        private let _set = NSMutableOrderedSet()
        init() {}
    
        func makeIterator() -> NSFastEnumerationIterator {
            return _set.makeIterator()
        }
    
        func add(_ element: T) {
            _set.add(element)
        }
    
        func remove(_ element: T) {
            _set.remove(element)
        }
    }

    NSByteCountFormatter

    Foundation 에는 아주아주 많은 포맷터들이 있다. ByteCountFormatter는 바이트 수를 사람일 읽을 수 있는 형태로 파일 사이즈를 형식화한다.

    let bytes = 1024 * 1024
    let formatter = ByteCountFormatter()
    formatter.allowsNonnumericFormatting = false // Uses '0' instead of 'Zero'
    formatter.countStyle = .file
    let string = formatter.string(fromByteCount: Int64(bytes))
    // 1 MB

    NSDataDetector

    NSDataDetectorNSScanner와 비슷하나, (전화번호, 주소, 링크 같은) 문맥적 데이터를 추출하는 점이 다르다.

    let string = "I write for https://swiftrocks.com and my phone is 555-111-111."
    let range = NSRange(0..<string.count)
    
    let types: NSTextCheckingResult.CheckingType = [.link]
    let dataDetector = try NSDataDetector(types: types.rawValue)
    dataDetector.enumerateMatches(in: string, options: [], range: range) { (match, _, _) in
        switch match?.resultType {
        case .link?:
            print(match?.url)
            // https://swiftrocks.com
        case .phoneNumber?:
            print(result?.phoneNumber)
            // "555-111-111"
        default:
            return
        }
    }

    오래된 것이라 Foundation의 정규표현 API 를 의존하고 있는 점이 아쉽지만, 특정 정보를 추출하는데 있어 NSDataDetector는 매우 훌륭한 클래스이다.

    댓글 0

Designed by Tistory.