Can't instantiate subclass of NSManagedObject - ios

Xcode 6 has had a ton of bugs. But I'm not quite sure if this is a bug or not. It might not be since this is something I'm just now learning.
My issue is, any time I try to instantiate my subclass of NSManagedObject, I do not have the option to pass the entity: NSEntityDescription and NSManagedContext: insertIntoManagedContext argument to the constructor, Xcode says "Extra Argument 'entity' in call"
I created a new Xcode project from scratch, just to see if I could re-create the problem in a smaller, minimal project.
ToDoList.Item is set as the Item entity class in the Data Model Inspector.
Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let ent = NSEntityDescription.entityForName("Item", inManagedObjectContext: context)!
//compiler complains here
var item = Item(entity: ent, insertIntoManagedObjectContext: context)!
}
Here's the subclass:
import UIKit
import CoreData
class Item: NSManagedObject {
#NSManaged var title: String
#NSManaged var completed: Bool
}
All help is appreciated.

Just came across the same problem: Init method for core data entity not available
Obviously we have to implement the
init(entity: NSEntityDescription, insertIntoManagedObjectContext context, NSManagedObjectContext?)
method in our custom NSManagedObject class. So just add
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
}
to your entity class and it will work.

Try the final line without exclamation mark, like this:
var item = Item(entity: ent, insertIntoManagedObjectContext: context)
And maybe You haven't added your app name to class name:
Swift classes are namespaced—they’re scoped to the module (typically, the project) they are compiled in. To use a Swift subclass of the NSManagedObject class with your Core Data model, prefix the class name in the Class field in the model entity inspector with the name of your module.
https://developer.apple.com/library/mac/documentation/Swift/Conceptual/BuildingCocoaApps/WritingSwiftClassesWithObjective-CBehavior.html

Are constructors inherited in Swift?
I'd try using NSEntityDescription.insertNewObjectForEntityForName:inManagedObjectContext

Related

CoreData XCTest tests fail when run together but not one at a time

I'm trying to figure out why this crashes when both unit tests are run together.
When run separately, one test at a time, everything works. But when I try to run the tests for the whole class, the second one fails with "The model configuration used to open the store is incompatible with the one that was used to create the store."
Here is a link to a GitHub repo with a dead-simple project with the reproducible issue: https://github.com/MatthewWaller/CoreDataTestingIssueNonBeta
Also, the relevant portion of my code is below. To make it work, add your xcdatamodel file with a "Note" entity with a String "title" attribute and add it to the test target.
import XCTest
import CoreData
#testable import CoreDataTestingIssue
class CoreDataTestingIssueTests: XCTestCase {
private var context: NSManagedObjectContext?
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
self.context = NSManagedObjectContext.contextForTests()
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExampleOne() throws {
guard let context = context else {
return
}
let note = Note(context: context)
note.title = "Hello"
try! context.save()
}
func testExampleTwo() throws {
guard let context = context else {
return
}
let note = Note(context: context)
note.title = "There"
try! context.save()
}
}
extension NSManagedObjectContext {
class func contextForTests() -> NSManagedObjectContext {
// Get the model
let model = NSManagedObjectModel.mergedModel(from: Bundle.allBundles)!
// Create and configure the coordinator
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
try! coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
// Setup the context
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
return context
}
}
I have reproduced the fail. The problem is that your code implements this Note class twice.
In the File Inspector of .xcdatamodeld file, you should only choose one Target Membership.
Choose the application target if you need this data model in the app and tests, or choose the tests' target if you will only use it in your test code.

NSManagedObject with NSCoding not working

I have a Swift 3 iOS app in which I want to be able to share an entity I store in Core Data. What I've done is implement NSCoding in the NSManagedObject class:
import Foundation
import CoreData
import UIKit
public class Event: NSManagedObject, NSCoding {
// MARK: NSCoding
override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
required public init(coder aDecoder: NSCoder) {
let ctx = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: "Group", in: ctx)!
// Note: pass `nil` to `insertIntoManagedObjectContext`
super.init(entity: entity, insertInto: nil)
for attribute:String in self.entity.attributesByName.keys{
self.setValue([aDecoder.decodeObject(forKey: attribute)], forKey: attribute)
}
}
public func encode(with aCoder: NSCoder){
for attribute:String in self.entity.attributesByName.keys{
aCoder.encode(self.value(forKey: attribute), forKey: attribute)
}
}
}
I then try to the serialize the object using:
NSKeyedArchiver.archiveRootObject(selectedGroup, toFile: file)
But when this gets executed it fails with:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance
So, even though I've implemented the NSCoding protocol, for some reason the NSCoding protocol doesn't seem to stick. Any idea what I'm doing wrong?

Cannot decode object of class

I am trying to send a "Class" to my Watchkit extension but I get this error.
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyApp.Person)
Archiving and unarchiving works fine on the iOS App but not while communicating with the watchkit extension. What's wrong?
InterfaceController.swift
let userInfo = ["method":"getData"]
WKInterfaceController.openParentApplication(userInfo,
reply: { (userInfo:[NSObject : AnyObject]!, error: NSError!) -> Void in
println(userInfo["data"]) // prints <62706c69 7374303...
if let data = userInfo["data"] as? NSData {
if let person = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Person {
println(person.name)
}
}
})
AppDelegate.swift
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
reply: (([NSObject : AnyObject]!) -> Void)!) {
var bob = Person()
bob.name = "Bob"
bob.age = 25
reply(["data" : NSKeyedArchiver.archivedDataWithRootObject(bob)])
return
}
Person.swift
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
According to Interacting with Objective-C APIs:
When you use the #objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when you migrate an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the #objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.
By adding the annotation #objc(name), namespacing is ignored even if we are just working with Swift. Let's demonstrate. Imagine target A defines three classes:
#objc(Adam)
class Adam:NSObject {
}
#objc class Bob:NSObject {
}
class Carol:NSObject {
}
If target B calls these classes:
print("\(Adam().classForCoder)")
print("\(Bob().classForCoder)")
print("\(Carol().classForCoder)")
The output will be:
Adam
B.Bob
B.Carol
However if target A calls these classes the result will be:
Adam
A.Bob
A.Carol
To resolve your issue, just add the #objc(name) directive:
#objc(Person)
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
I had to add the following lines after setting up the framework to make the NSKeyedUnarchiver work properly.
Before unarchiving:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
Before archiving:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
NOTE: While the information in this answer is correct, the way better answer is the one below by #agy.
This is caused by the compiler creating MyApp.Person & MyAppWatchKitExtension.Person from the same class. It's usually caused by sharing the same class across two targets instead of creating a framework to share it.
Two fixes:
The proper fix is to extract Person into a framework. Both the main app & watchkit extension should use the framework and will be using the same *.Person class.
The workaround is to serialize your class into a Foundation object (like NSDictionary) before you save & pass it. The NSDictionary will be code & decodable across both the app and extension. A good way to do this is to implement the RawRepresentable protocol on Person instead.
I had a similar situation where my app used my Core framework in which I kept all model classes. E.g. I stored and retrieved UserProfile object using NSKeyedArchiver and NSKeyedUnarchiver, when I decided to move all my classes to MyApp NSKeyedUnarchiver started throwing errors because the stored objects were like Core.UserProfile and not MyApp.UserProfile as expected by the unarchiver. How I solved it was to create a subclass of NSKeyedUnarchiver and override classforClassName function:
class SKKeyedUnarchiver: NSKeyedUnarchiver {
override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
let lagacyModuleString = "Core."
if let range = codedName.range(of: lagacyModuleString), range.lowerBound.encodedOffset == 0 {
return NSClassFromString(codedName.replacingOccurrences(of: lagacyModuleString, with: ""))
}
return NSClassFromString(codedName)
}
}
Then added #objc(name) to classes which needed to be archived, as suggested in one of the answers here.
And call it like this:
if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
currentUserProfile = unarchivedObject
}
It worked very well.
The reason why the solution NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName") was not for me because it doesn't work for nested objects such as when UserProfile has a var address: Address. Unarchiver will succeed with the UserProfile but will fail when it goes a level deeper to Address.
And the reason why the #objc(name) solution alone didn't do it for me was because I didn't move from OBJ-C to Swift, so the issue was not UserProfile -> MyApp.UserProfile but instead Core.UserProfile -> MyApp.UserProfile.
I started facing this after the App Name change,
The error I got was - ".....cannot decode object of class (MyOldModuleName.MyClassWhichISerialized) for key....."
This is because code by default saves Archived object with ModuleName prefix, which will not be locatable after ModuleName changes. You can identify the old Module Name from the error message class prefix, which here is  "MyOldModuleName". 
I simply used the old names to locate the old Archived objects.
So before Unarchieving add line,
NSKeyedUnarchiver.setClass(MyClassWhichISerialized.self, forClassName: "MyOldModuleName.MyClassWhichISerialized")
And before Archieving add line
NSKeyedArchiver.setClassName("MyOldModuleName.MyClassWhichISerialized", for: MyClassWhichISerialized.self)

Use of undeclared type 'AppDelegate' Swift

I've just spent half a day trying to solve next problem.
I am testing the CoreData using Swift language.
Follow this tutorial everything works fine.
But after titorial I've tried to modify the structure and my code. The 'src' and groups inside it is folders, not just groups created by xCode.
NSSExpense.swift
import Foundation
import CoreData
class NSSExpense: NSManagedObject {
#NSManaged var name: String
#NSManaged var descr: String
#NSManaged var value: NSNumber
#NSManaged var isMonthly: NSNumber
#NSManaged var payDayInMonth: NSNumber
class func createInManagedObjectContext(moc: NSManagedObjectContext, name: String, value: Double, payDayInMonth: Int16, isMonthly: Bool, descr: String!) -> NSSExpense {
let newExpense = NSEntityDescription.insertNewObjectForEntityForName("NSSExpense", inManagedObjectContext: moc) as NSSExpense
newExpense.name = name
newExpense.value = NSNumber(double: value)
newExpense.payDayInMonth = NSNumber(short: payDayInMonth)
newExpense.isMonthly = NSNumber(bool: isMonthly)
if let expenseDesctiption = descr {
newExpense.descr = expenseDesctiption
} else {
newExpense.descr = ""
}
return newExpense
}
}
NSSDataManager.swift
import UIKit
import CoreData
class NSSDataManager: NSObject {
class var sharedDataManager: NSSDataManager {
struct Static {
static var instance: NSSDataManager?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = NSSDataManager()
}
return Static.instance!
}
lazy var managedObjectContext : NSManagedObjectContext? = {
// Error at the next line "Use of undeclared type 'NSSAppDelegate'"
let appDelegate = UIApplication.sharedApplication().delegate as NSSAppDelegate
if let managedObjectContext = appDelegate.managedObjectContext {
return managedObjectContext
} else {
return nil
}
}()
var expensesInMemory : [NSSExpense] {
get {
let fetchRequest = NSFetchRequest(entityName: "NSSExpense")
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSSExpense] {
return fetchResults
} else {
return [NSSExpense]()
}
}
}
func addExpenseWithName(name: String, value: Double, payDayInMonth: Int16, isMonthly: Bool, descr: String!) -> NSSExpense {
return NSSExpense.createInManagedObjectContext(managedObjectContext!, name: name, value: value, payDayInMonth: payDayInMonth, isMonthly: isMonthly, descr: descr?)
}
}
I've tried to solve this problem different ways:
1) Create new project (Swift main language) and make the same structure again (failed)
2) Create new project (Objective-C main language). So I have the AppDelegate.h and AppDelegate.m. Add it to Swift files using Bridging-Header. The same problem. (failed)
Really interesting next thing. If I put next code to the ViewController.swift which creates automatically with new project everything works fine. But when I put this code to any other class. I've code this error.
lazy var managedObjectContext : NSManagedObjectContext? = {
// Error at the next line "Use of undeclared type 'NSSAppDelegate'"
let appDelegate = UIApplication.sharedApplication().delegate as NSSAppDelegate
if let managedObjectContext = appDelegate.managedObjectContext {
return managedObjectContext
} else {
return nil
}
}()
[UPDATE 1]
I've tried to create another class right at the same folder as NSSAppDelegate.swift and now everything works fine. However it's still an issue, how can I use the classes which stored in other folders?
[UPDATE 2]
Just tried to do the same thing in other project.
If the file structure is like this so the AppDelegate.swift and NGDataManager.swift are in the same folder everything works great.
BUT, if I put the NGDataManager.swift inside the 'src' folder like this (not just group, folder) the error occurs. May be I should create the other question for this.
[UPDATE 3]
I don't know how, but you can forget all I've said in UDATE 2. Because Now thats all not work. I even create a new project with CoreData named "Test" and just create a new class named "TestClass". The magic is in the next thing: if I put this code inside the TestClass.swift
import UIKit
class TestClass: NSObject {
func someFunc() {
let appDel = UIApplication.sharedApplication().delegate as AppDelegate
}
}
the error occurs. But if I put this line in viewDidLoad in ViewController.swift which was generated automatically by xCode
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let appDel = UIApplication.sharedApplication().delegate as AppDelegate
}
}
their is no error and everything works great. I don't know what to say...
You may see the AppDelegate code here but I didn't modify anything generated automatically by xCode. I've created a Single View Application with this settings.
Chances are when you created your project you also created a '{ProjectName}Tests' target. The problem is AppDelegate is not assigned membership in the '{ProjectName}Tests' target.
Select AppDelegate.swift then in the right-hand inspector click on the File Inspector (the paper icon) then make sure in the "Target Membership" both your project and the test target checkmarks are set to ON.
Clean, rebuild.
If trying to access an Objective-C AppDelegate in Swift code, make sure you have #import "AppDelegate.h" in your bridging header file.
This had me stuck for about an hour :-/
I had a similar problem that was resolved when I did a "Clean" option in the Product menu
Ran into this issue in Xcode 8 and this was the first result when I searched on google so adding my solution.
If your project has UI and Unit Tests, make sure your AppDelegate is added to both target memberships. Once I added it to my Tests I was able to access any variables I had in my AppDelegate without having to use UIApplication.shared regardless of how my files were grouped.
To check, go to your AppDelegate, click on the file inspector, and look at Target Membership.
Make sure the Target Memberships are set the same in each file. Target memberships can be found in the properties inspector when you click on a specific file.
It suppose to be like this.
let appDelegate: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
Instead of:
let appDelegate = UIApplication.sharedApplication().delegate as NSSAppDelegate
try:
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate

Why am I getting "Swift Dynamic Cast Failed" ?? :/

I was wondering if someone could explain the concept of "cast" or "casting" to me regarding programming and also help me with the issue of getting "Swift dynamic cast failed" when trying to execute this code:
import UIKit
import CoreData
class vcMain: UIViewController {
#IBOutlet var txtUsername: UITextField!
#IBOutlet var txtPassword: UITextField!
#IBAction func btnSave(){
//println("Save button pressed \(txtUsername.text)")//Adds the text written, in the console
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObjectContext
newUser.setValue("Test Username", forKey: "username")
newUser.setValue("Test Password", forKey: "password")
context.save(nil)
println(newUser)
println("Object Saved.")
}
#IBAction func btnLoad(){
//println("Load button pressed \(txtPassword.text)")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I'm following a tutorial on YouTube but cant figure out why its not working for me!
This line looks wrong:
var newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObjectContext
You're casting to a NSManagedObjectContext when you probably meant to cast to a NSManagedObject, so the line should be:
var newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObject
I agree with Jack Lawrence's answer.
We are looking at the function NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) which, from the documentation, will return a objC type, id, which kinda translates to Swift as an AnyObject. This is to say the computer doesn't know what it is, but we do, because we read the documentation:
insertNewObjectForEntityForName:inManagedObjectContext:
Creates, configures, and returns an instance of the class for the entity with a given name.
Return Value
A new, autoreleased, fully configured instance of the class for the entity named entityName. The instance has its entity description set and is inserted it into context.
Anyway, you can be safe to assume it is returning a type NSManagedObject, or some subclass of it.
Now about casting:
Most of this is from the Swift iBook. So you already know that type casting / downcasting in swift is done by the as operator. Because downcasting can fail, the type cast operator comes in two forms. 1) Forced as and 2) Optional as?
Basically the differnce is, use as? when you are not sure if the downcast will succeed, and use as when you are certain it will. Here is an example, where I have an array of type AnyObject called Vehicles..., which contains two different classes, Car and Truck, of which there are 5 cars and 2 trucks.
var carCount = 0
var truckCount = 0
for vehicle in vehicles{
if let car = car as? Car
{
carCount++
} else
if let truck = truck as? Truck
{
truckCount++
}
}
At the end of this my counts are correct, and I have not thrown an error. But, trying to access any one of the elements of vehicles, like vehicles[i] as Car would be dangerous, because it might be a truck, and you are forcefully saying that it is a car. Runtime error!
Anyway I hope that explains it a bit. If not, check out the chapter on type casting in the Swift iBook, page 391.
For anyone else out there with a similar problem. There is a little gotcha here.
If you are trying to generate a custom NSManagedObject subclass using the NSEntityDescription.insertNewObjectForEntityForName(Entity, inManagedObjectContext: context) method, then make sure that the swift class has the #objc() header added to the class. Otherwise you'll encounter a dynamic class cast failure also.

Resources