Skip to content

Commit d1ba949

Browse files
author
Geor Kasapidi
committed
add sources
1 parent 3d72fb3 commit d1ba949

Some content is hidden

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

62 files changed

+4068
-2
lines changed

.gitignore

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## User settings
6+
xcuserdata/
7+
8+
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9+
*.xcscmblueprint
10+
*.xccheckout
11+
12+
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13+
build/
14+
DerivedData/
15+
*.moved-aside
16+
*.pbxuser
17+
!default.pbxuser
18+
*.mode1v3
19+
!default.mode1v3
20+
*.mode2v3
21+
!default.mode2v3
22+
*.perspectivev3
23+
!default.perspectivev3
24+
25+
## Obj-C/Swift specific
26+
*.hmap
27+
28+
## App packaging
29+
*.ipa
30+
*.dSYM.zip
31+
*.dSYM
32+
33+
## Playgrounds
34+
timeline.xctimeline
35+
playground.xcworkspace
36+
37+
# Swift Package Manager
38+
#
39+
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40+
# Packages/
41+
# Package.pins
42+
# Package.resolved
43+
# *.xcodeproj
44+
#
45+
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46+
# hence it is not needed unless you have added a package configuration file to your project
47+
# .swiftpm
48+
49+
.build/
50+
51+
# CocoaPods
52+
#
53+
# We recommend against adding the Pods directory to your .gitignore. However
54+
# you should judge for yourself, the pros and cons are mentioned at:
55+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56+
#
57+
# Pods/
58+
#
59+
# Add this line if you want to avoid checking in source code from the Xcode workspace
60+
# *.xcworkspace
61+
62+
# Carthage
63+
#
64+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
65+
# Carthage/Checkouts
66+
67+
Carthage/Build/
68+
69+
# Accio dependency management
70+
Dependencies/
71+
.accio/
72+
73+
# fastlane
74+
#
75+
# It is recommended to not store the screenshots in the git repo.
76+
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
77+
# For more information about the recommended setup visit:
78+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
79+
80+
fastlane/report.xml
81+
fastlane/Preview.html
82+
fastlane/screenshots/**/*.png
83+
fastlane/test_output
84+
85+
# Code Injection
86+
#
87+
# After new code Injection tools there's a generated folder /iOSInjectionProject
88+
# https://github.com/johnno1962/injectionforxcode
89+
90+
iOSInjectionProject/
91+
.DS_Store
92+
.swiftpm
93+
*.xcworkspace/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Geor Kasapidi
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// swift-tools-version:5.3
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "Sworm",
8+
platforms: [
9+
.macOS(.v10_13),
10+
.iOS(.v11),
11+
.tvOS(.v11),
12+
.watchOS(.v4),
13+
],
14+
products: [
15+
.library(
16+
name: "Sworm",
17+
targets: ["Sworm"]
18+
),
19+
.library(
20+
name: "SwormTools",
21+
targets: ["SwormTools"]
22+
),
23+
],
24+
targets: [
25+
.target(
26+
name: "Sworm",
27+
dependencies: []
28+
),
29+
.target(
30+
name: "SwormTools",
31+
dependencies: []
32+
),
33+
.testTarget(
34+
name: "SwormTests",
35+
dependencies: ["Sworm", "SwormTools"]
36+
),
37+
]
38+
)

README.md

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,54 @@
1-
# Sworm
2-
CoreData based Swift ORM
1+
# Swift ORM
2+
3+
![SWORM](logo.svg)
4+
5+
## Features
6+
7+
1) Pure swift objects - no more subclasses of NSManagedObject
8+
2) Extensible attribute system - store any type in CoreData storage by implementing a simple protocol in type extension
9+
3) Strongly typed data queries
10+
4) Progressive migrations
11+
12+
## Motivation
13+
14+
CoreData is hard. It was originally designed as a data layer for applications with a focus on I/O performance, but hardware has become more powerful over time and the complexity of CoreData still persists (lol). In modern applications, building CoreData-based data layer is expensive and often unreasonable decision.
15+
16+
Even the NSPersistentContainer doesn't relieve us of the need to keep track of the lifecycle of the managed objects associated with the context and remember to read / write on the context queue. In addition, an application often has a second set of data models, similar to managed objects and code for converting between the two sets of models.
17+
18+
Apple is aware of all of this and in modern guides prefers data persistence based on Codable models.
19+
20+
At the same time, CoreData has many advantages - a powerful visual editor for data models, automatic migrations, a simplified (compared to SQL) query system, secure multi-threaded access to data out of the box, and so on.
21+
22+
Sworm is a tool that hides the complexity of CoreData from the developer, but keeps the advantages.
23+
24+
## How to use
25+
26+
[Basic usage](docs/basic_usage.md)
27+
28+
[Attributes](docs/attributes.md)
29+
30+
[Queries](docs/queries.md)
31+
32+
[Read and write your data graph](docs/read_write.md)
33+
34+
[Proper setup of PersistentContainer](docs/setup_pc.md)
35+
36+
[Progressive migrations](docs/migrations.md)
37+
38+
## Examples
39+
40+
[See tests](/Tests/SwormTests/)
41+
42+
## Installation
43+
44+
Use SPM:
45+
46+
``` swift
47+
dependencies: [
48+
.package(url: "https://github.com/geor-kasapidi/Sworm.git", .upToNextMajor(from: "1.0.0"))
49+
]
50+
```
51+
52+
## Code style
53+
54+
`swiftformat --self insert --swiftversion 5.3 .`
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import CoreData
2+
3+
public final class Attribute<PlainObject: ManagedObjectConvertible>: Hashable {
4+
let name: String
5+
let keyPath: PartialKeyPath<PlainObject>
6+
7+
let encode: (PlainObject, NSManagedObject) -> Void
8+
let decode: (inout PlainObject, NSManagedObject) throws -> Void
9+
10+
public func hash(into hasher: inout Hasher) {
11+
self.keyPath.hash(into: &hasher)
12+
}
13+
14+
public static func == (lhs: Attribute<PlainObject>, rhs: Attribute<PlainObject>) -> Bool {
15+
lhs.keyPath == rhs.keyPath
16+
}
17+
18+
public init<Attribute: SupportedAttributeType>(
19+
_ keyPath: WritableKeyPath<PlainObject, Attribute>,
20+
_ name: String
21+
) {
22+
self.name = name
23+
self.keyPath = keyPath
24+
self.encode = { plainObject, managedObject in
25+
managedObject[primitiveValue: name] = plainObject[keyPath: keyPath].encodePrimitiveValue()
26+
}
27+
self.decode = { plainObject, managedObject in
28+
do {
29+
plainObject[keyPath: keyPath] = try Attribute.decode(managedObject[primitiveValue: name])
30+
} catch {
31+
throw AttributeError.badAttribute(
32+
.init(
33+
name: name,
34+
entity: managedObject.entity.name ?? "",
35+
originalError: error
36+
)
37+
)
38+
}
39+
}
40+
}
41+
42+
public init<Attribute: SupportedAttributeType>(
43+
_ keyPath: WritableKeyPath<PlainObject, Attribute?>,
44+
_ name: String
45+
) {
46+
self.name = name
47+
self.keyPath = keyPath
48+
self.encode = { plainObject, managedObject in
49+
managedObject[primitiveValue: name] = plainObject[keyPath: keyPath]?.encodePrimitiveValue()
50+
}
51+
self.decode = { plainObject, managedObject in
52+
do {
53+
plainObject[keyPath: keyPath] = try Attribute?.decode(managedObject[primitiveValue: name])
54+
} catch {
55+
throw AttributeError.badAttribute(
56+
.init(
57+
name: name,
58+
entity: managedObject.entity.name ?? "",
59+
originalError: error
60+
)
61+
)
62+
}
63+
}
64+
}
65+
}
66+
67+
extension ManagedObjectConvertible {
68+
static func attribute(_ keyPath: PartialKeyPath<Self>) -> Attribute<Self> {
69+
self.attributes.first(where: { $0.keyPath == keyPath }).unsafelyUnwrapped
70+
}
71+
72+
@discardableResult
73+
func encodeAttributes(to managedObject: NSManagedObject) -> NSManagedObject {
74+
Self.attributes.forEach {
75+
$0.encode(self, managedObject)
76+
}
77+
return managedObject
78+
}
79+
80+
init(from managedObject: NSManagedObject) throws {
81+
self.init()
82+
try Self.attributes.forEach {
83+
try $0.decode(&self, managedObject)
84+
}
85+
}
86+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public enum AttributeError: Swift.Error {
2+
public struct Context {
3+
public let name: String
4+
public let entity: String
5+
public let originalError: Swift.Error
6+
}
7+
8+
case badInput(Any?)
9+
case badAttribute(Context)
10+
}

Sources/Sworm/Attributes/Decode.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
extension SupportedAttributeType {
2+
static func decode(_ anyValue: Any?) throws -> Self {
3+
guard let value = anyValue as? Self.PrimitiveAttributeType else {
4+
throw AttributeError.badInput(anyValue)
5+
}
6+
return try Self.decode(primitiveValue: value)
7+
}
8+
}
9+
10+
extension Optional where Wrapped: SupportedAttributeType {
11+
static func decode(_ anyValue: Any?) throws -> Wrapped? {
12+
try anyValue.flatMap {
13+
try Wrapped.decode($0)
14+
}
15+
}
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Foundation
2+
3+
public extension PrimitiveAttribute {
4+
func encodePrimitiveValue() -> Self { self }
5+
6+
static func decode(primitiveValue: Self) throws -> Self { primitiveValue }
7+
}
8+
9+
extension Bool: PrimitiveAttribute, SupportedAttributeType {}
10+
11+
extension Int: PrimitiveAttribute, SupportedAttributeType {}
12+
extension Int16: PrimitiveAttribute, SupportedAttributeType {}
13+
extension Int32: PrimitiveAttribute, SupportedAttributeType {}
14+
extension Int64: PrimitiveAttribute, SupportedAttributeType {}
15+
16+
extension Float: PrimitiveAttribute, SupportedAttributeType {}
17+
extension Double: PrimitiveAttribute, SupportedAttributeType {}
18+
extension Decimal: PrimitiveAttribute, SupportedAttributeType {}
19+
20+
extension Date: PrimitiveAttribute, SupportedAttributeType {}
21+
extension String: PrimitiveAttribute, SupportedAttributeType {}
22+
extension Data: PrimitiveAttribute, SupportedAttributeType {}
23+
extension UUID: PrimitiveAttribute, SupportedAttributeType {}
24+
extension URL: PrimitiveAttribute, SupportedAttributeType {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
public protocol PrimitiveAttribute {}
2+
3+
public protocol SupportedAttributeType {
4+
associatedtype PrimitiveAttributeType: PrimitiveAttribute
5+
6+
func encodePrimitiveValue() -> PrimitiveAttributeType
7+
8+
static func decode(primitiveValue: PrimitiveAttributeType) throws -> Self
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
public extension RawRepresentable where RawValue: SupportedAttributeType {
2+
func encodePrimitiveValue() -> RawValue.PrimitiveAttributeType {
3+
self.rawValue.encodePrimitiveValue()
4+
}
5+
6+
static func decode(primitiveValue: RawValue.PrimitiveAttributeType) throws -> Self {
7+
let rawValue = try RawValue.decode(primitiveValue: primitiveValue)
8+
guard let value = Self(rawValue: rawValue) else {
9+
throw AttributeError.badInput(rawValue)
10+
}
11+
return value
12+
}
13+
}

0 commit comments

Comments
 (0)