Skip to content

Commit 064d17c

Browse files
committed
Merge branch 'develop'
2 parents e38853d + 0ec1f5d commit 064d17c

File tree

73 files changed

+5249
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+5249
-1
lines changed

.github/workflows/swift.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Build and Test
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
nextstep:
9+
runs-on: macos-13
10+
steps:
11+
- name: Select latest available Xcode
12+
uses: maxim-lobanov/setup-xcode@v1
13+
with:
14+
xcode-version: '15.0.0'
15+
- name: Checkout Repository
16+
uses: actions/checkout@v2
17+
- name: Build Swift Debug Package
18+
run: swift build -c debug
19+
- name: Build Swift Release Package
20+
run: swift build -c release
21+
- name: Run Tests
22+
run: swift test

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/config/registries.json
8+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9+
.netrc
10+
.swiftpm
11+
TestGeneration.txt
12+
.docker.build
13+
.DS_Store
14+
Package.resolved
15+

CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Legal
2+
3+
By submitting a pull request, you represent that you have the right to license
4+
your contribution to ZeeZide GmbH and the community, and agree by submitting the
5+
patch that your contributions are licensed under the Apache 2.0 license.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 Data-swift
3+
Copyright (c) 2022 ZeeZide GmbH / Helge Heß
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Package.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// swift-tools-version: 5.9
2+
import PackageDescription
3+
import CompilerPluginSupport
4+
5+
let package = Package(
6+
name: "ManagedModels",
7+
8+
// For now :-)
9+
// macOS v13 needed to build the macro on macOS 13! (for iOS)
10+
platforms: [ .macOS(.v11), .iOS(.v14), .tvOS(.v15), .watchOS(.v8) ],
11+
products: [
12+
.library(name: "ManagedModels", targets: [ "ManagedModels" ])
13+
],
14+
dependencies: [
15+
// Depend on the latest Swift 5.9 prerelease of SwiftSyntax
16+
.package(url: "https://github.com/apple/swift-syntax.git",
17+
from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"),
18+
],
19+
targets: [
20+
.target(name: "ManagedModels", dependencies: [ "ManagedModelMacros" ]),
21+
22+
.macro(
23+
name: "ManagedModelMacros",
24+
dependencies: [
25+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
26+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
27+
]
28+
),
29+
30+
// A test target used to develop the macro implementation.
31+
.testTarget(
32+
name: "ManagedModelTests",
33+
dependencies: [ "ManagedModels" ]
34+
),
35+
36+
// A test target used to develop the macro implementation.
37+
.testTarget(
38+
name: "ManagedModelMacrosTests",
39+
dependencies: [
40+
"ManagedModelMacros",
41+
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
42+
]
43+
),
44+
]
45+
)

README.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<h2>ManagedModels for CoreData
2+
<img src="https://zeezide.com/img/lighter/Lighter256.png"
3+
align="right" width="64" height="64" />
4+
</h2>
5+
6+
> Instead of wrapping CoreData, use it directly :-)
7+
8+
The key thing **ManagedModels** provides is a `@Model` macro,
9+
that works similar (but not identical) to the SwiftData
10+
[`@Model`](https://developer.apple.com/documentation/swiftdata/model())
11+
macro.
12+
It generates an
13+
[`NSManagedObjectModel`](https://developer.apple.com/documentation/coredata/nsmanagedobjectmodel)
14+
straight from the code. I.e. no CoreData modeler / data model file is necessary.
15+
16+
A small sample model:
17+
```swift
18+
@Model class Item: NSManagedObject {
19+
var timestamp : Date
20+
var title : String?
21+
}
22+
```
23+
24+
<details>
25+
<summary>The full CoreData template application converted to ManagedModels</summary><br/>
26+
27+
```swift
28+
import SwiftUI
29+
import ManagedModels
30+
31+
@Model class Item: NSManagedObject {
32+
var timestamp : Date
33+
}
34+
35+
struct ContentView: View {
36+
37+
@Environment(\.modelContext) private var viewContext
38+
39+
@FetchRequest(sort: \.timestamp, animation: .default)
40+
private var items: FetchedResults<Item>
41+
42+
var body: some View {
43+
NavigationView {
44+
List {
45+
ForEach(items) { item in
46+
NavigationLink {
47+
Text("Item at \(item.timestamp!, format: .dateTime)")
48+
} label: {
49+
Text("\(item.timestamp!, format: .dateTime)")
50+
}
51+
}
52+
.onDelete(perform: deleteItems)
53+
}
54+
.toolbar {
55+
ToolbarItem(placement: .navigationBarTrailing) {
56+
EditButton()
57+
}
58+
ToolbarItem {
59+
Button(action: addItem) {
60+
Label("Add Item", systemImage: "plus")
61+
}
62+
}
63+
}
64+
Text("Select an item")
65+
}
66+
}
67+
68+
private func addItem() {
69+
withAnimation {
70+
let newItem = Item(context: viewContext)
71+
newItem.timestamp = Date()
72+
try! viewContext.save()
73+
}
74+
}
75+
76+
private func deleteItems(offsets: IndexSet) {
77+
withAnimation {
78+
offsets.map { items[$0] }.forEach(viewContext.delete)
79+
try! viewContext.save()
80+
}
81+
}
82+
}
83+
84+
#Preview {
85+
ContentView2()
86+
.modelContainer(for: ContentView2.Item.self, inMemory: true)
87+
}
88+
```
89+
90+
</details>
91+
92+
93+
> This is *not* intended as a replacement implementation of
94+
> [SwiftData](https://developer.apple.com/documentation/swiftdata).
95+
> I.e. the API is kept _similar_ to SwiftData, but not exactly the same.
96+
> It doesn't try to hide CoreData, but rather provides utilities to work *with*
97+
> CoreData in a similar way to SwiftData.
98+
99+
100+
#### Requirements
101+
102+
The macro implementation requires Xcode 15/Swift 5.9 for compilation.
103+
The generated code itself though should backport way back to
104+
iOS 10 / macOS 10.12 though (when `NSPersistentContainer` was introduced).
105+
106+
107+
#### Differences to SwiftData
108+
109+
- The model class must explicitly inherit from
110+
[`NSManagedObject`](https://developer.apple.com/documentation/coredata/nsmanagedobject)
111+
(superclasses can't be added by macros),
112+
e.g. `@Model class Person: NSManagedObject`.
113+
- ToMany relationships must be a `Set<Target>`, a plain `[ Target ]` cannot be
114+
used (yet?). E.g. `var contacts : Set<Contact>`.
115+
- Properties cannot be initialized in the declaration,
116+
e.g. this doesn't work: `var uuid = UUID()`.
117+
Must be done in an initializers (requirement by `@NSManaged`).
118+
- CoreData doesn't seem to support optional Swift base types like `Int?`.
119+
- Uses the CoreData `@FetchRequest` property wrapper instead `@Query`.
120+
- Doesn't use the new
121+
[Observation](https://developer.apple.com/documentation/observation)
122+
framework (which requires iOS 17+), but uses
123+
[ObservableObject](https://developer.apple.com/documentation/combine/observableobject)
124+
(which is directly supported by CoreData).
125+
126+
127+
#### TODO
128+
129+
- [ ] Archiving/Unarchiving, required for migration.
130+
- [ ] Figure out whether we can do ordered attributes.
131+
- [ ] Figure out whether we can add support for array toMany properties.
132+
- [ ] Support for "autosave".
133+
- [ ] Support transformable types, not sure they work right yet.
134+
- [ ] Generate property initializers if the user didn't specify any inits?
135+
- [ ] Generate `fetchRequest()` class function.
136+
- [ ] Support SchemaMigrationPlan/MigrationStage.
137+
- [ ] Write more tests.
138+
- [ ] Write DocC docs.
139+
- [ ] Support for entity inheritance.
140+
- [ ] Add support for originalName/versionHash in `@Model`.
141+
- [ ] Generate "To Many" accessor function prototypes (`addItemToGroup` etc).
142+
- [ ] Foundation Predicate support (would require iOS 17+)
143+
- [ ] SwiftUI `@Query` property wrapper/macro?
144+
- [ ] Figure out all the cloud sync options SwiftData has and whether CoreData
145+
can do them.
146+
- [ ] Figure out whether we can allow initialized properties
147+
(`var title = "No Title"`).
148+
149+
Pull requests are very welcome!
150+
Even just DocC documentation or more tests would be welcome contributions.
151+
152+
153+
#### Links
154+
155+
- Apple:
156+
- [CoreData](https://developer.apple.com/documentation/coredata)
157+
- [SwiftData](https://developer.apple.com/documentation/swiftdata)
158+
- [Lighter.swift](https://github.com/Lighter-swift), typesafe and superfast
159+
[SQLite](https://www.sqlite.org) Swift tooling.
160+
161+
162+
#### Disclaimer
163+
164+
SwiftData and SwiftUI are trademarks owned by Apple Inc. Software maintained as
165+
a part of the this project is not affiliated with Apple Inc.
166+
167+
168+
### Who
169+
170+
Models are brought to you by
171+
[Helge Heß](https://github.com/helje5/) / [ZeeZide](https://zeezide.de).
172+
We like feedback, GitHub stars, cool contract work,
173+
presumably any form of praise you can think of.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Created by Helge Heß.
3+
// Copyright © 2023 ZeeZide GmbH.
4+
//
5+
6+
import SwiftCompilerPlugin
7+
import SwiftSyntaxMacros
8+
9+
@main
10+
struct ModelsPlugin: CompilerPlugin {
11+
let providingMacros: [ Macro.Type ] = [
12+
TransientMacro.self,
13+
AttributeMacro.self,
14+
RelationshipMacro.self,
15+
ModelMacro.self
16+
]
17+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// Created by Helge Heß.
3+
// Copyright © 2023 ZeeZide GmbH.
4+
//
5+
6+
import SwiftDiagnostics
7+
8+
enum MacroDiagnostic: String, DiagnosticMessage, Swift.Error {
9+
10+
case modelMacroCanOnlyBeAppliedOnNSManagedObjects
11+
12+
case multipleAttributeMacrosAppliedOnProperty
13+
case multipleRelationshipMacrosAppliedOnProperty
14+
case attributeAndRelationshipMacrosAppliedOnProperty
15+
case propertyNameIsForbidden
16+
17+
case propertyHasNeitherTypeNorInit
18+
19+
case multipleMembersInAttributesMacroCall
20+
21+
case unexpectedTypeForExtension
22+
23+
var message: String {
24+
switch self {
25+
case .modelMacroCanOnlyBeAppliedOnNSManagedObjects:
26+
"The @Model macro can only be applied on classes that inherit from NSManagedObject."
27+
28+
case .propertyNameIsForbidden:
29+
"This property name cannot be used in NSManagedObject types, " +
30+
"it is a system property."
31+
32+
case .multipleAttributeMacrosAppliedOnProperty:
33+
"Multiple @Attribute macros applied on property."
34+
case .multipleRelationshipMacrosAppliedOnProperty:
35+
"Multiple @Relationship macros applied on property."
36+
case .attributeAndRelationshipMacrosAppliedOnProperty:
37+
"Both @Attribute and @Relationship macros applied on property."
38+
39+
case .propertyHasNeitherTypeNorInit:
40+
"Property has neither type nor initializer?"
41+
42+
case .multipleMembersInAttributesMacroCall:
43+
"Compiler issue, multiple members in attributes macro."
44+
case .unexpectedTypeForExtension:
45+
"Compiler issue, unexpected type passed into extension."
46+
}
47+
}
48+
49+
var diagnosticID: SwiftDiagnostics.MessageID {
50+
.init(domain: "ModelsMacro", id: rawValue)
51+
}
52+
53+
var severity: SwiftDiagnostics.DiagnosticSeverity {
54+
.error
55+
}
56+
}

0 commit comments

Comments
 (0)