blob: 6df2ae1867b7d75715ed94455edc51ce04771310 [file] [log] [blame]
// 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
if let hashable = object as? AnyHashable {
objectToEncode = self._replacementMap[hashable]
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.scanInt(&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)
}