Swift의 반사 메커니즘을 사용하여 객체 등록 정보 스트리밍

4919 단어
원문을 읽다
얼마 전에 맥OS 도면 작은 응용 프로그램인 cuImage를 썼는데 그 안의 몇몇 기술점은 줄곧 시간을 내서 정리하고 공유하지 못했다.졸업을 앞두고 일이 많아서 시간을 쪼개야만 했다.본고는 Swift의 반사 메커니즘을 이용하여 대상 속성을 옮겨다니며 코드를 간소화하고 코드 복용률을 높일 것이다.
cuImage에서 각 도상마다 상응하는 설정 정보가 있다. 예를 들어 칠우운의 도상 설정 정보 QiniuHostInfo이다.나는 그것을 UserDefaults을 통해 보존하기를 바란다.(그림의 일부 민감한 정보는 암호화해야 하지만 간략하게 설명하기 위해 본고는 암호화와 관련된 점을 언급하지 않는다. 사실 암호화에 있어서 나도 배추다. 왜 Keychain을 암호화하지 않는지는 별개의 문제다. 여기서 생략한다.)UserDefaults은 사용자 정의 객체를 지원하지 않으므로 강제로 저장하면 런타임 시 예외가 발생합니다.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object 'Abc' for key 'Xyz'. UserDefaults을 통해 사용자 객체를 영구적으로 저장하려면 NSKeyedArchiver을 통해 사용자 객체를 NSData 형식으로 인코딩해야 합니다.이것은 NSCoding 협의에 따라 이 협의 성명을 실현하는 두 가지 방법(init(_:)encode(_:))이 필요하다.일반적인 실현 방식은 다음과 같다.
final class QiniuHostInfo: NSObject, NSCoding {
    var name = ""
    var accessKey = ""
    var secretKey = ""
    
    init(name: String = "", accessKey: String = "", secretKey: String = "") {
        self.name = name
        self.accessKey = accessKey
        self.secretKey = secretKey
        
        super.init()
    }
    
    init?(coder aDecoder: NSCoder) {
        name = aDecoder.decodeObject(forKey: #keyPath(name)) as! String
        accessKey = aDecoder.decodeObject(forKey: #keyPath(accessKey)) as! String
        secretKey = aDecoder.decodeObject(forKey: #keyPath(secretKey)) as! String
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: #keyPath(name))
        aCoder.encode(accessKey, forKey: #keyPath(accessKey))
        aCoder.encode(secretKey, forKey: #keyPath(secretKey))
    }
}

final class ImgurHostInfo: NSObject, NSCoding {
    var name = ""
    var userName = ""
    var password = ""
    
    init(name: String = "", userName: String = "", password: String = "") {
        self.name = name
        self.userName = userName
        self.password = password
        
        super.init()
    }
    
    init?(coder aDecoder: NSCoder) {
        name = aDecoder.decodeObject(forKey: #keyPath(name)) as! String
        userName = aDecoder.decodeObject(forKey: #keyPath(userName)) as! String
        password = aDecoder.decodeObject(forKey: #keyPath(password)) as! String
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: #keyPath(name))
        aCoder.encode(userName, forKey: #keyPath(userName))
        aCoder.encode(password, forKey: #keyPath(password))
    }
}

이런 방식의 단점은 QiniuHostInfo이 새로운 속성을 추가하거나 코드를 재구성할 때 속성을 수정하면 NSCoding이 성명한 두 가지 방법에 대응하는 코드를 추가하거나 수정해야 하기 때문에 누락될 수 있다는 것이다.그리고 도상(예를 들어 ImgurHostInfo)을 추가해야 할 때 같은 절차에 따라 코드를 훑어보아야 한다.
다음은 내가 최종적으로 채택한 방식이다.우선, 나는 각종 그래픽 정보 클래스를 위해 공공 기류 HostInfo을 썼고 NSCoding 협의 방법을 실현하는 임무를 기류 HostInfo에 맡기고 한꺼번에 해결했다.앞으로 도상이 하나씩 증가할 때마다 상응하는 도상 정보 서브클래스(예를 들어 QiniuHostInfo, ImgurHostInfo)에서 자신의 속성을 정의하면 된다. 이런 종류의 코드는 훨씬 시원해진다.
class HostInfo: NSObject, NSCoding {
    var name = ""

    override init() {
        super.init()
    }
    
    convenience required init?(coder aDecoder: NSCoder) {
        self.init()
        
        forEachChildOfMirror(reflecting: self) { key in
            setValue(aDecoder.decodeObject(forKey: key), forKey: key)
        }
    }
    
    func encode(with aCoder: NSCoder) {
        forEachChildOfMirror(reflecting: self) { key in
            aCoder.encode(value(forKey: key), forKey: key)
        }
    }
    
    func forEachChildOfMirror(reflecting subject: Any, handler: (String) -> Void) {
        var mirror: Mirror? = Mirror(reflecting: subject)
        while mirror != nil {
            for child in mirror!.children {
                if let key = child.label {
                    handler(key)
                }
            }
            
            // Get super class's properties.
            mirror = mirror!.superclassMirror
        }
    }
}

final class QiniuHostInfo: HostInfo {
    var accessKey = ""
    var secretKey = ""
}

final class ImgurHostInfo: HostInfo {
    var userName = ""
    var password = ""
}

위의 코드에서 init(_:)encode(_:)은 모두 반사를 필요로 하기 때문에 나는 공공 부분을 추출하고 하나의 속성에 접근할 때마다 해야 하는 조작을 클로즈업 형식으로 forEachChildOfMirror(_:_:) 함수에 전달했다.그 중에서 superclassMirror을 사용한 것은 부류에서도 일부 공공 속성이 옮겨야 하기 때문이다. 예를 들어 HostInfoname 속성이다.그래서 현재 클래스의 속성을 두루 훑어본 후 superclassMirror을 통해 계승 체인을 따라 올라가 계승 연결된 클래스의 모든 속성을 방문할 수 있다.
만약 독자들이 다른 괜찮은 실현 방식이 있다면, 당신들의 공유를 기대합니다.

좋은 웹페이지 즐겨찾기