Last active
February 12, 2016 17:38
-
-
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…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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 | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // | |
| // | |
| // | |
| // 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 | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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