Skip to content

Instantly share code, notes, and snippets.

@mingsai
Last active February 12, 2016 17:38
Show Gist options
  • Select an option

  • Save mingsai/758c58c4ddc5413e46cc to your computer and use it in GitHub Desktop.

Select an option

Save mingsai/758c58c4ddc5413e46cc to your computer and use it in GitHub Desktop.
A set of functions to convert an entire NSManagedObject graph to a dictionary using recursion. The advanced version fixes issues with related objects that were performing endless recursion. I found that when storing large data files (video) it was prudent to import the libz.x.x.x.tb and the libcompression.tb frameworks. Note the NSData compressi…
//
// Compression.swift
//
//
// Created by Tommie N. Carter, Jr., MBA on 2/11/16.
// Copyright © 2016 MING Technology. All rights reserved.
//
import Foundation
import Compression
/// A convenience class for compressing an NSData object.
///
/// Example:
/// ```
/// let compression = Compression(algorithm: .ZLIB)
/// let compressedData = compression.compressData(data)
/// let decompressedData = compresison.decompressData(compressedData)
/// ```
///
public final class Compression {
public enum Algorithm {
case LZ4 //Use LZ4 if speed is critical, and you are willing to sacrifice compression ratio to achieve it.
case LZFSE //LZFSE is an Apple-developed algorithm that is faster than ZLIB, and generally achieves a better compression ratio
case LZMA //Use LZMA if compression ratio is critical, and you are willing to sacrifice speed to achieve it. Note that LZMA is an order of magnitude slower for both compression and decompression than other choices.
case ZLIB //If you do require interoperability with non-Apple devices, use ZLIB. If you do not, use LZFSE.
public func algorithm() -> compression_algorithm {
switch self {
case .LZ4: return COMPRESSION_LZ4
case .LZFSE: return COMPRESSION_LZFSE
case .LZMA: return COMPRESSION_LZMA
case .ZLIB: return COMPRESSION_ZLIB
}
}
}
private let algorithm: Algorithm
private let bufferSize: Int
public init(algorithm: Algorithm, bufferSize: Int = 0x20000) {
self.algorithm = algorithm
self.bufferSize = bufferSize
}
public func compressData(data: NSData) -> NSData? {
return processData(data, algorithm: algorithm, bufferSize: bufferSize, compress: true)
}
public func decompressData(data: NSData) -> NSData? {
return processData(data, algorithm: algorithm, bufferSize: bufferSize, compress: true)
}
private func processData(inputData: NSData, algorithm: Algorithm, bufferSize: Int, compress: Bool) -> NSData? {
guard inputData.length > 0 else { return nil }
var stream = UnsafeMutablePointer<compression_stream>.alloc(1).memory
let initStatus = compression_stream_init(&stream, compress ? COMPRESSION_STREAM_ENCODE : COMPRESSION_STREAM_DECODE, algorithm.algorithm())
guard initStatus != COMPRESSION_STATUS_ERROR else {
print("[Compression] \(compress ? "Compression" : "Decompression") with \(algorithm) failed to init stream with status \(initStatus).")
return nil
}
defer {
compression_stream_destroy(&stream)
}
stream.src_ptr = UnsafePointer<UInt8>(inputData.bytes)
stream.src_size = inputData.length
let buffer = UnsafeMutablePointer<UInt8>.alloc(bufferSize)
stream.dst_ptr = buffer
stream.dst_size = bufferSize
let outputData = NSMutableData()
while true {
let status = compression_stream_process(&stream, Int32(compress ? COMPRESSION_STREAM_FINALIZE.rawValue : 0))
if status == COMPRESSION_STATUS_OK {
guard stream.dst_size == 0 else { continue }
outputData.appendBytes(buffer, length: bufferSize)
stream.dst_ptr = buffer
stream.dst_size = bufferSize
} else if status == COMPRESSION_STATUS_END {
guard stream.dst_ptr > buffer else { continue }
outputData.appendBytes(buffer, length: stream.dst_ptr - buffer)
return outputData
} else if status == COMPRESSION_STATUS_ERROR {
print("[Compression] \(compress ? "Compression" : "Decompression") with \(algorithm) failed with status \(status).")
return nil
}
}
}
}
extension NSData {
func mng_uncompressedDataFromString(encodedString:String) -> NSData {
if let decode = encodedString.NS.dataUsingEncoding(NSUTF8StringEncoding) {
let compression = Compression(algorithm: .LZ4)
return compression.decompressData(decode)!
}
return NSData()
}
func mng_compressedDataString () -> String {
let compression = Compression(algorithm: .LZ4)
let compressedData = compression.compressData(self)
let encode:String? = NSString (data: compressedData!, encoding: NSUTF8StringEncoding) as? String
if encode != nil {
return encode!
} else {
return String()
}
}
}
//
//
//
//
// Created by Tommie N. Carter, Jr., MBA on 2/9/16.
// Copyright © 2016 MING Technology. All rights reserved.
//
import CoreData
import CoreLocation.CLLocation
var managedObjectsTraversed:[NSManagedObjectID] = []
extension NSManagedObject {
var className: String? {
get {
return self.entity.name!.componentsSeparatedByString(".").last! as String
}
}
//MARK: - Dictionary conversion methods
func mng_isTraversed (anObjectIdentifier:NSManagedObjectID) -> Bool {
return managedObjectsTraversed.contains(anObjectIdentifier)
}
func mng_clearTraversed () {
managedObjectsTraversed.removeAll()
}
func mng_populateDictionary () -> NSDictionary {
guard mng_isTraversed(self.objectID) == false else { return NSDictionary() }
//add self to the traversed objects list
managedObjectsTraversed.append(self.objectID)
var dictionaryRepresentation = NSMutableDictionary()
//set class name of dictionary
if let cname = self.className {
dictionaryRepresentation.setObject(cname, forKey: "class")
}
//map and loop through attribute, add non-nil values to dictionary
_ = self.entity.attributesByName.map({
let attrName = $0.0
let attrType = $0.1.attributeType
if let objValue = self.valueForKeyPath(attrName) {
//convert data to strings and store
if attrType == NSAttributeType.BinaryDataAttributeType {
let encodedString = (objValue as? NSData)?.mng_compressedDataString()
if encodedString != nil {
dictionaryRepresentation.setObject(encodedString!, forKey: attrName)
}
} else if attrType == NSAttributeType.TransformableAttributeType {
//I handle special cases using standardized attribute names
enum specialCases:String { case location, url, array; static let allValues = [location,url,array] }
switch true {
case attrName.lowercaseString.containsString(specialCases.location.rawValue):
if let validLocation = objValue as? CLLocation {
let lxl = "{ \"longitude\" : \"\(validLocation.coordinate.longitude)\",\"latitude\" : \"\(validLocation.coordinate.latitude)\" }"
dictionaryRepresentation.setObject(lxl, forKey: attrName)
}
break
case attrName.lowercaseString.containsString(specialCases.url.rawValue):
if let validURL = objValue as? NSURL {
if let urlEncodedString:String? = validURL.absoluteString {
dictionaryRepresentation.setObject(urlEncodedString!, forKey: attrName)
}
}
break
case attrName.lowercaseString.containsString(specialCases.array.rawValue):
if let validArray = objValue as? NSArray {
if let arrayEncodedString:String? = validArray.description {
dictionaryRepresentation.setObject(arrayEncodedString!, forKey: attrName)
}
}
break
default:
dictionaryRepresentation.setObject(objValue, forKey: attrName)
break
}
} else if attrType == NSAttributeType.StringAttributeType {
// store string attribute types
if let encodedStringValue = objValue as? String {
dictionaryRepresentation.setObject(encodedStringValue, forKey: attrName)
}
} else if attrType == NSAttributeType.Integer64AttributeType || attrType == NSAttributeType.Integer32AttributeType || attrType == NSAttributeType.Integer16AttributeType {
//just store int attribute types
if let encodedIntValue = objValue as? Int {
dictionaryRepresentation.setObject(encodedIntValue, forKey: attrName)
}
} else if attrType == NSAttributeType.DateAttributeType {
//just store date attribute types
if let encodedDateValue = objValue as? NSDate {
dictionaryRepresentation.setObject(encodedDateValue, forKey: attrName)
}
} else if attrType == NSAttributeType.BooleanAttributeType {
//just store Bool attribute types
if let encodedBoolValue = objValue as? NSNumber {
dictionaryRepresentation.setObject(encodedBoolValue, forKey: attrName)
}
} else {
//just store other attribute types
dictionaryRepresentation.setObject(objValue, forKey: attrName)
}
}
})
//map and loop through relationship names
_ = self.entity.relationshipsByName.map({
let relName = $0.0
//setup array to hold sub objects info
let subObjects = NSMutableArray(capacity: self.objectIDsForRelationshipNamed(relName).count)
//map and loop through objectids for relationship named x
_ = self.objectIDsForRelationshipNamed(relName).map({
//managed object id
let managedObjectId = $0
//only operate on untraversed items
if !(mng_isTraversed(managedObjectId)) {
//get new object for proper relationship, recurse, serialize and add to sub objects array.
if let relatedDict = self.managedObjectContext?.objectWithID(managedObjectId).mng_populateDictionary() {
subObjects.addObject(relatedDict)
}
}
})
dictionaryRepresentation.setObject(subObjects, forKey: relName)
})
return dictionaryRepresentation.copy() as! NSDictionary
}
}
//
// MNGNSManagedfObjectExtensions-Basic.swift
// DO NOT USE THIS VERSION IN PRODUCTION CODE ~ CHECK OUT THE ADVANCED FILE AND DESCRIPTION
//
// Created by Tommie N. Carter, Jr., MBA on 2/9/16.
// Copyright © 2016 MING Technology. All rights reserved.
//
// From a Controller and assuming aManagedObject is valid
// 1 - clear traversed objects before spinning new dict
// aManagedObject.clearTraversed()
// 2 - recurse managed object graph and output to a dictionary
// let adict = aManagedObject.populateDictionary()
import CoreData
var managedObjectsTraversed:[NSManagedObjectID] = []
extension NSManagedObject {
var className: String? {
get {
return self.entity.name!.componentsSeparatedByString(".").last! as String
}
}
//MARK: - Dictionary conversion methods
func isTraversed (anObjectIdentifier:NSManagedObjectID) -> Bool {
return managedObjectsTraversed.contains(anObjectIdentifier)
}
func clearTraversed () {
managedObjectsTraversed.removeAll()
}
func populateDictionary () -> NSDictionary {
guard isTraversed(self.objectID) == false else { return NSDictionary() }
//add self to the traversed objects list
managedObjectsTraversed.append(self.objectID)
var dictionaryRepresentation = NSMutableDictionary()
//set class name of dictionary
dictionaryRepresentation.setObject(self.className!, forKey: "class")
//map and loop through attribute, add non-nil values to dictionary
_ = self.entity.attributesByName.map({
if let objValue = self.valueForKeyPath($0.0) {
dictionaryRepresentation.setObject(objValue, forKey: $0.0)
}
})
//map and loop through relationship names
_ = self.entity.relationshipsByName.map({
let relName = $0.0
//setup array to hold sub objects info
let subObjects = NSMutableArray(capacity: self.objectIDsForRelationshipNamed(relName).count)
//map and loop through objectids for relationship named x
_ = self.objectIDsForRelationshipNamed(relName).map({
//managed object id
let managedObjectId = $0
//only operate on untraversed items
if !(isTraversed(managedObjectId)) {
//get new object for proper relationship, recurse, serialize and add to sub objects array.
if let relatedDict = self.managedObjectContext?.objectWithID(managedObjectId).populateDictionary() {
subObjects.addObject(relatedDict)
}
}
})
dictionaryRepresentation.setObject(subObjects, forKey: relName)
})
return dictionaryRepresentation.copy() as! NSDictionary
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment