diff --git a/AlecrimCoreData.podspec b/AlecrimCoreData.podspec index 33fa8e3..ffe9a1a 100644 --- a/AlecrimCoreData.podspec +++ b/AlecrimCoreData.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AlecrimCoreData" - s.version = "3.2" + s.version = “3.3” s.summary = "A framework to easily access Core Data objects in Swift." s.homepage = "https://github.com/Alecrim/AlecrimCoreData" diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftAppKit.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftAppKit.dylib index 0dcdf8d..ba1cbd2 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftAppKit.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftAppKit.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftCore.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftCore.dylib index c6cc116..beacb9f 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftCore.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftCore.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftCoreGraphics.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftCoreGraphics.dylib index ad991c4..b565b50 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftCoreGraphics.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftCoreGraphics.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftDarwin.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftDarwin.dylib index fd924a5..7f25fea 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftDarwin.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftDarwin.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftDispatch.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftDispatch.dylib index a9058bb..c2167f4 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftDispatch.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftDispatch.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftFoundation.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftFoundation.dylib index 9912b0c..36b7800 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftFoundation.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftFoundation.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftObjectiveC.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftObjectiveC.dylib index a19b011..929fa3a 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftObjectiveC.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftObjectiveC.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftQuartzCore.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftQuartzCore.dylib index 73c001b..7564c14 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftQuartzCore.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftQuartzCore.dylib differ diff --git a/Bin/ACDGen.app/Contents/Frameworks/libswiftSecurity.dylib b/Bin/ACDGen.app/Contents/Frameworks/libswiftSecurity.dylib index 1a1117a..b3479f9 100755 Binary files a/Bin/ACDGen.app/Contents/Frameworks/libswiftSecurity.dylib and b/Bin/ACDGen.app/Contents/Frameworks/libswiftSecurity.dylib differ diff --git a/Bin/ACDGen.app/Contents/Info.plist b/Bin/ACDGen.app/Contents/Info.plist index e9d9bc8..54bed5b 100644 --- a/Bin/ACDGen.app/Contents/Info.plist +++ b/Bin/ACDGen.app/Contents/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 14E11f + 14F6a CFBundleDevelopmentRegion en CFBundleExecutable @@ -19,15 +19,15 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.1 + 1.1.2 CFBundleSignature ???? CFBundleVersion - 32 + 33 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 6D1002 + 6E35b DTPlatformVersion GM DTSDKBuild @@ -35,9 +35,9 @@ DTSDKName macosx10.10 DTXcode - 0631 + 0640 DTXcodeBuild - 6D1002 + 6E35b LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/Bin/ACDGen.app/Contents/MacOS/ACDGen b/Bin/ACDGen.app/Contents/MacOS/ACDGen index c06d477..2ce44d9 100755 Binary files a/Bin/ACDGen.app/Contents/MacOS/ACDGen and b/Bin/ACDGen.app/Contents/MacOS/ACDGen differ diff --git a/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib b/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib index 823a9a1..a94588f 100644 Binary files a/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib and b/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib differ diff --git a/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib b/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib index d7584f3..c350dd8 100644 Binary files a/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib and b/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib differ diff --git a/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib b/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib index fed4fa9..7d61f1f 100644 Binary files a/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib and b/Bin/ACDGen.app/Contents/Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib differ diff --git a/Bin/ACDGen.app/Contents/_CodeSignature/CodeResources b/Bin/ACDGen.app/Contents/_CodeSignature/CodeResources index 3bf0e99..93f40f7 100644 --- a/Bin/ACDGen.app/Contents/_CodeSignature/CodeResources +++ b/Bin/ACDGen.app/Contents/_CodeSignature/CodeResources @@ -21,7 +21,7 @@ hash - 1QLSM8wD6PETb9WikPZ7qoJi710= + iRDK0lLdWU6w1WpjWWkWxVcCtRo= optional @@ -30,7 +30,7 @@ hash - nyIYDih/MI5raAWcKpT042/VZ9M= + 1CaM5HgncGgXzXNg75SO3MfshXQ= optional @@ -39,7 +39,7 @@ hash - 9p76Ts+XwQH622Nl7XjEtVKTMcI= + Uo8KT6kGKsN5+b/snwMEWcVyZO0= optional @@ -51,7 +51,7 @@ cdhash - G++ATML31mBsDB4G48ZYzLABB8g= + ovN+eirbZqGi0KECviPKVnUivSk= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftAppKit" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -60,7 +60,7 @@ cdhash - Tli1Xdsl7Y/b+nxbDPSyWNGUmes= + 5gdknEt+515QI5xw/kSbgq6Vuws= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftCore" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -69,7 +69,7 @@ cdhash - q32POiFZgLcG+HKg2wLxPkF4QiU= + 8s8g4p9zT9KHnFLHHNaJKVb5IbA= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftCoreGraphics" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -78,7 +78,7 @@ cdhash - JTdAPuucn59f2AKG/QR9yn/DDKM= + hO8HbeYaQIs2Lg5+Xglmn7F58GY= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftDarwin" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -87,7 +87,7 @@ cdhash - 7IDimHTbibzYAeAXWH2SIDux/MY= + yOZDTamXbl6ynLZp9qnzsE3tHzg= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftDispatch" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -96,7 +96,7 @@ cdhash - 1NdbGrSQ9LwhImTopsT2FQKGKGA= + ncZ69coM/h8All8+sujyXKVZabE= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftFoundation" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -105,7 +105,7 @@ cdhash - b0uuHstZJeA6erWNbjGcjGp5kwg= + AVGxQjqZLcqxoXWpdBC83bZu7YU= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftObjectiveC" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -114,7 +114,7 @@ cdhash - Vb0Kk8Hlra6m2x/vyngAEyM2yS8= + RYIPNdIiMVv/KcQrqHbUvjknkjk= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftQuartzCore" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -123,7 +123,7 @@ cdhash - nho4KXg/53dSC/hiIfOZrId1uM8= + giDIZNjLFTIUKIN7HJ5GxokPK8U= requirement anchor apple generic and identifier "com.apple.dt.runtime.swiftSecurity" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "89GFVQ5PK6") @@ -145,7 +145,7 @@ hash - 1QLSM8wD6PETb9WikPZ7qoJi710= + iRDK0lLdWU6w1WpjWWkWxVcCtRo= optional @@ -154,7 +154,7 @@ hash - nyIYDih/MI5raAWcKpT042/VZ9M= + 1CaM5HgncGgXzXNg75SO3MfshXQ= optional @@ -163,7 +163,7 @@ hash - 9p76Ts+XwQH622Nl7XjEtVKTMcI= + Uo8KT6kGKsN5+b/snwMEWcVyZO0= optional diff --git a/Examples/iOS/AlecrimCoreDataExample.xcodeproj/project.pbxproj b/Examples/iOS/AlecrimCoreDataExample.xcodeproj/project.pbxproj index 3525955..0fe918d 100644 --- a/Examples/iOS/AlecrimCoreDataExample.xcodeproj/project.pbxproj +++ b/Examples/iOS/AlecrimCoreDataExample.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 1448270F1A2BE7B000DA53F5 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1448270D1A2BE7B000DA53F5 /* LaunchScreen.xib */; }; 1448272F1A2BE98A00DA53F5 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1448272E1A2BE98A00DA53F5 /* Event.swift */; }; 144827311A2BE9A400DA53F5 /* DataContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144827301A2BE9A400DA53F5 /* DataContext.swift */; }; + 8B5131621B1CE3A1007001C0 /* SubEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B5131611B1CE3A1007001C0 /* SubEvent.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -47,6 +48,7 @@ 1448270E1A2BE7B000DA53F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 1448272E1A2BE98A00DA53F5 /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; 144827301A2BE9A400DA53F5 /* DataContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataContext.swift; sourceTree = ""; }; + 8B5131611B1CE3A1007001C0 /* SubEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubEvent.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -90,6 +92,7 @@ 144827011A2BE7B000DA53F5 /* AlecrimCoreDataExample.xcdatamodeld */, 144827301A2BE9A400DA53F5 /* DataContext.swift */, 1448272E1A2BE98A00DA53F5 /* Event.swift */, + 8B5131611B1CE3A1007001C0 /* SubEvent.swift */, 144826FD1A2BE7B000DA53F5 /* Supporting Files */, ); path = AlecrimCoreDataExample; @@ -183,6 +186,7 @@ buildActionMask = 2147483647; files = ( 144827001A2BE7B000DA53F5 /* AppDelegate.swift in Sources */, + 8B5131621B1CE3A1007001C0 /* SubEvent.swift in Sources */, 144827311A2BE9A400DA53F5 /* DataContext.swift in Sources */, 1448272F1A2BE98A00DA53F5 /* Event.swift in Sources */, 144827031A2BE7B000DA53F5 /* AlecrimCoreDataExample.xcdatamodeld in Sources */, diff --git a/Examples/iOS/AlecrimCoreDataExample/AlecrimCoreDataExample.xcdatamodeld/AlecrimCoreDataExample.xcdatamodel/contents b/Examples/iOS/AlecrimCoreDataExample/AlecrimCoreDataExample.xcdatamodeld/AlecrimCoreDataExample.xcdatamodel/contents index 4b34044..f1b71b6 100644 --- a/Examples/iOS/AlecrimCoreDataExample/AlecrimCoreDataExample.xcdatamodeld/AlecrimCoreDataExample.xcdatamodel/contents +++ b/Examples/iOS/AlecrimCoreDataExample/AlecrimCoreDataExample.xcdatamodeld/AlecrimCoreDataExample.xcdatamodel/contents @@ -1,12 +1,20 @@ - + + + + + + + + - + + \ No newline at end of file diff --git a/Examples/iOS/AlecrimCoreDataExample/Base.lproj/Main.storyboard b/Examples/iOS/AlecrimCoreDataExample/Base.lproj/Main.storyboard index b3b5a78..6a81638 100644 --- a/Examples/iOS/AlecrimCoreDataExample/Base.lproj/Main.storyboard +++ b/Examples/iOS/AlecrimCoreDataExample/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -22,7 +22,7 @@ - + @@ -32,23 +32,40 @@ + + - - - + + + + + + + + @@ -73,7 +90,7 @@ - + diff --git a/Examples/iOS/AlecrimCoreDataExample/DataContext.swift b/Examples/iOS/AlecrimCoreDataExample/DataContext.swift index 9f19b1f..f67fcae 100644 --- a/Examples/iOS/AlecrimCoreDataExample/DataContext.swift +++ b/Examples/iOS/AlecrimCoreDataExample/DataContext.swift @@ -1,18 +1,20 @@ // // DataContext.swift -// AlecrimCoreDataExample // -// Created by Vanderlei Martinelli on 2014-11-30. -// Copyright (c) 2014 Alecrim. All rights reserved. +// This code was generated by AlecrimCoreData code generator tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // import Foundation import AlecrimCoreData -let dataContext = DataContext()! +let dataContext = DataContext() -final class DataContext: AlecrimCoreData.Context { +class DataContext: AlecrimCoreData.Context { var events: AlecrimCoreData.Table { return AlecrimCoreData.Table(context: self) } - -} \ No newline at end of file + var subEvents: AlecrimCoreData.Table { return AlecrimCoreData.Table(context: self) } + +} diff --git a/Examples/iOS/AlecrimCoreDataExample/DetailViewController.swift b/Examples/iOS/AlecrimCoreDataExample/DetailViewController.swift index 9cd6afe..acf4007 100644 --- a/Examples/iOS/AlecrimCoreDataExample/DetailViewController.swift +++ b/Examples/iOS/AlecrimCoreDataExample/DetailViewController.swift @@ -11,6 +11,8 @@ import UIKit class DetailViewController: UIViewController { @IBOutlet weak var detailDescriptionLabel: UILabel! + @IBOutlet var detailChildLabel: UILabel! + @IBOutlet var detailChildrenLabel: UILabel! var detailItem: Event? { @@ -22,8 +24,18 @@ class DetailViewController: UIViewController { func configureView() { // Update the user interface for the detail item. - if let label = self.detailDescriptionLabel, let event = self.detailItem { - label.text = event.timeStamp.description + if let event = self.detailItem { + if let label = self.detailDescriptionLabel { + label.text = event.timeStamp.description + } + + if let label = self.detailChildLabel, let child = event.child { + detailChildLabel.text = child.title + } + + if let label = self.detailChildrenLabel where event.children.count > 0 { + detailChildrenLabel.text = (map(event.children, { $0.title! }) as NSArray).componentsJoinedByString(", ") + } } } diff --git a/Examples/iOS/AlecrimCoreDataExample/Event.swift b/Examples/iOS/AlecrimCoreDataExample/Event.swift index 7b89fcf..c17116e 100644 --- a/Examples/iOS/AlecrimCoreDataExample/Event.swift +++ b/Examples/iOS/AlecrimCoreDataExample/Event.swift @@ -1,23 +1,47 @@ // // Event.swift -// AlecrimCoreDataExample // -// Created by Vanderlei Martinelli on 2014-11-30. -// Copyright (c) 2014 Alecrim. All rights reserved. +// This code was generated by AlecrimCoreData code generator tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // import Foundation import CoreData + import AlecrimCoreData class Event: NSManagedObject { @NSManaged var timeStamp: NSDate + @NSManaged var child: SubEvent? + + @NSManaged var children: Set + } +// MARK: - AlecrimCoreData query attributes + extension Event { - + static let timeStamp = AlecrimCoreData.Attribute("timeStamp") - + + static let child = SubEventAttribute("child") + + static let children = AlecrimCoreData.EntitySetAttribute>("children") + +} + +class EventAttribute: AlecrimCoreData.SingleEntityAttribute { + + override init(_ name: String) { super.init(name) } + + lazy var timeStamp: AlecrimCoreData.Attribute = { AlecrimCoreData.Attribute("\(self.___name).timeStamp") }() + + lazy var child: SubEventAttribute = { SubEventAttribute("\(self.___name).child") }() + + lazy var children: AlecrimCoreData.EntitySetAttribute> = { AlecrimCoreData.EntitySetAttribute>("\(self.___name).children") }() + } diff --git a/Examples/iOS/AlecrimCoreDataExample/MasterViewController.swift b/Examples/iOS/AlecrimCoreDataExample/MasterViewController.swift index 8fd31fe..bc721ae 100644 --- a/Examples/iOS/AlecrimCoreDataExample/MasterViewController.swift +++ b/Examples/iOS/AlecrimCoreDataExample/MasterViewController.swift @@ -55,6 +55,19 @@ class MasterViewController: UITableViewController { // Configure the new managed object. newEvent.timeStamp = NSDate() + // Create child + newEvent.child = dataContext.subEvents.createEntity() + newEvent.child?.title = "My child" + + // Create children + var child1 = dataContext.subEvents.createEntity() + child1.title = "My child 1" + newEvent.children.insert(child1) + + var child2 = dataContext.subEvents.createEntity() + child2.title = "My child 2" + newEvent.children.insert(child2) + // Save the background data context. let (success, error) = dataContext.save() if !success { diff --git a/Examples/iOS/AlecrimCoreDataExample/SubEvent.swift b/Examples/iOS/AlecrimCoreDataExample/SubEvent.swift new file mode 100644 index 0000000..0736edf --- /dev/null +++ b/Examples/iOS/AlecrimCoreDataExample/SubEvent.swift @@ -0,0 +1,35 @@ +// +// SubEvent.swift +// +// This code was generated by AlecrimCoreData code generator tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// + +import Foundation +import CoreData + +import AlecrimCoreData + +class SubEvent: NSManagedObject { + + @NSManaged var title: String? + +} + +// MARK: - AlecrimCoreData query attributes + +extension SubEvent { + + static let title = AlecrimCoreData.Attribute("title") + +} + +class SubEventAttribute: AlecrimCoreData.SingleEntityAttribute { + + override init(_ name: String) { super.init(name) } + + lazy var title: AlecrimCoreData.Attribute = { AlecrimCoreData.Attribute("\(self.___name).title") }() + +} diff --git a/README.md b/README.md index 15ff246..3d1265b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ To use AlecrimCoreData you will need to create an inherited class from `AlecrimC ```swift import AlecrimCoreData -let dataContext = DataContext()! +let dataContext = DataContext() class DataContext: Context { var people: Table { return Table(context: self) } @@ -351,6 +351,10 @@ See `Samples` folder for a configuration example for iCloud Core Data sync. See `Samples` folder for a configuration example for [Ensembles](http://www.ensembles.io). +#### Additional Notes + +When using the default options, AlecrimCoreData determines the name of the `.xcdatamodeld` automatically using the project name. Please ensure the names match. + ## Using ### Minimum Requirements diff --git a/Samples/AppExtensionDataContext.swift b/Samples/AppExtensionDataContext.swift index 0f828f9..a0e4c26 100644 --- a/Samples/AppExtensionDataContext.swift +++ b/Samples/AppExtensionDataContext.swift @@ -15,24 +15,7 @@ class AppExtensionDataContext: AlecrimCoreData.Context { // MARK - custom init convenience init?() { - let contextOptions = ContextOptions(stackType: .SQLite) - - // only needed if entity class names are different from entity names - contextOptions.entityClassNameSuffix = "Entity" - - // needed as your model probably is not in the main bundle - contextOptions.modelBundle = NSBundle(forClass: AppExtensionDataContext.self) - - // set the managed object model name, usually the same name as the main app name - contextOptions.managedObjectModelName = "MyModelName" - - // must be set to not infer from main bundle - contextOptions.persistentStoreRelativePath = "com.company.MyAppName/CoreData" - - // the same identifier used to group your main app and its extensions - contextOptions.applicationGroupIdentifier = "group.com.company.MyAppName" - - // call designated initializer + let contextOptions = ContextOptions(managedObjectModelBundle: NSBundle(forClass: AppExtensionDataContext.self), applicationGroupIdentifier: "group.com.company.MyAppName") self.init(contextOptions: contextOptions) } diff --git a/Samples/EnsemblesDataContext.swift b/Samples/EnsemblesDataContext.swift index ac2c952..dfa427c 100644 --- a/Samples/EnsemblesDataContext.swift +++ b/Samples/EnsemblesDataContext.swift @@ -25,15 +25,7 @@ class EnsemblesDataContext: AlecrimCoreData.Context { // MARK - custom init convenience init?() { - let contextOptions = ContextOptions(stackType: .SQLite) - - // only needed if entity class names are different from entity names - contextOptions.entityClassNameSuffix = "Entity" - - // only needed if model is not in main bundle - contextOptions.modelBundle = NSBundle(forClass: EnsemblesDataContext.self) - - // call designated initializer + let contextOptions = ContextOptions() self.init(contextOptions: contextOptions) // configure Ensembles @@ -46,7 +38,7 @@ class EnsemblesDataContext: AlecrimCoreData.Context { ) // assign delegate - self.ensembleDelegate = EnsembleDelegate(managedObjectContext: self.managedObjectContext) + self.ensembleDelegate = EnsembleDelegate(managedObjectContext: self) self.ensemble.delegate = self.ensembleDelegate // set observers diff --git a/Samples/iCloudDataContext.swift b/Samples/iCloudDataContext.swift index 7f66081..e758b16 100644 --- a/Samples/iCloudDataContext.swift +++ b/Samples/iCloudDataContext.swift @@ -15,22 +15,20 @@ class iCloudDataContext: AlecrimCoreData.Context { // MARK - custom init convenience init?() { - let contextOptions = ContextOptions(stackType: .SQLite) + // + let contextOptions = ContextOptions() - // only needed if entity class names are different from entity names - contextOptions.entityClassNameSuffix = "Entity" + // + let ubiquitousContainerIdentifier = "iCloud.com.company.MyAppName" + let ubiquitousContentURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(ubiquitousContainerIdentifier)!.URLByAppendingPathComponent("CoreData", isDirectory: true) + let ubiquitousContentName = "UbiquityStore" - // only needed if model is not in main bundle - contextOptions.modelBundle = NSBundle(forClass: iCloudDataContext.self) - - // only needed if the identifier is different from default identifier - contextOptions.ubiquitousContainerIdentifier = "iCloud.com.company.MyAppName" - - // enable iCloud Core Data sync - contextOptions.ubiquityEnabled = true + contextOptions.configureUbiquityOptionsWithUbiquitousContainerIdentifier(ubiquitousContainerIdentifier, ubiquitousContentURL: ubiquitousContentURL, ubiquitousContentName: ubiquitousContentName) // call designated initializer self.init(contextOptions: contextOptions) + + // here you can add observers for ubiquity notifications (AlecrimCoreData.Context is a subclass of NSManagedObjectContext) } } diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index b9d423c..c8104f8 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 1464BD1D1B20F973007CD240 /* Attribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1464BD0F1B20F973007CD240 /* Attribute.swift */; }; 1464BD1E1B20F973007CD240 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1464BD111B20F973007CD240 /* Context.swift */; }; 1464BD1F1B20F973007CD240 /* ContextOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1464BD121B20F973007CD240 /* ContextOptions.swift */; }; - 1464BD201B20F973007CD240 /* Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1464BD131B20F973007CD240 /* Stack.swift */; }; 1464BD211B20F973007CD240 /* NSManagedObjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1464BD151B20F973007CD240 /* NSManagedObjectExtensions.swift */; }; 1464BD221B20F973007CD240 /* FetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1464BD171B20F973007CD240 /* FetchedResultsController.swift */; }; 1464BD231B20F973007CD240 /* FetchedResultsSectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1464BD181B20F973007CD240 /* FetchedResultsSectionInfo.swift */; }; @@ -31,7 +30,6 @@ 1464BD0F1B20F973007CD240 /* Attribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attribute.swift; sourceTree = ""; }; 1464BD111B20F973007CD240 /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; }; 1464BD121B20F973007CD240 /* ContextOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextOptions.swift; sourceTree = ""; }; - 1464BD131B20F973007CD240 /* Stack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stack.swift; sourceTree = ""; }; 1464BD151B20F973007CD240 /* NSManagedObjectExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSManagedObjectExtensions.swift; sourceTree = ""; }; 1464BD171B20F973007CD240 /* FetchedResultsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchedResultsController.swift; sourceTree = ""; }; 1464BD181B20F973007CD240 /* FetchedResultsSectionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchedResultsSectionInfo.swift; sourceTree = ""; }; @@ -87,7 +85,6 @@ children = ( 1464BD111B20F973007CD240 /* Context.swift */, 1464BD121B20F973007CD240 /* ContextOptions.swift */, - 1464BD131B20F973007CD240 /* Stack.swift */, 149733731B21F716000A0EDE /* FetchAsyncHandler.swift */, ); path = Context; @@ -239,7 +236,6 @@ 1464BD251B20F973007CD240 /* Query.swift in Sources */, 1464BD241B20F973007CD240 /* AttributeQuery.swift in Sources */, 1464BD2A1B20FA29007CD240 /* EntitySetAttribute.swift in Sources */, - 1464BD201B20F973007CD240 /* Stack.swift in Sources */, 1464BD1D1B20F973007CD240 /* Attribute.swift in Sources */, 1464BD1E1B20F973007CD240 /* Context.swift in Sources */, 1464BD261B20F973007CD240 /* Table.swift in Sources */, @@ -354,7 +350,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 259; + CURRENT_PROJECT_VERSION = 291; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = "$(DYLIB_CURRENT_VERSION)"; @@ -368,7 +364,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_INSTALL_OBJC_HEADER = YES; }; name = Debug; }; @@ -377,7 +373,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 259; + CURRENT_PROJECT_VERSION = 291; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = "$(DYLIB_CURRENT_VERSION)"; DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)"; @@ -389,7 +385,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_INSTALL_OBJC_HEADER = YES; }; name = Release; }; diff --git a/Source/AlecrimCoreData/Classes/Attributes/EntitySetAttribute.swift b/Source/AlecrimCoreData/Classes/Attributes/EntitySetAttribute.swift index 74fcbab..abba1b4 100644 --- a/Source/AlecrimCoreData/Classes/Attributes/EntitySetAttribute.swift +++ b/Source/AlecrimCoreData/Classes/Attributes/EntitySetAttribute.swift @@ -12,7 +12,9 @@ public class EntitySetAttribute: Attribute { public override init(_ name: String) { super.init(name) } - public lazy var count: EntitySetCollectionOperatorAttribute = EntitySetCollectionOperatorAttribute(collectionOperator: "@count", entitySetAttributeName: self.___name) + public lazy var count: EntitySetCollectionOperatorAttribute = { [unowned self] in + return EntitySetCollectionOperatorAttribute(collectionOperator: "@count", entitySetAttributeName: self.___name) + }() public func any(predicateClosure: (T.Generator.Element.Type) -> NSComparisonPredicate) -> NSComparisonPredicate { let p = predicateClosure(T.Generator.Element.self) @@ -63,13 +65,29 @@ public class EntitySetAttribute: Attribute { } public func none(predicateClosure: (T.Generator.Element.Type) -> NSComparisonPredicate) -> NSPredicate { + // *** METHOD 1 *** // + // Doesn't work because Core Data bug with NONE (Filled out Apple bug # 21994962) + // http://stackoverflow.com/a/14473445/235334 + //let p = self.all(predicateClosure) + //let format = "NONE" + (p.description as NSString).substringFromIndex(3) + //return NSPredicate(format: format) + + // *** METHOD 2 *** // + // Doesn't work probably because same Core Data bug with NONE above + // http://stackoverflow.com/questions/6866950 + // Although close, where is the NSComparisonPredicateModifier.NonePredicateModifier? + //let p = self.any(predicateClosure) + //return NSCompoundPredicate.notPredicateWithSubpredicate(p) + + // *** METHOD 3 *** // + // This is really super ugly but works let p = self.all(predicateClosure) + let pFormat = (p.description as NSString).substringFromIndex(3) + .stringByReplacingOccurrencesOfString( + self.___name, withString: "$o", options: .LiteralSearch, range: nil) - // this is really ugly! (where is the NSComparisonPredicateModifier.NonePredicateModifier?) - // TODO: find a better way to do this - let format = "NONE" + (p.description as NSString).substringFromIndex(3) + let format = "SUBQUERY(\(self.___name), $o, \(pFormat)).@count == 0" - // return NSPredicate(format: format) } diff --git a/Source/AlecrimCoreData/Classes/Context/Context.swift b/Source/AlecrimCoreData/Classes/Context/Context.swift index d5b53bc..7dfaf63 100644 --- a/Source/AlecrimCoreData/Classes/Context/Context.swift +++ b/Source/AlecrimCoreData/Classes/Context/Context.swift @@ -9,184 +9,289 @@ import Foundation import CoreData -public class Context { +@objc(ALCContext) +public class Context: ChildContext { - private let stack: Stack! - public let managedObjectContext: NSManagedObjectContext! // the underlying managed object context + // MARK: - public properties + + // This will be removed in the next major version. + + private lazy var _defaultBackgroundContext: NSManagedObjectContext = { + return self.dynamicType(parentContext: self) + }() - private var ___background: Bool = false // background context machinery (you did not see it) + private func defaultCreatedBackgroundContext() -> Self { + return unsafeBitCast(self._defaultBackgroundContext, self.dynamicType) + } - internal var contextOptions: ContextOptions { return self.stack.contextOptions } - - public required init?(contextOptions: ContextOptions? = nil) { - let stackContextOptions = (contextOptions == nil ? ContextOptions() : contextOptions!) - stackContextOptions.fillEmptyOptions() - - var stack = stackContextOptions.___stack - if stack == nil { - stack = Stack(contextOptions: stackContextOptions) - self.managedObjectContext = stack?.mainManagedObjectContext - } - else { - if stackContextOptions.___stackUsesNewBackgroundManagedObjectContext { - self.managedObjectContext = stack?.createBackgroundManagedObjectContext() - stackContextOptions.___stackUsesNewBackgroundManagedObjectContext = false - } - else { - self.managedObjectContext = stack?.backgroundManagedObjectContext - } - - self.___background = true - stackContextOptions.___stack = nil - } - - self.stack = stack + // MARK: - init and dealloc + + public convenience init() { + self.init(contextOptions: ContextOptions()) } - public init?(rootManagedObjectContext: NSManagedObjectContext, mainManagedObjectContext: NSManagedObjectContext) { - if let coordinator = rootManagedObjectContext.persistentStoreCoordinator, let store = coordinator.persistentStores.first as? NSPersistentStore { - var stackType: StackType! = nil - if store.type == NSSQLiteStoreType { - stackType = .SQLite - } - else if store.type == NSInMemoryStoreType { - stackType = .InMemory - } + public init(contextOptions: ContextOptions) { + let rootSavingContext = RootSavingContext(contextOptions: contextOptions) + super.init(concurrencyType: .MainQueueConcurrencyType, rootSavingContext: rootSavingContext) + self.name = "Main Thread Context" + } - if stackType != nil { - let stackContextOptions = ContextOptions(stackType: stackType, managedObjectModelName: nil, storeOptions: store.options) - stackContextOptions.fillEmptyOptions(customConfiguration: true) - - if let stack = Stack(rootManagedObjectContext: rootManagedObjectContext, mainManagedObjectContext: mainManagedObjectContext, contextOptions: stackContextOptions) { - self.stack = stack - self.managedObjectContext = stack.mainManagedObjectContext - } - else { - self.stack = nil - self.managedObjectContext = nil - - return nil - } - } - else { - self.stack = nil - self.managedObjectContext = nil - - return nil - } - } - else { - self.stack = nil - self.managedObjectContext = nil - - return nil - } - + public required init(parentContext: Context) { + super.init(concurrencyType: .PrivateQueueConcurrencyType, rootSavingContext: parentContext.rootSavingContext) + self.name = "Background Context" + self.undoManager = nil + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } + } -extension Context { +// MARK: - BaseContext + +public class BaseContext: NSManagedObjectContext { - public func save() -> (success: Bool, error: NSError?) { - return self.stack.saveManagedObjectContext(self.managedObjectContext) - } + // MARK: - private properties -} + private var observers = [NSObjectProtocol]() + + + // MARK: - init and dealloc + + public override init(concurrencyType: NSManagedObjectContextConcurrencyType) { + super.init(concurrencyType: concurrencyType) + self.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + self.addObservers() + } -extension Context { + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - public func undo() { - self.managedObjectContext.undo() + deinit { + self.removeObservers() } - public func redo() { - self.managedObjectContext.redo() + // MARK: - public methods + + public func save() -> (success: Bool, error: NSError?) { + var error: NSError? = nil + let success = self.save(&error) + + return (success, error) } - public func reset() { - self.managedObjectContext.reset() + public func perform(closure: () -> Void) { + self.performBlock(closure) } - public func rollback() { - self.managedObjectContext.rollback() + public func performAndWait(closure: () -> Void) { + self.performBlockAndWait(closure) } -} - -extension Context { + // MARK: - private methods - public func perform(closure: () -> Void) { - self.managedObjectContext.performBlock(closure) + private func addObservers() { + // this context will save + self.addObserverForName(NSManagedObjectContextWillSaveNotification, object: self) { notification in + if let notificationContext = notification.object as? NSManagedObjectContext where !notificationContext.insertedObjects.isEmpty { + notificationContext.obtainPermanentIDsForObjects(Array(notificationContext.insertedObjects), error: nil) + } + } } - public func performAndWait(closure: () -> Void) { - self.managedObjectContext.performBlockAndWait(closure) + private func removeObservers() { + let notificationCenter = NSNotificationCenter.defaultCenter() + + for observer in self.observers { + notificationCenter.removeObserver(observer) + } + } + + private func addObserverForName(name: String, object: AnyObject, closure: (NSNotification!) -> Void) { + let observer = NSNotificationCenter.defaultCenter().addObserverForName(name, object: object, queue: nil, usingBlock: closure) + self.observers.append(observer) } } -extension Context { +// MARK: - root saving data context - public var hasChanges: Bool { return self.managedObjectContext.hasChanges } +public class RootSavingContext: BaseContext { + + private let contextOptions: ContextOptions - public var undoManager: NSUndoManager? { - get { - return self.managedObjectContext.undoManager + public init(contextOptions: ContextOptions) { + self.contextOptions = contextOptions + super.init(concurrencyType: .PrivateQueueConcurrencyType) + + self.name = "Root Saving Context" + self.undoManager = nil + + // only the root data context has a direct assigned persistent store coordinator + self.assignPersistentStoreCoordinator() + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func assignPersistentStoreCoordinator() { + // managed object model + if let managedObjectModelURL = self.contextOptions.managedObjectModelURL, let managedObjectModel = NSManagedObjectModel(contentsOfURL: managedObjectModelURL) { + // persistent store coordinator + let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) + + // persistent store + switch self.contextOptions.storeType { + case .SQLite: + if let persistentStoreURL = self.contextOptions.persistentStoreURL, + let containerURL = persistentStoreURL.URLByDeletingLastPathComponent { + // if the directory does not exist, it will be created + var fileManagerError: NSError? = nil + if NSFileManager.defaultManager().createDirectoryAtURL(containerURL, withIntermediateDirectories: true, attributes: nil, error: &fileManagerError) { + var error: NSError? = nil + persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: self.contextOptions.configuration, URL: persistentStoreURL, options: self.contextOptions.options as [NSObject : AnyObject], error: &error) + + if let error = error { + var handled = false + + if error.domain == NSCocoaErrorDomain { + let migrationErrorCodes = [NSPersistentStoreIncompatibleVersionHashError, NSMigrationMissingSourceModelError, NSMigrationError] + + if contains(migrationErrorCodes, error.code) { + handled = self.handleMigrationError(error) + } + } + + if !handled { + return + } + } + } + else { + return + } + + } + else { + // throws RootSavingDataContext.genericError + return + } + + case .InMemory: + var error: NSError? = nil + persistentStoreCoordinator.addPersistentStoreWithType(NSInMemoryStoreType, configuration: self.contextOptions.configuration, URL: nil, options: self.contextOptions.options as [NSObject : AnyObject], error: &error) + + if error != nil { + return + } + } + + // + self.persistentStoreCoordinator = persistentStoreCoordinator } - set { - self.managedObjectContext.undoManager = newValue + else { + // throws RootSavingDataContext.genericError + return } } - + + private func handleMigrationError(error: NSError) -> Bool { + return false + } + } -#if os(OSX) +// MARK: - ChildContext -extension Context { +public class ChildContext: BaseContext { - public func commitEditing() -> Bool { - return self.managedObjectContext.commitEditing() - } + // MARK: - private properties + + private var enableMergeFromRootSavingContext = true - public func discardEditing() { - self.managedObjectContext.discardEditing() + // MARK: - public properties + + public let rootSavingContext: RootSavingContext + + // MARK: - init and dealloc + + public init(concurrencyType: NSManagedObjectContextConcurrencyType, rootSavingContext: RootSavingContext) { + self.rootSavingContext = rootSavingContext + super.init(concurrencyType: concurrencyType) + + self.parentContext = self.rootSavingContext } -} + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } -#endif + // MARK: - public overrided methods - -extension Context { - - internal func executeFetchRequest(fetchRequest: NSFetchRequest, error: NSErrorPointer) -> [AnyObject]? { - var objects: [AnyObject]? + public override func save(error: NSErrorPointer) -> Bool { + if !self.hasChanges { return true } - if self.___background { - // already in "performBlock" - objects = self.managedObjectContext.executeFetchRequest(fetchRequest, error: error) - } - else { - self.managedObjectContext.performBlockAndWait { - objects = self.managedObjectContext.executeFetchRequest(fetchRequest, error: error) + var success = super.save(error) + + if success { + self.rootSavingContext.performBlockAndWait { + self.enableMergeFromRootSavingContext = false + success = self.rootSavingContext.save(error) + self.enableMergeFromRootSavingContext = true } } - return objects + return success } - internal func executeAsynchronousFetchRequestWithFetchRequest(fetchRequest: NSFetchRequest, completionClosure: ([AnyObject]?, NSError?) -> Void) -> FetchAsyncHandler { + // MARK: - private overrided methods + + private override func addObservers() { // - let moc = self.managedObjectContext + super.addObservers() + // the root data context did save + self.addObserverForName(NSManagedObjectContextDidSaveNotification, object: self.rootSavingContext) { [unowned self] notification in + if !self.enableMergeFromRootSavingContext { + return + } + + if let changeNotificationData = notification.userInfo { + self.performBlock { + // + let updatedObjects = changeNotificationData[NSUpdatedObjectsKey] as? Set + if let updatedObjects = updatedObjects where !updatedObjects.isEmpty { + for updatedObject in updatedObjects { + self.objectWithID(updatedObject.objectID).willAccessValueForKey(nil) // ensures that a fault has been fired + } + } + + // + self.mergeChangesFromContextDidSaveNotification(notification) + } + } + } + } + +} + +// MARK: - BaseContext extensions + +extension BaseContext { + + internal func executeAsynchronousFetchRequestWithFetchRequest(fetchRequest: NSFetchRequest, completionHandler: ([AnyObject]?, NSError?) -> Void) -> FetchAsyncHandler { // - var completionClosureCalled = false + var completionHandlerCalled = false // let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in - if !completionClosureCalled { - completionClosureCalled = true - completionClosure(asynchronousFetchResult.finalResult, asynchronousFetchResult.operationError) + if !completionHandlerCalled { + completionHandlerCalled = true + completionHandler(asynchronousFetchResult.finalResult, asynchronousFetchResult.operationError) } } @@ -194,25 +299,23 @@ extension Context { let handler = FetchAsyncHandler(asynchronousFetchRequest: asynchronousFetchRequest) // - moc.performBlock { - var error: NSError? = nil - if handler.cancelled { - completionClosureCalled = true - completionClosure(nil, NSError(domain: "com.alecrim.AlecrimCoreData", code: NSUserCancelledError, userInfo: nil)) + var error: NSError? = nil + if handler.cancelled { + completionHandlerCalled = true + completionHandler(nil, NSError(domain: "com.alecrim.AlecrimCoreData", code: NSUserCancelledError, userInfo: nil)) + } + else { + handler.foolProgress.becomeCurrentWithPendingUnitCount(1) + handler.asynchronousFetchResult = self.executeRequest(asynchronousFetchRequest, error: &error) as? NSAsynchronousFetchResult + handler.foolProgress.resignCurrent() + + if error != nil { + completionHandlerCalled = true + completionHandler(nil, error) } - else { - handler.foolProgress.becomeCurrentWithPendingUnitCount(1) - handler.asynchronousFetchResult = moc.executeRequest(asynchronousFetchRequest, error: &error) as? NSAsynchronousFetchResult - handler.foolProgress.resignCurrent() - - if error != nil { - completionClosureCalled = true - completionClosure(nil, error) - } - else if handler.asynchronousFetchResult?.operationError != nil { - completionClosureCalled = true - completionClosure(nil, handler.asynchronousFetchResult!.operationError) - } + else if handler.asynchronousFetchResult?.operationError != nil { + completionHandlerCalled = true + completionHandler(nil, handler.asynchronousFetchResult!.operationError) } } @@ -220,22 +323,31 @@ extension Context { return handler } - internal func executeBatchUpdateRequestWithEntityDescription(entityDescription: NSEntityDescription, propertiesToUpdate: [NSObject : AnyObject], predicate: NSPredicate, completionClosure: (Int, NSError?) -> Void) { + internal func executeBatchUpdateRequestWithEntityDescription(entityDescription: NSEntityDescription, propertiesToUpdate: [NSObject : AnyObject], predicate: NSPredicate, completionHandler: (Int, NSError?) -> Void) { let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription) batchUpdateRequest.propertiesToUpdate = propertiesToUpdate batchUpdateRequest.predicate = predicate batchUpdateRequest.resultType = .UpdatedObjectsCountResultType - let moc = self.stack.backgroundManagedObjectContext + // + // HAX: + // The `executeRequest:` method for a batch update only works in the root saving context. + // If called in a context that has a parent context, both the `batchUpdateResult` and the `error` will be quietly set to `nil` by Core Data. + // + + var moc: NSManagedObjectContext = self + while moc.parentContext != nil { + moc = moc.parentContext! + } + moc.performBlock { var error: NSError? = nil - let batchUpdateResult = moc.executeRequest(batchUpdateRequest, error: &error) as! NSBatchUpdateResult - if error != nil { - completionClosure(0, error) + if let batchUpdateResult = moc.executeRequest(batchUpdateRequest, error: &error) as? NSBatchUpdateResult, let count = batchUpdateResult.result as? Int { + completionHandler(count, nil) } else { - completionClosure(batchUpdateResult.result as! Int, nil) + completionHandler(0, error ?? alecrimCoreDataError()) } } } @@ -244,12 +356,15 @@ extension Context { // MARK: - public global functions - background contexts -public func createBackgroundContext(parentContext: T, usingNewBackgroundManagedObjectContext: Bool) -> T! { - parentContext.contextOptions.___stack = parentContext.stack - parentContext.contextOptions.___stackUsesNewBackgroundManagedObjectContext = usingNewBackgroundManagedObjectContext - let backgroundContext = T(contextOptions: parentContext.contextOptions) - - return backgroundContext +// This functions will be removed in the next major version. + +public func createBackgroundContext(parentContext: T, usingNewBackgroundManagedObjectContext: Bool) -> T { + if usingNewBackgroundManagedObjectContext { + return T(parentContext: parentContext) + } + else { + return parentContext.defaultCreatedBackgroundContext() + } } public func performInBackground(parentContext: T, closure: (T) -> Void) { @@ -259,16 +374,21 @@ public func performInBackground(parentContext: T, closure: (T) -> Vo public func performInBackground(parentContext: T, usingNewBackgroundManagedObjectContext: Bool, closure: (T) -> Void) { let backgroundContext = createBackgroundContext(parentContext, usingNewBackgroundManagedObjectContext) - backgroundContext.perform { + backgroundContext.performBlock { closure(backgroundContext) } } + // MARK: - internal global functions - error handling +internal func alecrimCoreDataError(code: Int = NSCoreDataError, userInfo: [NSObject : AnyObject]? = nil) -> NSError { + return NSError(domain: "com.alecrim.AlecrimCoreData", code: code, userInfo: userInfo) +} + internal func alecrimCoreDataHandleError(error: NSError?, filename: String = __FILE__, line: Int = __LINE__, funcname: String = __FUNCTION__) { if let error = error where error.code != NSUserCancelledError { - var dateFormatter = NSDateFormatter() + let dateFormatter = NSDateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss:SSS" let process = NSProcessInfo.processInfo() @@ -278,3 +398,4 @@ internal func alecrimCoreDataHandleError(error: NSError?, filename: String = __F println(string) } } + diff --git a/Source/AlecrimCoreData/Classes/Context/ContextOptions.swift b/Source/AlecrimCoreData/Classes/Context/ContextOptions.swift index 7804ba2..5241e9b 100644 --- a/Source/AlecrimCoreData/Classes/Context/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/Context/ContextOptions.swift @@ -1,5 +1,5 @@ // -// ContextOptions.swift +// DataContextOptions.swift // AlecrimCoreData // // Created by Vanderlei Martinelli on 2015-02-26. @@ -9,227 +9,161 @@ import Foundation import CoreData -// -// To set context options: -// -// 1) Subclass the (possibly generated by ACDGen) Context class -// 2) Create a new instance of ContextOptions inside subclass init -// 3) Set the properties you want to change and pass the created instance to super -// -// Example: -// -// public final class DataContext: BaseDataContext { -// -// public init?() { -// let contextOptions = ContextOptions(stackType: StackType.SQLite) -// -// // set options here... -// -// super.init(contextOptions: contextOptions) -// } -// -// } -// -// If you are writing an app extension you may be specially interested in -// "applicationGroupIdentifier", "modelBundle", "managedObjectModelName" and -// "persistentStoreRelativePath" properties. -// -public final class ContextOptions { +public enum StoreType { + case SQLite + case InMemory +} - // MARK: - public static properties +public struct ContextOptions { + + // older versions compatibility (will be removed in 4.0) public static var stringComparisonPredicateOptions = (NSComparisonPredicateOptions.CaseInsensitivePredicateOption | NSComparisonPredicateOptions.DiacriticInsensitivePredicateOption) - - // MARK: - public properties - fetch request - public var fetchBatchSize = 20 + public static var fetchBatchSize = 20 + + // MARK: - - // MARK: - public properties - entity class names x entity names - public var entityClassNamePrefix: String? = nil // you will have to change this if your class names begin with prefixes (for example: DMCustomer) - public var entityClassNameSuffix: String? = nil // you will have to change this if your class names have suffixes (for example: CustomerEntity) + public let managedObjectModelURL: NSURL? + public let persistentStoreURL: NSURL? - // MARK: - public properties - stack options - public let stackType: StackType + // MARK: - + + public var storeType: StoreType = .SQLite public var configuration: String? = nil + public let options: NSMutableDictionary = [NSMigratePersistentStoresAutomaticallyOption : true, NSInferMappingModelAutomaticallyOption : true] + + // MARK: - THE constructor + + public init(managedObjectModelURL: NSURL, persistentStoreURL: NSURL) { + self.managedObjectModelURL = managedObjectModelURL + self.persistentStoreURL = persistentStoreURL + } + + // MARK: - "convenience" initializers + + public init(managedObjectModelURL: NSURL) { + let mainBundle = NSBundle.mainBundle() + + self.managedObjectModelURL = managedObjectModelURL + self.persistentStoreURL = mainBundle.defaultPersistentStoreURL() + } + + public init(persistentStoreURL: NSURL) { + let mainBundle = NSBundle.mainBundle() + + self.managedObjectModelURL = mainBundle.defaultManagedObjectModelURL() + self.persistentStoreURL = persistentStoreURL + } + + public init() { + let mainBundle = NSBundle.mainBundle() + + self.managedObjectModelURL = mainBundle.defaultManagedObjectModelURL() + self.persistentStoreURL = mainBundle.defaultPersistentStoreURL() + } + + // MARK: - + + public init(managedObjectModelBundle: NSBundle, managedObjectModelName: String, bundleIdentifier: String) { + self.managedObjectModelURL = managedObjectModelBundle.managedObjectModelURLForManagedObjectModelName(managedObjectModelName) + self.persistentStoreURL = managedObjectModelBundle.persistentStoreURLForManagedObjectModelName(managedObjectModelName, bundleIdentifier: bundleIdentifier) + } + + /// Initializes ContextOptions with properties filled for use by main app and its extensions. + /// + /// :param: managedObjectModelBundle The managed object model bundle. You can use `NSBundle(forClass: MyModule.MyDataContext.self)`, for example. + /// :param: managedObjectModelName The managed object model name without the extension. Example: `"MyGreatApp"`. + /// :param: bundleIdentifier The bundle identifier for use when creating the directory for the persisent store. Example: `"com.mycompany.MyGreatApp"`. + /// :param: applicationGroupIdentifier The application group identifier (see Xcode target settings). Example: `"group.com.mycompany.MyGreatApp"` for iOS or `"12ABCD3EF4.com.mycompany.MyGreatApp"` for OS X where `12ABCD3EF4` is your team identifier. + /// + /// :returns: An initialized ContextOptions with properties filled for use by main app and its extensions. + public init(managedObjectModelBundle: NSBundle, managedObjectModelName: String, bundleIdentifier: String, applicationGroupIdentifier: String) { + self.managedObjectModelURL = managedObjectModelBundle.managedObjectModelURLForManagedObjectModelName(managedObjectModelName) + self.persistentStoreURL = managedObjectModelBundle.persistentStoreURLForManagedObjectModelName(managedObjectModelName, bundleIdentifier: bundleIdentifier, applicationGroupIdentifier: applicationGroupIdentifier) + } + +} - // MARK: - public properties - store options - public var storeOptions: [NSObject : AnyObject]! - public var migratePersistentStoresAutomatically = true - public var inferMappingModelAutomaticallyOption = true - - // MARK: - public properties - bundles - public let mainBundle: NSBundle = NSBundle.mainBundle() - public var modelBundle: NSBundle = NSBundle.mainBundle() // you will have to change this if your xcdatamodeld file is not in the main bundle (in a framework bundle, for example) - - // MARK: - public properties - managed object model - public var managedObjectModelName: String! // defaults to main bundle name, you will have to set this if you are writing an app extension - public private(set) var managedObjectModelURL: NSURL! = nil - public private(set) var managedObjectModel: NSManagedObjectModel! = nil - - // MARK: - public properties - app extensions - public var applicationGroupIdentifier: String? // you will have to set this if you are writing an app extension (com.apple.security.application-groups entitlement needed) - // MARK: - public properties - persistent location - public var persistentStoreRelativePath: String! = nil // defaults to main bundle identifier + "/CoreData", you will have to set this if you are writing an app extension - public var persistentStoreFileName: String! = nil // defaults to managed object model name + ".sqlite" - public private(set) var persistentStoreURL: NSURL! = nil +// Ubiquity (iCloud) support has changed in this version. - // MARK: - public properties - iCloud - public var ubiquityEnabled = false // turns the iCloud "light" on/off - public var ubiquitousContainerIdentifier: String! // defaults to "iCloud." + main bundle identifier - public var ubiquitousContentName = "UbiquityStore" - public var ubiquitousContentRelativePath: String! = "CoreData/TransactionLogs" - public private(set) var ubiquitousContentURL: NSURL! = nil +extension ContextOptions { + + public var ubiquityEnabled: Bool { + return self.storeType == .SQLite && self.options[NSPersistentStoreUbiquitousContainerIdentifierKey] != nil + } + + public func configureUbiquityOptionsWithUbiquitousContainerIdentifier(ubiquitousContainerIdentifier: String, ubiquitousContentURL: NSURL, ubiquitousContentName: String) { + self.options[NSPersistentStoreUbiquitousContainerIdentifierKey] = ubiquitousContainerIdentifier + self.options[NSPersistentStoreUbiquitousContentURLKey] = ubiquitousContentURL + self.options[NSPersistentStoreUbiquitousContentNameKey] = ubiquitousContentName + + self.options[NSMigratePersistentStoresAutomaticallyOption] = true + self.options[NSInferMappingModelAutomaticallyOption] = true + } + +} - // MARK: - private / internal properties - internal private(set) var filled = false - private var cachedEntityNames = Dictionary() - - // MARK: - internal properties - background context machinery (you did not see it) - internal var ___stack: Stack? = nil - internal var ___stackUsesNewBackgroundManagedObjectContext = false - - // MARK: - init (finally) - public init(stackType: StackType = StackType.SQLite, managedObjectModelName: String? = nil, storeOptions: [NSObject : AnyObject]? = nil) { - self.stackType = stackType - self.managedObjectModelName = managedObjectModelName - self.storeOptions = storeOptions +extension NSBundle { + + private var bundleName: String? { + return self.infoDictionary?[String(kCFBundleNameKey)] as? String } } -extension ContextOptions { +extension NSBundle { - internal func fillEmptyOptions(customConfiguration: Bool = false) { - // - if self.filled { - return - } - - // verify if we have exiting managed object contexts set (customConfiguration == true in this case) - if customConfiguration { - self.filled = true - return - } - - // if managed object model name is nil, try to get default name from main bundle - if self.managedObjectModelName == nil { - if let infoDictionary = self.mainBundle.infoDictionary { - self.managedObjectModelName = infoDictionary[kCFBundleNameKey] as? String - } - } - - // managed object model - if self.managedObjectModelName != nil { - self.managedObjectModelURL = self.modelBundle.URLForResource(self.managedObjectModelName!, withExtension: "momd") - - if self.managedObjectModelURL != nil { - self.managedObjectModel = NSManagedObjectModel(contentsOfURL: self.managedObjectModelURL) - } - } - - // local store - if self.persistentStoreRelativePath == nil { - self.persistentStoreRelativePath = NSString(format: "%@/%@", self.mainBundle.bundleIdentifier!, "CoreData") as String - } - - // - let fileManager = NSFileManager.defaultManager() - var persistentStoreContainerURL: NSURL? - - // - if let groupIdentifier = self.applicationGroupIdentifier { - // stored in "~/Library/Group Containers/." (this method also creates the directory if it does not yet exist) - persistentStoreContainerURL = fileManager.containerURLForSecurityApplicationGroupIdentifier(groupIdentifier) - persistentStoreContainerURL = persistentStoreContainerURL?.URLByAppendingPathComponent("Library", isDirectory: true) - persistentStoreContainerURL = persistentStoreContainerURL?.URLByAppendingPathComponent("Application Support", isDirectory: true) - } else{ - let urls = fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask) - persistentStoreContainerURL = urls.last as? NSURL - } - - // - if let persistentStoreRelativePath = self.persistentStoreRelativePath { - persistentStoreContainerURL = persistentStoreContainerURL?.URLByAppendingPathComponent(self.persistentStoreRelativePath, isDirectory: true) - } - - // - if let containerURL = persistentStoreContainerURL { - if self.persistentStoreFileName == nil { - self.persistentStoreFileName = self.managedObjectModelName.stringByAppendingPathExtension("sqlite")! - } - - self.persistentStoreURL = containerURL.URLByAppendingPathComponent(self.persistentStoreFileName, isDirectory: false) - - if !fileManager.fileExistsAtPath(containerURL.path!) { - fileManager.createDirectoryAtURL(containerURL, withIntermediateDirectories: true, attributes: nil, error: nil) - } - } - - // iCloud - if self.ubiquityEnabled { - if self.ubiquitousContainerIdentifier == nil { - self.ubiquitousContainerIdentifier = NSString(format: "%@.%@", "iCloud", self.mainBundle.bundleIdentifier!) as String - } - - if self.ubiquitousContainerIdentifier != nil { - if var ubiquitousContentURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(self.ubiquitousContainerIdentifier) { - if let ubiquitousContentRelativePath = self.ubiquitousContentRelativePath { - ubiquitousContentURL = ubiquitousContentURL.URLByAppendingPathComponent(ubiquitousContentRelativePath, isDirectory: true) - } - - self.ubiquitousContentURL = ubiquitousContentURL - } - } - - if self.ubiquitousContentURL == nil { - self.ubiquityEnabled = false - } + private func defaultManagedObjectModelURL() -> NSURL? { + if let managedObjectModelName = self.bundleName { + return self.managedObjectModelURLForManagedObjectModelName(managedObjectModelName) } - // store options - if self.storeOptions == nil { - self.storeOptions = [NSObject : AnyObject]() + return nil + } + + private func defaultPersistentStoreURL() -> NSURL? { + if let managedObjectModelName = self.bundleName, let bundleIdentifier = self.bundleIdentifier { + return self.persistentStoreURLForManagedObjectModelName(managedObjectModelName, bundleIdentifier: bundleIdentifier) } - // - self.filled = true + return nil } } -// MARK: entity class names x entity names - -extension ContextOptions { +extension NSBundle { - internal func entityNameFromClass(aClass: AnyClass) -> String { - let className = NSStringFromClass(aClass) - - if let name = self.cachedEntityNames[className] { - return name - } - else { - var name: NSString = className - let range = name.rangeOfString(".", options: (.BackwardsSearch)) - if range.location != NSNotFound { - name = name.substringFromIndex(range.location + 1) - } - - if let prefix = self.entityClassNamePrefix { - if !name.isEqualToString(prefix) && name.hasPrefix(prefix) { - name = name.substringFromIndex((prefix as NSString).length) - } - } - - if let suffix = self.entityClassNameSuffix { - if !name.isEqualToString(suffix) && name.hasSuffix(suffix) { - name = name.substringToIndex(name.length - (suffix as NSString).length) - } - } + private func managedObjectModelURLForManagedObjectModelName(managedObjectModelName: String) -> NSURL? { + return self.URLForResource(managedObjectModelName, withExtension: "momd") + } + + private func persistentStoreURLForManagedObjectModelName(managedObjectModelName: String, bundleIdentifier: String) -> NSURL? { + if let applicationSupportURL = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).last as? NSURL { + let url = applicationSupportURL + .URLByAppendingPathComponent(bundleIdentifier, isDirectory: true) + .URLByAppendingPathComponent("CoreData", isDirectory: true) + .URLByAppendingPathComponent(managedObjectModelName.stringByAppendingPathExtension("sqlite")!, isDirectory: false) - let nameAsString = name as! String - self.cachedEntityNames[className] = nameAsString + return url + } + + return nil + } + + private func persistentStoreURLForManagedObjectModelName(managedObjectModelName: String, bundleIdentifier: String, applicationGroupIdentifier: String) -> NSURL? { + if let containerURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier(applicationGroupIdentifier) { + let url = containerURL + .URLByAppendingPathComponent("Library", isDirectory: true) + .URLByAppendingPathComponent("Application Support", isDirectory: true) + .URLByAppendingPathComponent(bundleIdentifier, isDirectory: true) + .URLByAppendingPathComponent("CoreData", isDirectory: true) + .URLByAppendingPathComponent(managedObjectModelName.stringByAppendingPathExtension("sqlite")!, isDirectory: false) - return nameAsString + return url } + + return nil } } + diff --git a/Source/AlecrimCoreData/Classes/Context/Stack.swift b/Source/AlecrimCoreData/Classes/Context/Stack.swift deleted file mode 100644 index 2d45420..0000000 --- a/Source/AlecrimCoreData/Classes/Context/Stack.swift +++ /dev/null @@ -1,295 +0,0 @@ -// -// Stack.swift -// AlecrimCoreData -// -// Created by Vanderlei Martinelli on 2014-06-24. -// Copyright (c) 2014 Alecrim. All rights reserved. -// - -import Foundation -import CoreData - -public enum StackType { - case SQLite - case InMemory -} - -internal final class Stack { - - internal let contextOptions: ContextOptions - private let coordinator: NSPersistentStoreCoordinator! - private let store: NSPersistentStore! - - private var rootManagedObjectContext: NSManagedObjectContext! - internal private(set) var mainManagedObjectContext: NSManagedObjectContext! - internal lazy var backgroundManagedObjectContext: NSManagedObjectContext = { self.createBackgroundManagedObjectContext() }() - - // MARK: - constructors - - internal init?(contextOptions: ContextOptions) { - self.contextOptions = contextOptions - - if contextOptions.managedObjectModel == nil { - self.coordinator = nil - self.store = nil - - return nil - } - - // coordinator - self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.contextOptions.managedObjectModel) - - // store - switch contextOptions.stackType { - case .SQLite: - self.store = Stack.persistentStoreForSQLiteStoreTypeWithCoordinator(self.coordinator, contextOptions: contextOptions) - - case .InMemory: - self.store = Stack.persistentStoreForInMemoryStoreTypeWithCoordinator(self.coordinator, contextOptions: contextOptions) - } - - if self.store == nil { - return nil - } - - // root (saving) managed object context - self.rootManagedObjectContext = ManagedObjectContext(stack: self, type: .Root) - self.rootManagedObjectContext.persistentStoreCoordinator = self.coordinator - - // main thread managed object context - self.mainManagedObjectContext = ManagedObjectContext(stack: self, type: .Main) - self.mainManagedObjectContext.parentContext = self.rootManagedObjectContext - } - - internal init?(rootManagedObjectContext: NSManagedObjectContext, mainManagedObjectContext: NSManagedObjectContext, contextOptions: ContextOptions) { - self.contextOptions = contextOptions - self.coordinator = rootManagedObjectContext.persistentStoreCoordinator - self.store = self.coordinator.persistentStores.first as! NSPersistentStore - self.rootManagedObjectContext = rootManagedObjectContext - self.mainManagedObjectContext = mainManagedObjectContext - } - -} - -// MARK: - internal methods - -extension Stack { - - internal func createBackgroundManagedObjectContext() -> NSManagedObjectContext { - let backgroundContext = ManagedObjectContext(stack: self, type: .Background) - backgroundContext.parentContext = self.rootManagedObjectContext - - return backgroundContext - } - - internal func saveManagedObjectContext(context: NSManagedObjectContext) -> (Bool, NSError?) { - var success = false - var error: NSError? = nil - - if context.hasChanges { - var currentContext: NSManagedObjectContext? = context - - while let c = currentContext { - c.performBlockAndWait { - success = c.save(&error) - } - - if (!success) { - break - } - - currentContext = currentContext?.parentContext - } - } - else { - success = true // context does not have changes - } - - return (success, error) - } - -} - -// MARK: - private class methods - -extension Stack { - - private class func managedObjectModelWithName(var name: String?, bundle: NSBundle) -> NSManagedObjectModel? { - if let managedObjectModelName = name { - if let managedObjectModelURL = bundle.URLForResource(managedObjectModelName, withExtension: "momd") { - if let managedObjectModel = NSManagedObjectModel(contentsOfURL: managedObjectModelURL) { - return managedObjectModel - } - } - } - - return nil - } - - private class func persistentStoreForSQLiteStoreTypeWithCoordinator(coordinator: NSPersistentStoreCoordinator, contextOptions: ContextOptions) -> NSPersistentStore? { - if contextOptions.ubiquityEnabled { - contextOptions.storeOptions[NSPersistentStoreUbiquitousContentNameKey] = contextOptions.ubiquitousContentName - contextOptions.storeOptions[NSPersistentStoreUbiquitousContentURLKey] = contextOptions.ubiquitousContentURL - contextOptions.storeOptions[NSPersistentStoreUbiquitousContainerIdentifierKey] = contextOptions.ubiquitousContainerIdentifier - - contextOptions.migratePersistentStoresAutomatically = true // always true, ignores previous value - contextOptions.inferMappingModelAutomaticallyOption = true // always true, ignores previous value - } - - contextOptions.storeOptions[NSMigratePersistentStoresAutomaticallyOption] = contextOptions.migratePersistentStoresAutomatically - contextOptions.storeOptions[NSInferMappingModelAutomaticallyOption] = contextOptions.inferMappingModelAutomaticallyOption - - var error: NSError? = nil - if let store = coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: contextOptions.configuration, URL: contextOptions.persistentStoreURL, options: contextOptions.storeOptions, error: &error) { - return store - } - else { - alecrimCoreDataHandleError(error) - return nil - } - } - - private class func persistentStoreForInMemoryStoreTypeWithCoordinator(coordinator: NSPersistentStoreCoordinator, contextOptions: ContextOptions) -> NSPersistentStore? { - var error: NSError? = nil - - if let store = coordinator.addPersistentStoreWithType(NSInMemoryStoreType, configuration: contextOptions.configuration, URL: nil, options: contextOptions.storeOptions, error: &error) { - return store - } - else { - alecrimCoreDataHandleError(error) - return nil - } - } - -} - -// MARK: - private classes - -private enum ManagedObjectContextType { - case Root - case Main - case Background -} - -private final class ManagedObjectContext: NSManagedObjectContext { - - private unowned let stack: Stack - private let type: ManagedObjectContextType - - private init(stack: Stack, type: ManagedObjectContextType) { - self.stack = stack - self.type = type - - let concurrencyType: NSManagedObjectContextConcurrencyType - switch self.type { - case .Main: - concurrencyType = .MainQueueConcurrencyType - default: - concurrencyType = .PrivateQueueConcurrencyType - } - - super.init(concurrencyType: concurrencyType) - - self.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - if type != .Main { - self.undoManager = nil - } - - self.addObservers() - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.removeObservers() - } - - private func addObservers() { - let notificationCenter = NSNotificationCenter.defaultCenter() - - if self.stack.contextOptions.stackType == .SQLite && self.stack.contextOptions.ubiquityEnabled { - notificationCenter.addObserver(self, selector: Selector("persistentStoreCoordinatorStoresWillChange:"), name: NSPersistentStoreCoordinatorStoresWillChangeNotification, object: self.stack.coordinator) - notificationCenter.addObserver(self, selector: Selector("persistentStoreCoordinatorStoresDidChange:"), name: NSPersistentStoreCoordinatorStoresDidChangeNotification, object: self.stack.coordinator) - notificationCenter.addObserver(self, selector: Selector("persistentStoreDidImportUbiquitousContentChanges:"), name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: self.stack.coordinator) - } - - notificationCenter.addObserver(self, selector: Selector("managedObjectContextWillSave:"), name: NSManagedObjectContextWillSaveNotification, object: self) - - if self.type != .Root { - notificationCenter.addObserver(self, selector: Selector("rootManagedObjectContextDidSave:"), name: NSManagedObjectContextDidSaveNotification, object: self.stack.rootManagedObjectContext) - } - } - - private func removeObservers() { - let notificationCenter = NSNotificationCenter.defaultCenter() - - notificationCenter.removeObserver(self, name: NSManagedObjectContextWillSaveNotification, object: self) - - if self.type != .Root { - notificationCenter.removeObserver(self, name: NSManagedObjectContextDidSaveNotification, object: self.stack.rootManagedObjectContext) - } - - if self.stack.contextOptions.stackType == .SQLite && self.stack.contextOptions.ubiquityEnabled { - notificationCenter.removeObserver(self, name: NSPersistentStoreCoordinatorStoresWillChangeNotification, object: self.stack.coordinator) - notificationCenter.removeObserver(self, name: NSPersistentStoreCoordinatorStoresDidChangeNotification, object: self.stack.coordinator) - notificationCenter.removeObserver(self, name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: self.stack.coordinator) - } - - } - - @objc private func managedObjectContextWillSave(notification: NSNotification) { - if let context = notification.object as? NSManagedObjectContext, let insertedObjects = context.insertedObjects as? Set where !insertedObjects.isEmpty { - var error: NSError? = nil - if !context.obtainPermanentIDsForObjects(Array(insertedObjects), error: &error) { - alecrimCoreDataHandleError(error) - } - } - } - - @objc private func rootManagedObjectContextDidSave(notification: NSNotification) { - if self.type != .Root { - ManagedObjectContext.mergeChangesFromContextDidSaveNotification(notification, intoContext: self) - } - } - - @objc private func persistentStoreCoordinatorStoresWillChange(notification: NSNotification) { - self.performBlock { - var error: NSError? = nil - self.save(&error) - if error != nil { - alecrimCoreDataHandleError(error) - } - - self.reset() - } - } - - @objc private func persistentStoreCoordinatorStoresDidChange(notification: NSNotification) { - self.performBlock { - self.reset() - } - } - - @objc private func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification) { - ManagedObjectContext.mergeChangesFromContextDidSaveNotification(notification, intoContext: self) - } - - private class func mergeChangesFromContextDidSaveNotification(notification: NSNotification, intoContext context: NSManagedObjectContext) { - context.performBlock { - if let userInfo = notification.userInfo { - let dict = userInfo as NSDictionary - if let updatedObjects = dict[NSUpdatedObjectsKey] as? Set where !updatedObjects.isEmpty { - for objectObject in updatedObjects { - context.objectWithID(objectObject.objectID).willAccessValueForKey(nil) // ensures that a fault has been fired - } - } - } - - context.mergeChangesFromContextDidSaveNotification(notification) - } - } -} - diff --git a/Source/AlecrimCoreData/Classes/Extensions/NSManagedObjectExtensions.swift b/Source/AlecrimCoreData/Classes/Extensions/NSManagedObjectExtensions.swift index e82908e..80fedd0 100644 --- a/Source/AlecrimCoreData/Classes/Extensions/NSManagedObjectExtensions.swift +++ b/Source/AlecrimCoreData/Classes/Extensions/NSManagedObjectExtensions.swift @@ -12,7 +12,7 @@ import CoreData extension NSManagedObject { public func inContext(context: Context) -> Self? { - return self.inManagedObjectContext(context.managedObjectContext) + return self.inManagedObjectContext(context) } public func inManagedObjectContext(otherManagedObjectContext: NSManagedObjectContext) -> Self? { diff --git a/Source/AlecrimCoreData/Classes/Other/ALCFetchedResultsController.swift b/Source/AlecrimCoreData/Classes/Other/ALCFetchedResultsController.swift index 8dc8023..a337e9d 100644 --- a/Source/AlecrimCoreData/Classes/Other/ALCFetchedResultsController.swift +++ b/Source/AlecrimCoreData/Classes/Other/ALCFetchedResultsController.swift @@ -151,7 +151,7 @@ public class ALCFetchedResultsController: NSObject { var success = false self.managedObjectContext.performBlockAndWait { - self.calculateSections(error: error).success + success = self.calculateSections(error: error).success } return success diff --git a/Source/AlecrimCoreData/Classes/Other/FetchedResultsController.swift b/Source/AlecrimCoreData/Classes/Other/FetchedResultsController.swift index 8620985..61e1bdf 100644 --- a/Source/AlecrimCoreData/Classes/Other/FetchedResultsController.swift +++ b/Source/AlecrimCoreData/Classes/Other/FetchedResultsController.swift @@ -24,7 +24,7 @@ public final class FetchedResultsController { private var hasUnderlyingFetchedResultsController = false private var underlyingFecthedResultsControllerDelegate: FecthedResultsControllerDelegate! = nil - private lazy var underlyingFetchedResultsController: NSFetchedResultsController = { + private lazy var underlyingFetchedResultsController: NSFetchedResultsController = { [unowned self] in let frc = NSFetchedResultsController(fetchRequest: self.initialFetchRequest, managedObjectContext: self.initialManagedObjectContext, sectionNameKeyPath: self.initialSectionNameKeyPath, cacheName: self.initialCacheName) // we have to retain the delegate first diff --git a/Source/AlecrimCoreData/Classes/Queries/Query.swift b/Source/AlecrimCoreData/Classes/Queries/Query.swift index 21da6a8..88be085 100644 --- a/Source/AlecrimCoreData/Classes/Queries/Query.swift +++ b/Source/AlecrimCoreData/Classes/Queries/Query.swift @@ -28,7 +28,7 @@ public class Query { public func toFetchRequest() -> NSFetchRequest { let fetchRequest = NSFetchRequest(entityName: self.entityName) - fetchRequest.fetchBatchSize = (self.limit > 0 && self.context.contextOptions.fetchBatchSize > self.limit ? 0 : self.context.contextOptions.fetchBatchSize) + fetchRequest.fetchBatchSize = (self.limit > 0 && ContextOptions.fetchBatchSize > self.limit ? 0 : ContextOptions.fetchBatchSize) fetchRequest.fetchOffset = self.offset fetchRequest.fetchLimit = self.limit @@ -198,11 +198,6 @@ extension Query { if clone.predicate == nil { clone.predicate = predicate } - else if let compoundPredicate = clone.predicate as? NSCompoundPredicate { - var subpredicates = compoundPredicate.subpredicates as! [NSPredicate] - subpredicates.append(predicate) - clone.predicate = NSCompoundPredicate.andPredicateWithSubpredicates(subpredicates) - } else { let subpredicates = [clone.predicate!, predicate] clone.predicate = NSCompoundPredicate.andPredicateWithSubpredicates(subpredicates) @@ -218,15 +213,15 @@ extension Query { extension Query { public func count() -> Int { - let fetchRequest = self.toFetchRequest() - var c = 0 + var error: NSError? = nil + let c = self.context.countForFetchRequest(self.toFetchRequest(), error: &error) - self.context.managedObjectContext.performBlockAndWait { - var error: NSError? = nil - c += self.context.managedObjectContext.countForFetchRequest(fetchRequest, error: &error) + if error == nil && c != NSNotFound { + return c + } + else { + return 0 } - - return c } } diff --git a/Source/AlecrimCoreData/Classes/Queries/Table.swift b/Source/AlecrimCoreData/Classes/Queries/Table.swift index 51a6347..4eb62e3 100644 --- a/Source/AlecrimCoreData/Classes/Queries/Table.swift +++ b/Source/AlecrimCoreData/Classes/Queries/Table.swift @@ -9,13 +9,45 @@ import Foundation import CoreData +private var cachedEntityDescriptions = [String : NSEntityDescription]() + +private func entityDescriptionFromClass(aClass: AnyClass, context: NSManagedObjectContext) -> NSEntityDescription { + let managedObjectClassName = NSStringFromClass(aClass) + + // + let ed: NSEntityDescription + if let cachedEntityDescription = cachedEntityDescriptions[managedObjectClassName] { + ed = cachedEntityDescription + } + else { + let persistentStoreCoordinator = context.persistentStoreCoordinator! + let managedObjectModel = persistentStoreCoordinator.managedObjectModel + + ed = Swift.filter(managedObjectModel.entities, { $0.managedObjectClassName == managedObjectClassName }).first! as! NSEntityDescription + cachedEntityDescriptions[managedObjectClassName] = ed + } + + return ed +} + + +// MARK: - + public final class Table: Query { - private lazy var entityDescription: NSEntityDescription = { return NSEntityDescription.entityForName(self.entityName, inManagedObjectContext: self.context.managedObjectContext)! }() + private var _entityDescription: NSEntityDescription! + private var entityDescription: NSEntityDescription { + if self._entityDescription == nil { + self._entityDescription = entityDescriptionFromClass(T.self, self.context) + } + + return self._entityDescription + } public convenience init(context: Context) { - let entityName = context.contextOptions.entityNameFromClass(T.self) - self.init(context: context, entityName: entityName) + let entityDescription = entityDescriptionFromClass(T.self, context) + self.init(context: context, entityName: entityDescription.name!) + self._entityDescription = entityDescription } public required init(context: Context, entityName: String) { @@ -36,7 +68,7 @@ public final class Table: Query { extension Table { public func createEntity() -> T { - let entity = T(entity: self.entityDescription, insertIntoManagedObjectContext: self.context.managedObjectContext) + let entity = T(entity: self.entityDescription, insertIntoManagedObjectContext: self.context) return entity } @@ -55,8 +87,8 @@ extension Table { public func deleteEntity(entity: T) -> (success: Bool, error: NSError?) { var retrieveExistingObjectError: NSError? = nil - if let managedObjectInContext = self.context.managedObjectContext.existingObjectWithID(entity.objectID, error: &retrieveExistingObjectError) { - self.context.managedObjectContext.deleteObject(managedObjectInContext) + if let managedObjectInContext = self.context.existingObjectWithID(entity.objectID, error: &retrieveExistingObjectError) { + self.context.deleteObject(managedObjectInContext) return (entity.deleted || entity.managedObjectContext == nil, nil) } else { @@ -84,8 +116,8 @@ extension Table { for objectID in objectIDs { var retrieveExistingObjectError: NSError? = nil - if let object = self.context.managedObjectContext.existingObjectWithID(objectID, error: &retrieveExistingObjectError) { - self.context.managedObjectContext.deleteObject(object) + if let object = self.context.existingObjectWithID(objectID, error: &retrieveExistingObjectError) { + self.context.deleteObject(object) } if let retrieveExistingObjectError = retrieveExistingObjectError { @@ -122,16 +154,20 @@ extension Table: SequenceType { extension Table { public func toArray() -> [T] { - let fetchRequest = self.toFetchRequest() var results = [T]() - if let objects = self.executeFetchRequest(fetchRequest) as? [T] { - results += objects + if let objects = self.executeFetchRequest(self.toFetchRequest()) { + if let entities = objects as? [T] { + results += entities + } + else { + // HAX: `self.executeFetchRequest(fetchRequest) as? [T]` may not work in Swift 1.x in some circumstances + results += objects.map { $0 as! T } + } } return results } - } // MARK: - element @@ -153,7 +189,7 @@ extension Table { /// :param: predicateClosure A closure with a simple equality comparison between an attribute and a value. /// /// :returns: The found entity or a new entity from the same type (with the attribute filled with the specified value). - public func firstOrCreated(predicateClosure: (T.Type) -> NSComparisonPredicate) -> T { + public func firstOrCreated(@noescape predicateClosure: (T.Type) -> NSComparisonPredicate) -> T { let predicate = predicateClosure(T.self) if let entity = self.filterBy(predicate: predicate).first() { return entity @@ -177,19 +213,19 @@ extension Table { extension Table { - public func any(predicateClosure: (T.Type) -> NSPredicate) -> Bool { + public func any(@noescape predicateClosure: (T.Type) -> NSPredicate) -> Bool { return self.filterBy(predicate: predicateClosure(T.self)).any() } - public func count(predicateClosure: (T.Type) -> NSPredicate) -> Int { + public func count(@noescape predicateClosure: (T.Type) -> NSPredicate) -> Int { return self.filterBy(predicate: predicateClosure(T.self)).count() } - public func filter(predicateClosure: (T.Type) -> NSPredicate) -> Self { + public func filter(@noescape predicateClosure: (T.Type) -> NSPredicate) -> Self { return self.filterBy(predicate: predicateClosure(T.self)) } - public func first(predicateClosure: (T.Type) -> NSPredicate) -> T? { + public func first(@noescape predicateClosure: (T.Type) -> NSPredicate) -> T? { return self.filterBy(predicate: predicateClosure(T.self)).first() } @@ -199,34 +235,28 @@ extension Table { extension Table { - public func orderBy(orderingClosure: (T.Type) -> Attribute) -> Self { - let attributeName = orderingClosure(T.self).___name - return self.sortBy(attributeName, ascending: true) + public func orderBy(@noescape orderingClosure: (T.Type) -> Attribute) -> Self { + return self.sortBy(orderingClosure(T.self).___name, ascending: true) } - public func orderByAscending(orderingClosure: (T.Type) -> Attribute) -> Self { - let attributeName = orderingClosure(T.self).___name - return self.sortBy(attributeName, ascending: true) + public func orderByAscending(@noescape orderingClosure: (T.Type) -> Attribute) -> Self { + return self.sortBy(orderingClosure(T.self).___name, ascending: true) } - public func orderByDescending(orderingClosure: (T.Type) -> Attribute) -> Self { - let attributeName = orderingClosure(T.self).___name - return self.sortBy(attributeName, ascending: false) + public func orderByDescending(@noescape orderingClosure: (T.Type) -> Attribute) -> Self { + return self.sortBy(orderingClosure(T.self).___name, ascending: false) } - public func thenBy(orderingClosure: (T.Type) -> Attribute) -> Self { - let attributeName = orderingClosure(T.self).___name - return self.sortBy(attributeName, ascending: true) + public func thenBy(@noescape orderingClosure: (T.Type) -> Attribute) -> Self { + return self.sortBy(orderingClosure(T.self).___name, ascending: true) } - public func thenByAscending(orderingClosure: (T.Type) -> Attribute) -> Self { - let attributeName = orderingClosure(T.self).___name - return self.sortBy(attributeName, ascending: true) + public func thenByAscending(@noescape orderingClosure: (T.Type) -> Attribute) -> Self { + return self.sortBy(orderingClosure(T.self).___name, ascending: true) } - public func thenByDescending(orderingClosure: (T.Type) -> Attribute) -> Self { - let attributeName = orderingClosure(T.self).___name - return self.sortBy(attributeName, ascending: false) + public func thenByDescending(@noescape orderingClosure: (T.Type) -> Attribute) -> Self { + return self.sortBy(orderingClosure(T.self).___name, ascending: false) } } @@ -251,7 +281,7 @@ extension Table { return self.aggregateWithFunctionName("average", attributeClosure: attributeClosure) } - private func aggregateWithFunctionName(functionName: String, attributeClosure: (T.Type) -> Attribute) -> U { + private func aggregateWithFunctionName(functionName: String, @noescape attributeClosure: (T.Type) -> Attribute) -> U { let attribute = attributeClosure(T.self) let attributeDescription = self.entityDescription.attributesByName[attribute.___name] as! NSAttributeDescription @@ -288,9 +318,9 @@ extension Table { extension Table { - public func fetchAsync(completionClosure: ([T]!, NSError?) -> Void) -> FetchAsyncHandler { + public func fetchAsync(completionHandler: ([T]!, NSError?) -> Void) -> FetchAsyncHandler { return self.context.executeAsynchronousFetchRequestWithFetchRequest(self.toFetchRequest()) { objects, error in - completionClosure(objects as? [T], error) + completionHandler(objects as? [T], error) } } @@ -300,7 +330,7 @@ extension Table { extension Table { - public func batchUpdate(propertiesToUpdate: [NSString : AnyObject], completionClosure: (Int, NSError?) -> Void) { + public func batchUpdate(propertiesToUpdate: [NSString : AnyObject], completionHandler: (Int, NSError?) -> Void) { let batchUpdatePredicate = self.predicate ?? NSPredicate(value: true) self.context.executeBatchUpdateRequestWithEntityDescription( @@ -309,18 +339,18 @@ extension Table { predicate: batchUpdatePredicate ) { updatedObjectsCount, error in dispatch_async(dispatch_get_main_queue()) { - completionClosure(updatedObjectsCount, error) + completionHandler(updatedObjectsCount, error) } } } - public func batchUpdate(attributeToUpdateClosure: (T.Type) -> (Attribute, U), completionClosure: (Int, NSError?) -> Void) { + public func batchUpdate(@noescape attributeToUpdateClosure: (T.Type) -> (Attribute, U), completionHandler: (Int, NSError?) -> Void) { let attributeAndValue = attributeToUpdateClosure(T.self) var propertiesToUpdate = [NSString : AnyObject]() propertiesToUpdate[attributeAndValue.0.___name as NSString] = attributeAndValue.1 as? AnyObject - self.batchUpdate(propertiesToUpdate, completionClosure: completionClosure) + self.batchUpdate(propertiesToUpdate, completionHandler: completionHandler) } } @@ -334,7 +364,7 @@ extension Table { return attributeQuery } - public func select(attributeToSelectClosure: (T.Type) -> Attribute) -> AttributeQuery { + public func select(@noescape attributeToSelectClosure: (T.Type) -> Attribute) -> AttributeQuery { return self.select([attributeToSelectClosure(T.self).___name]) } @@ -353,18 +383,18 @@ extension Table { extension Table { - public func toFetchedResultsController(sectionNameKeyPathClosure: (T.Type) -> Attribute) -> FetchedResultsController { + public func toFetchedResultsController(@noescape sectionNameKeyPathClosure: (T.Type) -> Attribute) -> FetchedResultsController { let sectionNameKeyPath = sectionNameKeyPathClosure(T.self).___name return self.toFetchedResultsController(sectionNameKeyPath: sectionNameKeyPath, cacheName: nil) } public func toFetchedResultsController(sectionNameKeyPath: String? = nil, cacheName: String? = nil) -> FetchedResultsController { - return FetchedResultsController(fetchRequest: self.toFetchRequest(), managedObjectContext: self.context.managedObjectContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) + return FetchedResultsController(fetchRequest: self.toFetchRequest(), managedObjectContext: self.context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) } public func toNativeFetchedResultsController(sectionNameKeyPath: String? = nil, cacheName: String? = nil, performFetch: Bool = true) -> NSFetchedResultsController { - let frc = NSFetchedResultsController(fetchRequest: self.toFetchRequest(), managedObjectContext: self.context.managedObjectContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) + let frc = NSFetchedResultsController(fetchRequest: self.toFetchRequest(), managedObjectContext: self.context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) if performFetch { var error: NSError? = nil @@ -387,7 +417,7 @@ extension Table { let arrayController = NSArrayController(content: nil) - arrayController.managedObjectContext = self.context.managedObjectContext + arrayController.managedObjectContext = self.context arrayController.entityName = fetchRequest.entityName arrayController.fetchPredicate = fetchRequest.predicate diff --git a/Source/AlecrimCoreData/Info.plist b/Source/AlecrimCoreData/Info.plist index 13a041c..a155b4b 100644 --- a/Source/AlecrimCoreData/Info.plist +++ b/Source/AlecrimCoreData/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.2 + 3.3 CFBundleSignature ???? CFBundleVersion