Working with coredate I used the option codegen: category/extension to be able to create a file where I can put in the re-usable code for finding, updating or deleting database entries.
I started the coredata entity first with the codegen option Class Definition and changed it to category/extension in a later stage.
Now I run against a compile error:
'Property cannot be declared public because its type uses an internal type'
The file name is a generated swift file called:
Gameresults+Coredataproperties.swift
I got the error on the player: TournamentPlayer?
player and round are both relations to another entity.
import Foundation
import CoreData
extension GameResults {
#nonobjc public class func fetchRequest() -> NSFetchRequest<GameResults> {
return NSFetchRequest<GameResults>(entityName: "GameResults")
}
#NSManaged public var earnedRankingPoints: Int16
#NSManaged public var framePoints: Int16
#NSManaged public var highestBreak: Int16
#NSManaged public var isWon: Bool
#NSManaged public var player: TournamentPlayer?
#NSManaged public var round: Rounds?
}
I could not believe that the error did come out of swift so I tried the clean build folder option, saving file, exiting XCode, etc. Nothing worked.
Any tips where to look at how to fix this?
Use the Manual/None option for the codegen.
There are a couple of things that you should consider here.
when you use codegen it automatically creates all classes which are equivalent to your entities as public. I assume TournamentPlayer class is not public and you have definded it yourself. so based on default definition it is internal and the compiler gives you the error correctly. the only thing you can do here is changing the access level and because you're using the codegen and it means you want to change your model, so it's better to change your TournamentPlayer to public. Although it depends on the architecture of your application.
The codegen is automatically regenrated whenever you change your model and is saved in DerivedData.
If you want to take care of it yourself you set it to manual and you handle the code generation yourself manually.
check this article, it gives you a better understanding of codegen https://useyourloaf.com/blog/core-data-code-generation/
one more thing, when you change to manual/none you have to do a clean build otherwise it still uses the generated code in DerivedData
Related
I'm working with a Cocoa Touch framework, built for distribution, which uses Core Data managed object subclasses. When I implement the framework in a client project built with the same Xcode/Swift version as the framework, the app compiles successfully.
However, when I build the framework with Xcode 11.1 (Swift 5.1), and implement the framework in a client project using Xcode 11.2.1 (Swift 5.1.2), I get a compile error in the property declaration of the generated "swiftinterface" file. "#NSManaged not allowed on computed properties"
From Framework project NSManagedObject subclass:
extension TestEntity {
#nonobjc public class func fetchRequest() -> NSFetchRequest<TestEntity> {
return NSFetchRequest<TestEntity>(entityName: "TestEntity")
}
#NSManaged public var id: Int64
}
From Framework .swiftinterface file:
#objc #NSManaged dynamic public var id: Swift.Int64 {
#objc get
#objc set(value)
}
If I remove the setter-getter, the client app builds and runs, but I see this warning:
Cannot load Swift type information; AST validation error in "my_test_framework": The module file format is too old to be used by this version of the debugger.
Thoughts, rants, lessons, and/or workarounds are welcome!
I created a CoreData Model with an Entity "News" :
I set it to "Manuel/None" and created a NSManagedObject :
public class News: NSManagedObject {
#NSManaged var id: String
#NSManaged var newsType: Int16
#NSManaged var newsImageUrl: String
#NSManaged var newsVideoUrl: String
#NSManaged var newsTitle: String
#NSManaged var newsDesc: String
}
I want to override the properties of my entity without touching the CoreData Model, just by doing this :
extension News {
#NSManaged var newsUrl: String
}
Of course, if I do :
news.newsUrl = ""
I get a nice
reason: '-[NSManagedObject setNewsUrl:]: unrecognized selector
How can I add properly new properties in my Entity (without modifying CoreData Model) and, of course, I want this news property to be saved in CoreData ?
TY
The approach you tried doesn't work because it's not enough to just declare the new property, you have to make that property exist in the data model. If you don't edit the model, you have to do the work in your code.
You can modify the entire model in code until you start using it. Once you load your persistent store file, you have to treat the object model as read-only. The basic steps would be
Ask the NSManagedObjectModel for its entities or entitiesByName.
Find the appropriate NSEntityDescription in that list.
Create a new NSAttributeDescription for your new property.
Add the new attribute to the properties array on the entity.
This is not a good idea, and I strongly recommend not doing it, but it's not impossible. In many years of Core Data coding I've only modified the model in code once, to work around a (since fixed) bug in the model compiler.
Keep in mind that this does not let you avoid doing model migration. Your persistent store file must match the data model that you use. Modifying the model in code will make managing model versions more difficult, and will increase the odds of the app crashing because the models don't match.
I have created 2 core data entities and then created NSManagedObject Subclasses for them from the editor menu.
However when I run my app I get errors on every line of all the files for some reason.
Here is an example, these errors are the same for both entities created files.
File Code was auto generated so i can apste it here but not sure of its use
import Foundation
import CoreData
extension UserExercise {
#nonobjc public class func fetchRequest() -> NSFetchRequest<UserExercise> {
return NSFetchRequest<UserExercise>(entityName: "UserExercise");
}
#NSManaged public var id: Int16
#NSManaged public var name: String?
#NSManaged public var reps: Int16
#NSManaged public var sets: Int16
#NSManaged public var weight: Int16
#NSManaged public var relationship: NSSet?
}
// MARK: Generated accessors for relationship
extension UserExercise {
#objc(addRelationshipObject:)
#NSManaged public func addToRelationship(_ value: UserRoutine)
#objc(removeRelationshipObject:)
#NSManaged public func removeFromRelationship(_ value: UserRoutine)
#objc(addRelationship:)
#NSManaged public func addToRelationship(_ values: NSSet)
#objc(removeRelationship:)
#NSManaged public func removeFromRelationship(_ values: NSSet)
}
Errors are:
Command/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1
Invalid redeclaration of 'UserRoutine'
'UserExercise' is ambiguous for type lookup in this context
#NSManaged only allowed on an instance property or method
Theres too many to list its basically these repeated over and over
Xcode by default manage generation of these subclasses for you. If you want to manage generation of them yourself then:
Select your entities in the data model.
Find Codegen in the utilities pane(right pane) and set it to Manual/None.
Clean and build.
The current default in Xcode is to automatically create subclasses of NSManagedObject for you in the /Users/<your user name>/Library/Developer/Xcode/DerivedData/AppName-agkwmibzbopevjgfajlcbyixzyev/Build/Intermediates/AppName.build/Debug-iphonesimulator/AppName.build/DerivedSources/CoreDataGenerated/Modeldirectory; The DerivedData directory is where Xcode saves automatically generated code. You are redeclaring the same subclass by doing Editor>Create NSManagedObject Subclass... that is why you are getting the "Invalid redeclaration of 'UserRoutine' 'UserExercise' is ambiguous for type lookup in this context #NSManaged only allowed on an instance property or method" error. To resolve the errors and manually create a subclass of NSManagedObjects what you need to do is:
Open terminal and navigate to /Users/<your user name>/Library/Developer/Xcode/DerivedData/AppName-agkwmibzbopevjgfajlcbyixzyev/Build/Intermediates/AppName.build/Debug-iphonesimulator/AppName.build/DerivedSources/CoreDataGenerated/Model
Run this command: rm -rf * (now be careful with this command, run it only when you get to the final directory where the generated codes are or you'll break your project for good)
Delete your current data model
Create a new data model
Select your new data model's entity (do this for each entity within the data model) and go to its attributes inspector and set its Codegen to Manual/None Before the first run after you have created a new data model.
Create a subclass of NSManagedObject by going to Editor>Create NSManagedObject Subclass...
Your errors should disappear.
Hope this helped!
A simple approach that worked for me .... You can find these by choosing an entity and clicking on the Data Model Inspector at the top right . Do this for all of your entities
Its important to FIRST set Module to "Current Product Module" AND Codegen to "Manual/None"
Only then, SECOND: Editor -> Create NSManagedObject subclass.
This is for Swift 3.0 and Xcode 8.2.1(8C1002)
Not sure why you would manually navigate to the project when you can just instead delete the files directly from Xcode.
Find all of your generated files from the xcdatamodeld, inside of your project navigator. Delete all of the files and make sure you move to trash, do not remove references otherwise you're going to have to manually delete the files from:
/Users//Library/Developer/Xcode/DerivedData/AppName-agkwmibzbopevjgfajlcbyixzyev/Build/Intermediates/AppName.build/Debug-iphonesimulator/AppName.build/DerivedSources/CoreDataGenerated/Model
Ok so after you've deleted the files, open your xcdatamodeld, select all of your entities, and in the Utilities panel, select Data Model Inspector button, and make sure that Codegen is set to "Manual/None".
Select your xcdatamodeld and make sure that that Codegen is set to "Manual/None"
Keep all of your entities selected and click Editor > Create NSManagedObject Subclass, make sure to select the folder and drag and drop your generated files in your Model sub-group and your Generated sub-group.
This worked for me and it should work for you too.
I hope that I have helped you.
I also ran into this bug. I fixed it by deleting the old generated entity subclasses and creating it again and for the group I selected the workspace instead of the project or folder in the project, and the subclasses got saved above the project file. It looks weird but it fixed the issue. Also if i move the files into the folder inside the project the error reappears.
I ran into this issue during unit testing due to recreating my NSPersistentContainer over and over again in every unit test. It looks like loading the same managed object model repeatedly could lead to this.
My resolution was to use a single shared instance across all of my unit tests and do clean up between them accordingly.
I was getting the "... is ambiguous for type lookup in this context" error when building a simple SwiftUI Mac app for testing this issue after running Editor/Create NSManagedObject subclass..." with Xcode Version 13.4 (13F17a) on macOS Monterey 12.3.1.
I found a solution in deselecting/unchecking the checkbox for "Targets" in the final dialog before clicking "Create". This checkbox was selected by default. Clearing it and clicking "Create" resulted in a successful build.
This results with the subclass files at the top level of the project structure:
I have some swift extensions I want to across projects.
I'd like to avoid category pollution though, unless those extensions are requested.
Is it possible to write them so that they only apply if I've done a certain import, like:
import MySwiftExtensions
// Use custom extensions
let x = [1,3,5,7].average()
let y = [1,3,5,7].firstWhere { $0 > 3 }
let z = "campervan".make1337()
I could write these as static methods wrapped in a single letter class (eg: ø.average([1,3,5,7]), like lodash) to achieve the same thing but sometimes you get much more concise usage from instance methods.
You wrote:
I have some swift extensions I want to across projects...
When I have code that I want to use across projects I create a separate framework to hold that code. Then, when I want to use that code in a new project, I embed the framework in that project. Or, for development purposes, I create a workspace that includes the project and the framework. That allows me to work on both at the same time, and then only embed the framework in the final product when it is time to export it.
Once the framework is either embedded or in the same workspace, then you should be able to import it into any individual file in your project with:
import MySwiftExtensions
Any file that does not have the import statement will not have access to the extensions.
EDIT:
Here is a link to a blog that describes how to create a Cocoa Touch Framework. And here is another link that describes in detail how to use workspaces to use frameworks in development projects.
I would like to focus attention on what you reported: "..only apply if I've done a certain import.."
It would also mean you want these extensions can be applyed only to a specific class
As reported in this interesting Apple blog chapter and in the official Apple doc you can handle the "Access Control" of your extension
You can extend a class, structure, or enumeration in any access
context in which the class, structure, or enumeration is available.
Any type members added in an extension have the same default access
level as type members declared in the original type being extended. If
you extend a public or internal type, any new type members you add
will have a default access level of internal. If you extend a private
type, any new type members you add will have a default access level of
private.
Alternatively, you can mark an extension with an explicit access level
modifier (for example, private extension) to set a new default access
level for all members defined within the extension. This new default
can still be overridden within the extension for individual type
members.
/* no access level modifier: default access level will be 'internal' */
extension UIViewSubClass
{
// default access level used: internal
var helloWorld : String {
get {
return "helloWorld"
}
}
}
// modify default access level to public
public extension UIViewSubClass
{
// default access level used: public
var helloWorld : String {
get {
return "helloWorld"
}
}
}
The members of extensions marked private are available within the file where they’re defined, and are not available outside that file. Outside the file where the private extension members were defined, any attempt to use them results in an error, and auto-complete wouldn’t even list them
// modify default access level to private
private extension UIViewSubClass
{
var helloWorld : String {
get {
return "helloWorld"
}
}
}
I don't believe you can do what you want per se, but I've used the following approach to provide functionality to only the specific class that implements an interface:
protocol ExampleProtocol {
}
extension ExampleProtocol where Self: UIViewController{
// extend what you need to here
}
This is for XCode 6 and Swift...
I'm trying to make a fetch request to the managed object context but it's not returning the correct subclass.
I have already set the subclass in the data model data modeler configuration to the name of my custom subclass and in the code, it is extending the NSManagedObject class.
Any ideas?
Just figured out the solution.
I had to add the #objc attribute to allow the class to be compatible with Objective-C.
Now the fetch request is returning a correct result of Tasks[]
import Foundation
import CoreData
#objc(Task) // make compatible with objective-c
class Task : NSManagedObject
{
#NSManaged var note: String!
#NSManaged var completed: Bool
}
Reference: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_36
Using #objc(Task) seems to be working but you could also just edit the data model data modeler configuration to the name ToDoList.Task instead of just Task. That will work too and avoid Class conflicts if Task is used anywhere else in the Objective-C code.
Check to make sure that in the "Entity" inspector (right side of the screen, Utilities pane) when Task is selected in your Model that its Class field is properly filled in with "Task" (it's blank by default).