| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| |
| import CoreFoundation |
| |
| /// Archives created using the class method `archivedData(withRootObject:)` use this key |
| /// for the root object in the hierarchy of encoded objects. The `NSKeyedUnarchiver` class method |
| /// `unarchiveObject(with:)` looks for this root key as well. |
| public let NSKeyedArchiveRootObjectKey: String = "root" |
| |
| internal let NSKeyedArchiveNullObjectReference = _NSKeyedArchiverUID(value: 0) |
| internal let NSKeyedArchiveNullObjectReferenceName: String = "$null" |
| internal let NSKeyedArchivePlistVersion = 100000 |
| internal let NSKeyedArchiverSystemVersion : UInt32 = 2000 |
| |
| internal func escapeArchiverKey(_ key: String) -> String { |
| if key.hasPrefix("$") { |
| return "$" + key |
| } else { |
| return key |
| } |
| } |
| |
| internal let NSPropertyListClasses : [AnyClass] = [ |
| NSArray.self, |
| NSDictionary.self, |
| NSString.self, |
| NSData.self, |
| NSDate.self, |
| NSNumber.self |
| ] |
| |
| /// `NSKeyedArchiver`, a concrete subclass of `NSCoder`, provides a way to encode objects |
| /// (and scalar values) into an architecture-independent format that can be stored in a file. |
| /// When you archive a set of objects, the class information and instance variables for each object |
| /// are written to the archive. `NSKeyedArchiver`’s companion class, `NSKeyedUnarchiver`, |
| /// decodes the data in an archive and creates a set of objects equivalent to the original set. |
| /// |
| /// A keyed archive differs from a non-keyed archive in that all the objects and values |
| /// encoded into the archive are given names, or keys. When decoding a non-keyed archive, |
| /// values have to be decoded in the same order in which they were encoded. |
| /// When decoding a keyed archive, because values are requested by name, values can be decoded |
| /// out of sequence or not at all. Keyed archives, therefore, provide better support |
| /// for forward and backward compatibility. |
| /// |
| /// The keys given to encoded values must be unique only within the scope of the current |
| /// object being encoded. A keyed archive is hierarchical, so the keys used by object A |
| /// to encode its instance variables do not conflict with the keys used by object B, |
| /// even if A and B are instances of the same class. Within a single object, |
| /// however, the keys used by a subclass can conflict with keys used in its superclasses. |
| /// |
| /// An `NSKeyedArchiver` object can write the archive data to a file or to a |
| /// mutable-data object (an instance of `NSMutableData`) that you provide. |
| open class NSKeyedArchiver : NSCoder { |
| |
| struct ArchiverFlags : OptionSet { |
| let rawValue : UInt |
| |
| init(rawValue : UInt) { |
| self.rawValue = rawValue |
| } |
| |
| static let none = ArchiverFlags(rawValue: 0) |
| static let finishedEncoding = ArchiverFlags(rawValue : 1) |
| static let requiresSecureCoding = ArchiverFlags(rawValue: 2) |
| } |
| |
| private class EncodingContext { |
| // the object container that is being encoded |
| var dict = Dictionary<String, Any>() |
| // the index used for non-keyed objects (encodeObject: vs encodeObject:forKey:) |
| var genericKey : UInt = 0 |
| } |
| |
| private static var _classNameMap = Dictionary<String, String>() |
| private static var _classNameMapLock = NSLock() |
| |
| private var _stream : AnyObject |
| private var _flags = ArchiverFlags(rawValue: 0) |
| private var _containers : Array<EncodingContext> = [EncodingContext()] |
| private var _objects : Array<Any> = [NSKeyedArchiveNullObjectReferenceName] |
| private var _objRefMap : Dictionary<AnyHashable, UInt32> = [:] |
| private var _replacementMap : Dictionary<AnyHashable, Any> = [:] |
| private var _classNameMap : Dictionary<String, String> = [:] |
| private var _classes : Dictionary<String, _NSKeyedArchiverUID> = [:] |
| private var _cache : Array<_NSKeyedArchiverUID> = [] |
| |
| /// The archiver’s delegate. |
| open weak var delegate: NSKeyedArchiverDelegate? |
| |
| /// The format in which the receiver encodes its data. |
| /// |
| /// The available formats are `xml` and `binary`. |
| open var outputFormat = PropertyListSerialization.PropertyListFormat.binary { |
| willSet { |
| if outputFormat != PropertyListSerialization.PropertyListFormat.xml && |
| outputFormat != PropertyListSerialization.PropertyListFormat.binary { |
| NSUnimplemented() |
| } |
| } |
| } |
| |
| /// Returns an `NSData` object containing the encoded form of the object graph |
| /// whose root object is given. |
| /// |
| /// - Parameter rootObject: The root of the object graph to archive. |
| /// - Returns: An `NSData` object containing the encoded form of the object graph |
| /// whose root object is rootObject. The format of the archive is |
| /// `NSPropertyListBinaryFormat_v1_0`. |
| open class func archivedData(withRootObject rootObject: Any) -> Data { |
| let data = NSMutableData() |
| let keyedArchiver = NSKeyedArchiver(forWritingWith: data) |
| |
| keyedArchiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey) |
| keyedArchiver.finishEncoding() |
| |
| return data._swiftObject |
| } |
| |
| /// Archives an object graph rooted at a given object by encoding it into a data object |
| /// then atomically writes the resulting data object to a file at a given path, |
| /// and returns a Boolean value that indicates whether the operation was successful. |
| /// |
| /// - Parameters: |
| /// - rootObject: The root of the object graph to archive. |
| /// - path: The path of the file in which to write the archive. |
| /// - Returns: `true` if the operation was successful, otherwise `false`. |
| open class func archiveRootObject(_ rootObject: Any, toFile path: String) -> Bool { |
| var fd : Int32 = -1 |
| var auxFilePath : String |
| var finishedEncoding : Bool = false |
| |
| do { |
| (fd, auxFilePath) = try _NSCreateTemporaryFile(path) |
| } catch _ { |
| return false |
| } |
| |
| defer { |
| do { |
| if finishedEncoding { |
| try _NSCleanupTemporaryFile(auxFilePath, path) |
| } else { |
| try FileManager.default.removeItem(atPath: auxFilePath) |
| } |
| } catch _ { |
| } |
| } |
| |
| let writeStream = _CFWriteStreamCreateFromFileDescriptor(kCFAllocatorSystemDefault, fd)! |
| |
| if !CFWriteStreamOpen(writeStream) { |
| return false |
| } |
| |
| defer { CFWriteStreamClose(writeStream) } |
| |
| let keyedArchiver = NSKeyedArchiver(output: writeStream) |
| |
| keyedArchiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey) |
| keyedArchiver.finishEncoding() |
| finishedEncoding = keyedArchiver._flags.contains(ArchiverFlags.finishedEncoding) |
| |
| return finishedEncoding |
| } |
| |
| public override convenience init() { |
| self.init(forWritingWith: NSMutableData()) |
| } |
| |
| private init(output: AnyObject) { |
| self._stream = output |
| super.init() |
| } |
| |
| /// Returns the archiver, initialized for encoding an archive into a given a mutable-data object. |
| /// |
| /// When you finish encoding data, you must invoke `finishEncoding()` at which point data |
| /// is filled. The format of the archive is `NSPropertyListBinaryFormat_v1_0`. |
| /// |
| /// - Parameter data: The mutable-data object into which the archive is written. |
| public convenience init(forWritingWith data: NSMutableData) { |
| self.init(output: data) |
| } |
| |
| private func _writeXMLData(_ plist : NSDictionary) -> Bool { |
| var success = false |
| |
| if let data = self._stream as? NSMutableData { |
| let xml : CFData? |
| |
| xml = _CFPropertyListCreateXMLDataWithExtras(kCFAllocatorSystemDefault, plist) |
| if let unwrappedXml = xml { |
| data.append(unwrappedXml._swiftObject) |
| success = true |
| } |
| } else { |
| success = CFPropertyListWrite(plist, self._stream as! CFWriteStream, |
| kCFPropertyListXMLFormat_v1_0, 0, nil) > 0 |
| } |
| |
| return success |
| } |
| |
| private func _writeBinaryData(_ plist : NSDictionary) -> Bool { |
| return __CFBinaryPlistWriteToStream(plist, self._stream) > 0 |
| } |
| |
| /// Returns the encoded data for the archiver. |
| /// |
| /// If encoding has not yet finished, invoking this property calls `finishEncoding()` |
| /// and returns the data. If you initialized the keyed archiver with a specific |
| /// mutable data instance, then that data is returned by the property after |
| /// `finishEncoding()` is called. |
| open var encodedData: Data { |
| |
| if !_flags.contains(.finishedEncoding) { |
| finishEncoding() |
| } |
| |
| return (_stream as! NSData)._swiftObject |
| } |
| |
| /// Instructs the archiver to construct the final data stream. |
| /// |
| /// No more values can be encoded after this method is called. You must call this method when finished. |
| open func finishEncoding() { |
| if _flags.contains(ArchiverFlags.finishedEncoding) { |
| return |
| } |
| |
| var plist = Dictionary<String, Any>() |
| var success : Bool |
| |
| plist["$archiver"] = NSStringFromClass(type(of: self)) |
| plist["$version"] = NSKeyedArchivePlistVersion |
| plist["$objects"] = self._objects |
| plist["$top"] = self._containers[0].dict |
| |
| |
| |
| if let unwrappedDelegate = self.delegate { |
| unwrappedDelegate.archiverWillFinish(self) |
| } |
| |
| let nsPlist = plist._bridgeToObjectiveC() |
| |
| if self.outputFormat == PropertyListSerialization.PropertyListFormat.xml { |
| success = _writeXMLData(nsPlist) |
| } else { |
| success = _writeBinaryData(nsPlist) |
| } |
| |
| if let unwrappedDelegate = self.delegate { |
| unwrappedDelegate.archiverDidFinish(self) |
| } |
| |
| if success { |
| let _ = self._flags.insert(ArchiverFlags.finishedEncoding) |
| } |
| } |
| |
| /// Adds a class translation mapping to `NSKeyedArchiver` whereby instances of a given |
| /// class are encoded with a given class name instead of their real class names. |
| /// |
| /// When encoding, the class’s translation mapping is used only if no translation |
| /// is found first in an instance’s separate translation map. |
| /// |
| /// - Parameters: |
| /// - codedName: The name of the class that `NSKeyedArchiver` uses in place of `cls`. |
| /// - cls: The class for which to set up a translation mapping. |
| open class func setClassName(_ codedName: String?, for cls: AnyClass) { |
| let clsName = String(describing: type(of: cls)) |
| _classNameMapLock.synchronized { |
| _classNameMap[clsName] = codedName |
| } |
| } |
| |
| /// Adds a class translation mapping to `NSKeyedArchiver` whereby instances of a given |
| /// class are encoded with a given class name instead of their real class names. |
| /// |
| /// When encoding, the receiver’s translation map overrides any translation |
| /// that may also be present in the class’s map. |
| /// |
| /// - Parameters: |
| /// - codedName: The name of the class that the archiver uses in place of `cls`. |
| /// - cls: The class for which to set up a translation mapping. |
| open func setClassName(_ codedName: String?, for cls: AnyClass) { |
| let clsName = String(describing: type(of: cls)) |
| _classNameMap[clsName] = codedName |
| } |
| |
| open override var systemVersion: UInt32 { |
| return NSKeyedArchiverSystemVersion |
| } |
| |
| open override var allowsKeyedCoding: Bool { |
| return true |
| } |
| |
| private func _validateStillEncoding() -> Bool { |
| if self._flags.contains(ArchiverFlags.finishedEncoding) { |
| fatalError("Encoder already finished") |
| } |
| |
| return true |
| } |
| |
| private class func _supportsSecureCoding(_ objv : Any?) -> Bool { |
| var supportsSecureCoding : Bool = false |
| |
| if let secureCodable = objv as? NSSecureCoding { |
| supportsSecureCoding = type(of: secureCodable).supportsSecureCoding |
| } |
| |
| return supportsSecureCoding |
| } |
| |
| private func _validateObjectSupportsSecureCoding(_ objv : Any?) { |
| if let objv = objv, self.requiresSecureCoding && |
| !NSKeyedArchiver._supportsSecureCoding(objv) { |
| fatalError("Secure coding required when encoding \(objv)") |
| } |
| } |
| |
| private func _createObjectRefCached(_ uid : UInt32) -> _NSKeyedArchiverUID { |
| if uid == 0 { |
| return NSKeyedArchiveNullObjectReference |
| } else if Int(uid) <= self._cache.count { |
| return self._cache[Int(uid) - 1] |
| } else { |
| let objectRef = _NSKeyedArchiverUID(value: uid) |
| self._cache.insert(objectRef, at: Int(uid) - 1) |
| return objectRef |
| } |
| } |
| |
| /** |
| Return a new object identifier, freshly allocated if need be. A placeholder null |
| object is associated with the reference. |
| */ |
| private func _referenceObject(_ objv: Any?, conditional: Bool = false) -> _NSKeyedArchiverUID? { |
| var uid : UInt32? |
| |
| if objv == nil { |
| return NSKeyedArchiveNullObjectReference |
| } |
| |
| let value = _SwiftValue.store(objv)! |
| |
| uid = self._objRefMap[value] |
| if uid == nil { |
| if conditional { |
| return nil // object has not been unconditionally encoded |
| } |
| |
| uid = UInt32(self._objects.count) |
| |
| self._objRefMap[value] = uid |
| self._objects.insert(NSKeyedArchiveNullObjectReferenceName, at: Int(uid!)) |
| } |
| |
| return _createObjectRefCached(uid!) |
| } |
| |
| /** |
| Returns true if the object has already been encoded. |
| */ |
| private func _haveVisited(_ objv: Any?) -> Bool { |
| if objv == nil { |
| return true // always have a null reference |
| } else { |
| return self._objRefMap[_SwiftValue.store(objv!)] != nil |
| } |
| } |
| |
| /** |
| Get or create an object reference, and associate the object. |
| */ |
| private func _addObject(_ objv: Any?) -> _NSKeyedArchiverUID? { |
| let haveVisited = _haveVisited(objv) |
| let objectRef = _referenceObject(objv) |
| |
| if !haveVisited { |
| _setObject(objv!, forReference: objectRef!) |
| } |
| |
| return objectRef |
| } |
| |
| private func _pushEncodingContext(_ encodingContext: EncodingContext) { |
| self._containers.append(encodingContext) |
| } |
| |
| private func _popEncodingContext() { |
| self._containers.removeLast() |
| } |
| |
| private var _currentEncodingContext : EncodingContext { |
| return self._containers.last! |
| } |
| |
| /** |
| Associate an encoded object or reference with a key in the current encoding context |
| */ |
| private func _setObjectInCurrentEncodingContext(_ object : Any?, forKey key: String? = nil, escape: Bool = true) { |
| let encodingContext = self._containers.last! |
| var encodingKey : String |
| |
| if key != nil { |
| if escape { |
| encodingKey = escapeArchiverKey(key!) |
| } else { |
| encodingKey = key! |
| } |
| } else { |
| encodingKey = _nextGenericKey() |
| } |
| |
| if encodingContext.dict[encodingKey] != nil { |
| NSLog("*** NSKeyedArchiver warning: replacing existing value for key '\(encodingKey)'; probable duplication of encoding keys in class hierarchy") |
| } |
| |
| encodingContext.dict[encodingKey] = object |
| } |
| |
| /** |
| The generic key is used for objects that are encoded without a key. It is a per-encoding |
| context monotonically increasing integer prefixed with "$". |
| */ |
| private func _nextGenericKey() -> String { |
| let key = "$" + String(_currentEncodingContext.genericKey) |
| _currentEncodingContext.genericKey += 1 |
| return key |
| } |
| |
| /** |
| Update replacement object mapping |
| */ |
| private func replaceObject(_ object: Any, withObject replacement: Any?) { |
| if let unwrappedDelegate = self.delegate { |
| unwrappedDelegate.archiver(self, willReplace: object, with: replacement) |
| } |
| |
| self._replacementMap[_SwiftValue.store(object)] = replacement |
| } |
| |
| /** |
| Returns true if the type cannot be encoded directly (i.e. is a container type) |
| */ |
| private func _isContainer(_ objv: Any?) -> Bool { |
| // Note that we check for class equality rather than membership, because |
| // their mutable subclasses are as object references |
| guard let obj = objv else { return false } |
| if obj is String { return false } |
| guard let nsObject = obj as? NSObject else { return true } |
| return !(nsObject.classForCoder === NSString.self || nsObject.classForCoder === NSNumber.self || nsObject.classForCoder === NSData.self) |
| } |
| |
| /** |
| Associates an object with an existing reference |
| */ |
| private func _setObject(_ objv: Any, forReference reference : _NSKeyedArchiverUID) { |
| let index = Int(reference.value) |
| self._objects[index] = objv |
| } |
| |
| /** |
| Returns a dictionary describing class metadata for a class |
| */ |
| private func _classDictionary(_ clsv: AnyClass) -> Dictionary<String, Any> { |
| func _classNameForClass(_ clsv: AnyClass) -> String? { |
| var className : String? |
| |
| className = classNameForClass(clsv) |
| if className == nil { |
| className = NSKeyedArchiver.classNameForClass(clsv) |
| } |
| |
| return className |
| } |
| |
| var classDict : [String:Any] = [:] |
| let className = NSStringFromClass(clsv) |
| let mappedClassName = _classNameForClass(clsv) |
| |
| if mappedClassName != nil && mappedClassName != className { |
| // If we have a mapped class name, OS X only encodes the mapped name |
| classDict["$classname"] = mappedClassName |
| } else { |
| var classChain : [String] = [] |
| var classIter : AnyClass? = clsv |
| |
| classDict["$classname"] = className |
| |
| repeat { |
| classChain.append(NSStringFromClass(classIter!)) |
| classIter = _getSuperclass(classIter!) |
| } while classIter != nil |
| |
| classDict["$classes"] = classChain |
| |
| if let ns = clsv as? NSObject.Type { |
| let classHints = ns.classFallbacksForKeyedArchiver() |
| if !classHints.isEmpty { |
| classDict["$classhints"] = classHints |
| } |
| } |
| } |
| |
| return classDict |
| } |
| |
| /** |
| Return an object reference for a class |
| |
| Because _classDictionary() returns a dictionary by value, and every |
| time we bridge to NSDictionary we get a new object (the hash code is |
| different), we maintain a private mapping between class name and |
| object reference to avoid redundantly encoding class metadata |
| */ |
| private func _classReference(_ clsv: AnyClass) -> _NSKeyedArchiverUID? { |
| let className = NSStringFromClass(clsv) |
| var classRef = self._classes[className] // keyed by actual class name |
| |
| if classRef == nil { |
| let classDict = _classDictionary(clsv) |
| classRef = _addObject(classDict._bridgeToObjectiveC()) |
| |
| if let unwrappedClassRef = classRef { |
| self._classes[className] = unwrappedClassRef |
| } |
| } |
| |
| return classRef |
| } |
| |
| /** |
| Return the object replacing another object (if any) |
| */ |
| private func _replacementObject(_ object: Any?) -> Any? { |
| var objectToEncode : Any? = nil // object to encode after substitution |
| |
| // nil cannot be mapped |
| if object == nil { |
| return nil |
| } |
| |
| // check replacement cache |
| objectToEncode = self._replacementMap[object as! AnyHashable] |
| if objectToEncode != nil { |
| return objectToEncode |
| } |
| |
| // object replaced by NSObject.replacementObject(for:) |
| // if it is replaced with nil, it cannot be further replaced |
| if let ns = objectToEncode as? NSObject { |
| objectToEncode = ns.replacementObject(for: self) |
| if objectToEncode == nil { |
| replaceObject(object!, withObject: nil) |
| return nil |
| } |
| } |
| |
| if objectToEncode == nil { |
| objectToEncode = object |
| } |
| |
| // object replaced by delegate. If the delegate returns nil, nil is encoded |
| if let unwrappedDelegate = self.delegate { |
| objectToEncode = unwrappedDelegate.archiver(self, willEncode: objectToEncode!) |
| replaceObject(object!, withObject: objectToEncode) |
| } |
| |
| return objectToEncode |
| } |
| |
| /** |
| Internal function to encode an object. Returns the object reference. |
| */ |
| private func _encodeObject(_ objv: Any?, conditional: Bool = false) -> NSObject? { |
| var object : Any? = nil // object to encode after substitution |
| var objectRef : _NSKeyedArchiverUID? // encoded object reference |
| let haveVisited : Bool |
| |
| let _ = _validateStillEncoding() |
| |
| haveVisited = _haveVisited(objv) |
| object = _replacementObject(objv) |
| |
| // bridge value types |
| if let bridgedObject = object as? _ObjectBridgeable { |
| object = bridgedObject._bridgeToAnyObject() |
| } |
| |
| objectRef = _referenceObject(object, conditional: conditional) |
| guard let unwrappedObjectRef = objectRef else { |
| // we can return nil if the object is being conditionally encoded |
| return nil |
| } |
| |
| _validateObjectSupportsSecureCoding(object) |
| |
| if !haveVisited { |
| var encodedObject : Any |
| |
| if _isContainer(object) { |
| guard let codable = object as? NSCoding else { |
| fatalError("Object \(String(describing: object)) does not conform to NSCoding") |
| } |
| |
| let innerEncodingContext = EncodingContext() |
| _pushEncodingContext(innerEncodingContext) |
| codable.encode(with: self) |
| |
| let ns = object as? NSObject |
| let cls : AnyClass = ns?.classForKeyedArchiver ?? type(of: object!) as! AnyClass |
| |
| _setObjectInCurrentEncodingContext(_classReference(cls), forKey: "$class", escape: false) |
| _popEncodingContext() |
| encodedObject = innerEncodingContext.dict |
| } else { |
| encodedObject = object! |
| } |
| |
| _setObject(encodedObject, forReference: unwrappedObjectRef) |
| } |
| |
| if let unwrappedDelegate = self.delegate { |
| unwrappedDelegate.archiver(self, didEncode: object) |
| } |
| |
| return unwrappedObjectRef |
| } |
| |
| /** |
| Encode an object and associate it with a key in the current encoding context. |
| */ |
| private func _encodeObject(_ objv: Any?, forKey key: String?, conditional: Bool = false) { |
| if let objectRef = _encodeObject(objv, conditional: conditional) { |
| _setObjectInCurrentEncodingContext(objectRef, forKey: key, escape: key != nil) |
| } |
| } |
| |
| open override func encode(_ object: Any?) { |
| _encodeObject(object, forKey: nil) |
| } |
| |
| open override func encodeConditionalObject(_ object: Any?) { |
| _encodeObject(object, forKey: nil, conditional: true) |
| } |
| |
| /// Encodes a given object and associates it with a given key. |
| /// |
| /// - Parameters: |
| /// - objv: The value to encode. |
| /// - key: The key with which to associate `objv`. |
| open override func encode(_ objv: Any?, forKey key: String) { |
| _encodeObject(objv, forKey: key, conditional: false) |
| } |
| |
| /// Encodes a reference to a given object and associates it with a given key |
| /// only if it has been unconditionally encoded elsewhere in the archive with `encode(_:forKey:)`. |
| /// |
| /// - Parameters: |
| /// - objv: The object to encode. |
| /// - key: The key with which to associate the encoded value. |
| open override func encodeConditionalObject(_ objv: Any?, forKey key: String) { |
| _encodeObject(objv, forKey: key, conditional: true) |
| } |
| |
| open override func encodePropertyList(_ aPropertyList: Any) { |
| if !NSPropertyListClasses.contains(where: { $0 == type(of: aPropertyList) }) { |
| fatalError("Cannot encode non-property list type \(type(of: aPropertyList)) as property list") |
| } |
| encode(aPropertyList) |
| } |
| |
| open func encodePropertyList(_ aPropertyList: Any, forKey key: String) { |
| if !NSPropertyListClasses.contains(where: { $0 == type(of: aPropertyList) }) { |
| fatalError("Cannot encode non-property list type \(type(of: aPropertyList)) as property list") |
| } |
| encode(aPropertyList, forKey: key) |
| } |
| |
| open func _encodePropertyList(_ aPropertyList: Any, forKey key: String? = nil) { |
| let _ = _validateStillEncoding() |
| _setObjectInCurrentEncodingContext(aPropertyList, forKey: key) |
| } |
| |
| internal func _encodeValue<T: NSObject>(_ objv: T, forKey key: String? = nil) where T: NSCoding { |
| _encodePropertyList(objv, forKey: key) |
| } |
| |
| private func _encodeValueOfObjCType(_ type: _NSSimpleObjCType, at addr: UnsafeRawPointer) { |
| switch type { |
| case .ID: |
| let objectp = addr.assumingMemoryBound(to: Any.self) |
| encode(objectp.pointee) |
| case .Class: |
| let classp = addr.assumingMemoryBound(to: AnyClass.self) |
| encode(NSStringFromClass(classp.pointee)._bridgeToObjectiveC()) |
| case .Char: |
| let charp = addr.assumingMemoryBound(to: CChar.self) |
| _encodeValue(NSNumber(value: charp.pointee)) |
| case .UChar: |
| let ucharp = addr.assumingMemoryBound(to: UInt8.self) |
| _encodeValue(NSNumber(value: ucharp.pointee)) |
| case .Int, .Long: |
| let intp = addr.assumingMemoryBound(to: Int32.self) |
| _encodeValue(NSNumber(value: intp.pointee)) |
| case .UInt, .ULong: |
| let uintp = addr.assumingMemoryBound(to: UInt32.self) |
| _encodeValue(NSNumber(value: uintp.pointee)) |
| case .LongLong: |
| let longlongp = addr.assumingMemoryBound(to: Int64.self) |
| _encodeValue(NSNumber(value: longlongp.pointee)) |
| case .ULongLong: |
| let ulonglongp = addr.assumingMemoryBound(to: UInt64.self) |
| _encodeValue(NSNumber(value: ulonglongp.pointee)) |
| case .Float: |
| let floatp = addr.assumingMemoryBound(to: Float.self) |
| _encodeValue(NSNumber(value: floatp.pointee)) |
| case .Double: |
| let doublep = addr.assumingMemoryBound(to: Double.self) |
| _encodeValue(NSNumber(value: doublep.pointee)) |
| case .Bool: |
| let boolp = addr.assumingMemoryBound(to: Bool.self) |
| _encodeValue(NSNumber(value: boolp.pointee)) |
| case .CharPtr: |
| let charpp = addr.assumingMemoryBound(to: UnsafePointer<Int8>.self) |
| encode(NSString(utf8String: charpp.pointee)) |
| default: |
| fatalError("NSKeyedArchiver.encodeValueOfObjCType: unknown type encoding ('\(type.rawValue)')") |
| } |
| } |
| |
| open override func encodeValue(ofObjCType typep: UnsafePointer<Int8>, at addr: UnsafeRawPointer) { |
| guard let type = _NSSimpleObjCType(UInt8(typep.pointee)) else { |
| let spec = String(typep.pointee) |
| fatalError("NSKeyedArchiver.encodeValueOfObjCType: unsupported type encoding spec '\(spec)'") |
| } |
| |
| if type == .StructBegin { |
| fatalError("NSKeyedArchiver.encodeValueOfObjCType: this archiver cannot encode structs") |
| } else if type == .ArrayBegin { |
| let scanner = Scanner(string: String(cString: typep)) |
| |
| scanner.scanLocation = 1 // advance past ObJCType |
| |
| var count : Int = 0 |
| guard scanner.scanInteger(&count) && count > 0 else { |
| fatalError("NSKeyedArchiver.encodeValueOfObjCType: array count is missing or zero") |
| } |
| |
| guard let elementType = _NSSimpleObjCType(scanner.scanUpToString(String(_NSSimpleObjCType.ArrayEnd))) else { |
| fatalError("NSKeyedArchiver.encodeValueOfObjCType: array type is missing") |
| } |
| |
| encode(_NSKeyedCoderOldStyleArray(objCType: elementType, count: count, at: addr)) |
| } else { |
| return _encodeValueOfObjCType(type, at: addr) |
| } |
| } |
| |
| /// Encodes a given Boolean value and associates it with a given key. |
| /// |
| /// - Parameters: |
| /// - boolv: The value to encode. |
| /// - key: The key with which to associate `boolv`. |
| open override func encode(_ boolv: Bool, forKey key: String) { |
| _encodeValue(NSNumber(value: boolv), forKey: key) |
| } |
| |
| |
| /// Encodes a given 32-bit integer value and associates it with a given key. |
| /// |
| /// - Parameters: |
| /// - intv: The value to encode. |
| /// - key: The key with which to associate `intv`. |
| open override func encode(_ intv: Int32, forKey key: String) { |
| _encodeValue(NSNumber(value: intv), forKey: key) |
| } |
| |
| /// Encodes a given 64-bit integer value and associates it with a given key. |
| /// |
| /// - Parameters: |
| /// - intv: The value to encode. |
| /// - key: The key with which to associate `intv`. |
| open override func encode(_ intv: Int64, forKey key: String) { |
| _encodeValue(NSNumber(value: intv), forKey: key) |
| } |
| |
| /// Encodes a given float value and associates it with a given key. |
| /// |
| /// - Parameters: |
| /// - realv: The value to encode. |
| /// - key: The key with which to associate `realv`. |
| open override func encode(_ realv: Float, forKey key: String) { |
| _encodeValue(NSNumber(value: realv), forKey: key) |
| } |
| |
| /// Encodes a given double value and associates it with a given key. |
| /// |
| /// - Parameters: |
| /// - realv: The value to encode. |
| /// - key: The key with which to associate `realv`. |
| open override func encode(_ realv: Double, forKey key: String) { |
| _encodeValue(NSNumber(value: realv), forKey: key) |
| } |
| |
| /// Encodes a given integer value and associates it with a given key. |
| /// |
| /// - Parameters: |
| /// - intv: The value to encode. |
| /// - key: The key with which to associate `intv`. |
| open override func encode(_ intv: Int, forKey key: String) { |
| _encodeValue(NSNumber(value: intv), forKey: key) |
| } |
| |
| open override func encode(_ data: Data) { |
| // this encodes as a reference to an NSData object rather than encoding inline |
| encode(data._nsObject) |
| } |
| |
| /// Encodes a given number of bytes from a given C array of bytes and associates |
| /// them with the a given key. |
| /// |
| /// - Parameters: |
| /// - bytesp: A C array of bytes to encode. |
| /// - lenv: The number of bytes from `bytesp` to encode. |
| /// - key: The key with which to associate the encoded value. |
| open override func encodeBytes(_ bytesp: UnsafePointer<UInt8>?, length lenv: Int, forKey key: String) { |
| // this encodes the data inline |
| let data = NSData(bytes: bytesp, length: lenv) |
| _encodeValue(data, forKey: key) |
| } |
| |
| /** |
| Helper API for NSArray and NSDictionary that encodes an array of objects, |
| creating references as it goes |
| */ |
| internal func _encodeArrayOfObjects(_ objects : NSArray, forKey key : String) { |
| var objectRefs = [NSObject]() |
| |
| objectRefs.reserveCapacity(objects.count) |
| |
| for object in objects { |
| let objectRef = _encodeObject(_SwiftValue.store(object))! |
| |
| objectRefs.append(objectRef) |
| } |
| |
| _encodeValue(objectRefs._bridgeToObjectiveC(), forKey: key) |
| } |
| |
| /// Indicates whether the archiver requires all archived classes to conform to `NSSecureCoding`. |
| /// |
| /// If you set the receiver to require secure coding, it will cause a fatal error |
| /// if you attempt to archive a class which does not conform to `NSSecureCoding`. |
| open override var requiresSecureCoding: Bool { |
| get { |
| return _flags.contains(ArchiverFlags.requiresSecureCoding) |
| } |
| set { |
| if newValue { |
| let _ = _flags.insert(ArchiverFlags.requiresSecureCoding) |
| } else { |
| _flags.remove(ArchiverFlags.requiresSecureCoding) |
| } |
| } |
| } |
| |
| /// Returns the class name with which `NSKeyedArchiver` encodes instances of a given class. |
| /// |
| /// - Parameter cls: The class for which to determine the translation mapping. |
| /// - Returns: The class name with which `NSKeyedArchiver` encodes instances of `cls`. |
| /// Returns `nil` if `NSKeyedArchiver` does not have a translation mapping for `cls`. |
| open class func classNameForClass(_ cls: AnyClass) -> String? { |
| let clsName = String(reflecting: cls) |
| var mappedClass : String? |
| |
| _classNameMapLock.synchronized { |
| mappedClass = _classNameMap[clsName] |
| } |
| |
| return mappedClass |
| } |
| |
| /// Returns the class name with which the archiver encodes instances of a given class. |
| /// |
| /// - Parameter cls: The class for which to determine the translation mapping. |
| /// - Returns: The class name with which the receiver encodes instances of cls. |
| /// Returns `nil` if the archiver does not have a translation |
| /// mapping for `cls`. The class’s separate translation map is not searched. |
| open func classNameForClass(_ cls: AnyClass) -> String? { |
| let clsName = String(reflecting: cls) |
| return _classNameMap[clsName] |
| } |
| } |
| |
| extension NSKeyedArchiverDelegate { |
| func archiver(_ archiver: NSKeyedArchiver, willEncode object: Any) -> Any? { |
| // Returning the same object is the same as doing nothing |
| return object |
| } |
| |
| func archiver(_ archiver: NSKeyedArchiver, didEncode object: Any?) { } |
| |
| func archiver(_ archiver: NSKeyedArchiver, willReplace object: Any?, with newObject: Any?) { } |
| |
| func archiverWillFinish(_ archiver: NSKeyedArchiver) { } |
| |
| func archiverDidFinish(_ archiver: NSKeyedArchiver) { } |
| |
| } |
| |
| /// The `NSKeyedArchiverDelegate` protocol defines the optional methods implemented |
| /// by delegates of `NSKeyedArchiver` objects. |
| public protocol NSKeyedArchiverDelegate : class { |
| |
| /// Informs the delegate that `object` is about to be encoded. |
| /// |
| /// This method is called after the original object may have replaced itself |
| /// with `replacementObject(for:)`. |
| /// |
| /// This method is called whether or not the object is being encoded conditionally. |
| /// |
| /// This method is not called for an object once a replacement mapping has been set up |
| /// for that object (either explicitly, or because the object has previously been encoded). |
| /// This method is also not called when `nil` is about to be encoded. |
| /// |
| /// - Parameters: |
| /// - archiver: The archiver that invoked the method. |
| /// - object: The object that is about to be encoded. |
| /// - Returns: Either object or a different object to be encoded in its stead. |
| /// The delegate can also modify the coder state. If the delegate |
| /// returns `nil`, `nil` is encoded. |
| func archiver(_ archiver: NSKeyedArchiver, willEncode object: Any) -> Any? |
| |
| /// Informs the delegate that a given object has been encoded. |
| /// |
| /// The delegate might restore some state it had modified previously, |
| /// or use this opportunity to keep track of the objects that are encoded. |
| /// |
| /// This method is not called for conditional objects until they are actually encoded (if ever). |
| /// |
| /// - Parameters: |
| /// - archiver: The archiver that invoked the method. |
| /// - object: The object that has been encoded. |
| func archiver(_ archiver: NSKeyedArchiver, didEncode object: Any?) |
| |
| /// Informs the delegate that one given object is being substituted for another given object. |
| /// |
| /// This method is called even when the delegate itself is doing, or has done, |
| /// the substitution. The delegate may use this method if it is keeping track |
| /// of the encoded or decoded objects. |
| /// |
| /// - Parameters: |
| /// - archiver: The archiver that invoked the method. |
| /// - object: The object being replaced in the archive. |
| /// - newObject: The object replacing `object` in the archive. |
| func archiver(_ archiver: NSKeyedArchiver, willReplace object: Any?, withObject newObject: Any?) |
| |
| |
| /// Notifies the delegate that encoding is about to finish. |
| /// |
| /// - Parameter archiver: The archiver that invoked the method. |
| func archiverWillFinish(_ archiver: NSKeyedArchiver) |
| |
| |
| /// Notifies the delegate that encoding has finished. |
| /// |
| /// - Parameter archiver: The archiver that invoked the method. |
| func archiverDidFinish(_ archiver: NSKeyedArchiver) |
| } |