initial commit

This commit is contained in:
Shial 2018-07-11 23:43:22 +10:00
commit cead37bf70
16 changed files with 628 additions and 0 deletions

53
.gitignore vendored Normal file
View File

@ -0,0 +1,53 @@
Packages
*.pins
## Breakpoints
*.xcbkptlist
## Build generated
.build
build
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
#*.xcodeproj
#*.xcodeproj/*
#!*.xcodeproj/project.pbxproj
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
Packages/
.build/
# CocoaPods
/Pods
# Carthage
Carthage/Checkouts
Carthage/Build
*.pins

1
.swift-version Normal file
View File

@ -0,0 +1 @@
4.0

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Shial
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
Package.resolved Normal file
View File

@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "SLazeKit",
"repositoryURL": "https://github.com/shial4/SLazeKit.git",
"state": {
"branch": null,
"revision": "d7edbee9976e47e47e70db10dae8b5e5a44c6a9f",
"version": "0.2.0"
}
}
]
},
"version": 1
}

27
Package.swift Normal file
View File

@ -0,0 +1,27 @@
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "SLazeCoreData",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "SLazeCoreData",
targets: ["SLazeCoreData"]),
],
dependencies: [
.package(url: "https://github.com/shial4/SLazeKit.git", from: "0.2.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "SLazeCoreData",
dependencies: []),
.testTarget(
name: "SLazeCoreDataTests",
dependencies: ["SLazeCoreData, SLazeKit"]),
]
)

69
README.md Normal file
View File

@ -0,0 +1,69 @@
#SLazeCoreData
<p align="center">
<a href="http://swift.org">
<img src="https://img.shields.io/badge/Swift-4.0-brightgreen.svg" alt="Language" />
</a>
<a href="https://raw.githubusercontent.com/shial4/SLazeCoreData/master/LICENSE">
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License" />
</a>
<a href="https://cocoapods.org/pods/SLazeKit">
<img src="https://img.shields.io/cocoapods/v/SLazeKit.svg" alt="CocoaPods" />
</a>
<a href="https://cocoapods.org/pods/SLazeCoreData">
<img src="https://img.shields.io/cocoapods/v/SLazeCoreData.svg" alt="CocoaPods" />
</a>
<a href="https://github.com/Carthage/Carthage">
<img src="https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
</a>
</p>
CoreData serializer extension for [Object.swift](https://github.com/shial4/SLazeKit.git)
**Models example**
[Object.swift](Tests/SLazeCoreDataTests/Models/Object.swift)
Advance
[Model.swift](Tests/SLazeCoreDataTests/Models/Model.swift)
## 🔧 Installation
**CocoaPods:**
Add the line `pod "SLazeCoreData"` to your `Podfile`
**Carthage:**
Add the line `github "shial4/SLazeCoreData"` to your `Cartfile`
**Manual:**
Clone the repo and drag the folder `SLazeCoreData` into your Xcode project.
**Swift Package Manager:**
Add the line `.package(url: "https://github.com/shial4/SLazeCoreData.git", from: "0.1.0"),` to your `Package.swift`
**Swift Package Manager in your iOS Project:**
This project demonstrates a working method for using Swift Package Manager (SPM) to manage the dependencies of an iOS project.
<a href="https://github.com/j-channings/swift-package-manager-ios">Example of how to use SPM v4 to manage iOS dependencies</a>
## 💊 Usage
Use it with SLazeKit. Under you API configuration with synchronize handler.
Run
```swift
EntityMapping.synchronize(_ obj: Any, context: NSManagedObjectContext) throws
```
## ⭐ Contributing
Be welcome to contribute to this project! :)
## ❓ Questions
Just create an issue on GitHub.
## 📝 License
This project was released under the [MIT](LICENSE) license.

19
SLazeCoreData.podspec Normal file
View File

@ -0,0 +1,19 @@
Pod::Spec.new do |s|
s.name = 'SLazeCoreData'
s.version = '0.1.0'
s.summary = 'Swift LazyKit CoreData serializer.'
s.description = <<-DESC
CoreData serializer extension for SLazeKit
DESC
s.homepage = 'https://github.com/shial4/SLazeCoreData.git'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Szymon Lorenz' => 'shial184686@gmail.com' }
s.social_media_url = 'https://www.facebook.com/SLSolutionsAU/'
s.ios.deployment_target = '10.0'
s.source = { :git => 'https://github.com/shial4/SLazeKit.git', :tag => s.version.to_s }
s.source_files = 'Sources/**/*.swift'
s.frameworks = 'CoreData'
end

View File

@ -0,0 +1,38 @@
import Foundation
import CoreData
extension NSManagedObject {
/// String entity name
final public class var entityName: String? {
return NSStringFromClass(self).components(separatedBy: ".").last
}
/// Finds `NSManagedObject` by EntityAttribute parameters.
///
/// - Parameters:
/// - context: Context on which fetch should be executed
/// - attributes: Entity attribute mapped by key - value
/// - Returns: Returns first object that meet the criteria specified by a given fetch request.
final public class func find(_ context: NSManagedObjectContext?, by attributes: EntityAttribute...) throws -> NSManagedObject? {
return try find(context, by: attributes.map {$0})
}
/// Finds `NSManagedObject` by EntityAttribute parameters.
///
/// - Parameters:
/// - context: Context on which fetch should be executed
/// - attributes: Entity attribute mapped by key - value
/// - Returns: Returns first object that meet the criteria specified by a given fetch request.
final public class func find(_ context: NSManagedObjectContext?, by attributes: [EntityAttribute]) throws -> NSManagedObject? {
guard let name = entityName else { return nil }
let fetchRequest: NSFetchRequest<NSManagedObject> = NSFetchRequest<NSManagedObject>(entityName: name)
var predicates: [NSPredicate] = []
attributes.forEach({
predicates.append(NSPredicate(format: "\($0.key) = %@", argumentArray: [$0.value]))
})
if !predicates.isEmpty {
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
}
fetchRequest.fetchLimit = 1
return try context?.fetch(fetchRequest).first
}
}

View File

@ -0,0 +1,22 @@
import Foundation
import CoreData
extension NSManagedObjectContext {
/// If the context has uncommitted changes, attempts to commit unsaved changes to registered objects to the contexts parent store.
public func commit() {
self.performAndWait {
if self.hasChanges {
do {
try self.save()
self.parent?.perform({
if self.parent?.hasChanges == true {
self.parent?.commit()
}
})
} catch {
print(error)
}
}
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,74 @@
import Foundation
import CoreData
public typealias EntityMappingCodable = EntityMapping & Codable
public typealias EntityMappingDecodable = EntityMapping & Decodable
/// Entity attribute alias for key, value tuple.
public typealias EntityAttribute = (key: String, value: Any)
/// Mapping protocol. Required for request object serialization.
public protocol EntityMapping {
static var entityType: NSManagedObject.Type { get }
var idAttributes: [EntityAttribute]? { get }
func fillObject(with model: NSManagedObject)
}
extension EntityMapping {
public static func synchronize(_ obj: Any, context: NSManagedObjectContext) throws {
if let array = obj as? [EntityMapping] {
array.forEach({_ = try? $0.map(context)})
} else {
guard let mapper = obj as? EntityMapping else { return }
_ = try mapper.map(context)
}
context.commit()
}
func map(_ context: NSManagedObjectContext) throws -> NSManagedObject? {
var model: NSManagedObject? = nil
var mapError: Error?
context.performAndWait {
do {
if let attribiutes = idAttributes {
model = try Self.entityType.find(context, by: attribiutes) ?? Self.entityType.init(context: context)
} else {
model = try findObject(context) ?? Self.entityType.init(context: context)
}
if let object = model {
fillObject(with: object)
}
} catch {
mapError = error
}
}
if let error = mapError {
throw error
}
return model
}
private func findObject(_ context: NSManagedObjectContext?) throws -> NSManagedObject? {
return try Self.entityType.find(context, by: idAttributes ?? [])
}
/// Serialized managed object from datastore by given attribiutes. To be more specific. If request is returning JSON with `EntityMapping` and given Encodable object conform to this protocol. It will be automaticaly updated in DataStore. This method featch this object.
///
/// - Parameter context: Context on which fetch should be executed
/// - Returns: Serialized object from EntityMapping model
public func serialized<T: NSManagedObject>(_ context: NSManagedObjectContext?) throws -> T? {
return (try Self.entityType.find(context, by: idAttributes ?? [])) as? T
}
}
extension Array where Element: EntityMapping {
/// Serialized managed objects from datastore by given attribiutes. To be more specific. If request is returning JSON with `EntityMapping` and given Encodable object conform to this protocol. It will be automaticaly updated in DataStore. This method featch this objects.
///
/// - Parameter context: Context on which fetch should be executed
/// - Returns: Array of serialized object from EntityMapping response type.
public func serialized<T: NSManagedObject>(_ context: NSManagedObjectContext?) throws -> [T] {
return try compactMap({ try $0.serialized(context) })
}
}

View File

@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
//! Project version number for SLazeKit.
FOUNDATION_EXPORT double SLazeCoreDataVersionNumber;
//! Project version string for SLazeKit.
FOUNDATION_EXPORT const unsigned char SLazeCoreDataVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <SLazeKit/PublicHeader.h>

6
Tests/LinuxMain.swift Normal file
View File

@ -0,0 +1,6 @@
import XCTest
@testable import SLazeKitTests
XCTMain([
testCase(SLazeKitCoreDataTests.allTests),
])

View File

@ -0,0 +1,171 @@
import Foundation
import SLazeCoreData
import SLazeKit
import CoreData
class Model: NSManagedObject {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Model> {
return NSFetchRequest<Model>(entityName: "Model")
}
@NSManaged public var id: String
@NSManaged public var value: Double
@NSManaged public var name: String?
/// Path pattern for our model API requests
public struct PathPattern {
static var model: String { return "/api/Models/:modelId" }
static var models: String { return "/api/Models" }
static var create: String { return "/api/Models/:modelId/create" }
static var delete: String { return "/api/Models/:modelId/delete" }
}
/// We are creating struct which represents Decodable response of API request
/// `EntityMappingDecodable` is required
public struct ModelResponse: EntityMappingDecodable {
var id: String
var value: Double
var name: String?
/// We need provide NSManagedObject type for our serialization.
public static var entityType: NSManagedObject.Type {
return Model.self
}
/// By providing id attributes our model are updated/created/serialized
public var idAttributes: [EntityAttribute]? {
return [
("id",id)
]
}
/// Fill CoreData object with our model response
public func fillObject(with model: NSManagedObject) {
guard let object = model as? Model else { return }
object.id = id
object.value = value
object.name = name
}
}
/// We could skip that and add `Encodable` to our ModelResponse. But to highlight it. We will create separate one for request purpose
public struct ModelRequest: Encodable {
var id: String
var value: Double
var name: String?
//Convenience initializer.
init(with model: Model) {
self.id = model.id
self.value = model.value
self.name = model.name
}
}
/// Create request. Called from SLazeKit. Response is not maped or serialized.
///
/// - Parameters:
/// - model: CoreData model used to post request with body of ModelRequest
class func create(model: Model, success: @escaping (() ->()), failure: ((_ statusCode: Int, _ error: Error?) ->())? = nil) {
/// SLazeKit request are done i background. To handle response on main thread we need to dispatch it.
let _ = SLazeKit<Default>.post(path: PathPattern.create.patternToPath(with: ["modelId":model.id]), body: ModelRequest(with: model)) { (response, error) in
guard error == nil else {
DispatchQueue.main.async { failure?(response.http?.statusCode ?? -1,error) }
return
}
DispatchQueue.main.async { success() }
}
}
/// Another request example with out mapping or serialization.
///
/// - Parameters:
/// - modelId: model id used to replace key string in path pattern.
///
/// SLazeKit request are done i background. To handle response on main thread we need to dispatch it.
class func remove(for modelId: String, success: @escaping (() ->()), failure: ((_ statusCode: Int, _ error: Error?) ->())? = nil) {
let _ = SLazeKit<Default>.delete(path: PathPattern.delete.patternToPath(with: ["modelId":modelId])) { (response, error) in
guard error == nil else {
DispatchQueue.main.async { failure?(response.http?.statusCode ?? -1,error) }
return
}
DispatchQueue.main.async { success() }
}
}
/// `[ModelResponse]` Stands as a result type. Decodable provides generic requests. if Response model beside `Decodable` comforms to `EntityMapping` as well it will be serialized.
///
/// [ModelResponse] Decodable type used to generate result type.
///
///Result is type of `[ModelResponse]` to return array of our CoreData models we need to serialize it.
///`result?.serialized` will return `[Model]`
class func getFromServer(success: @escaping (([Model]?) ->()), failure: (() ->())? = nil) {
let _ = [ModelResponse].get(path: PathPattern.models.patternToPath()) { (response, result, error) in
guard error == nil else {
failure?()
return
}
var models: [Model]? = nil
do {
models = try result?.serialized(Default.newBackgroundContext())
} catch {
print(error)
}
success(models)
}
}
/// In this example API request will decode single object of type ModelResponse instead of an array.
///
///Result of type `ModelResponse` to return CoreData model we need to serialize it.
///
///`result?.serialized` will return `Model`
class func getFromServer(for modelId: String, success: @escaping ((Model?) ->()), failure: (() ->())? = nil) {
let _ = ModelResponse.get(path: PathPattern.model.patternToPath(with: ["modelId":modelId])) { (response, result, error) in
guard error == nil else {
failure?()
return
}
var models: Model? = nil
do {
models = try result?.serialized(Default.newBackgroundContext())
} catch {
print(error)
}
success(models)
}
}
/// In this example API request will decode single object of type ModelResponse instead of an array.
///
///Result of type `ModelResponse` to return CoreData model we need to serialize it.
///
///`result?.serialized` will return `Model`
class func getFromOtherServer(for modelId: String, success: @escaping ((Model?) ->()), failure: (() ->())? = nil) {
class NoneDefault: LazeConfiguration {
static var basePath: String? { return "www.yourdomain.com" }
static var basePort: Int? { return 8765 }
static var decoder: JSONDecoder { return JSONDecoder() }
static var urlSession: URLSession { return URLSession.shared }
static func setup(_ request: URLRequest) -> URLRequest {
var request: URLRequest = request
request.setValue("Your token", forHTTPHeaderField: "X-Access-Token")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
return request
}
static func handle(_ response: HTTPURLResponse?) {
if response?.statusCode == 401 {
print("unauthorised")
}
}
public static func newBackgroundContext() -> NSManagedObjectContext? { return NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) }
}
let _ = Laze<NoneDefault,ModelResponse>.get(path: PathPattern.model.patternToPath(with: ["modelId":modelId])) { (response, result, error) in
guard error == nil else {
failure?()
return
}
var models: Model? = nil
do {
models = try result?.serialized(Default.newBackgroundContext())
} catch {
print(error)
}
success(models)
}
}
}

View File

@ -0,0 +1,56 @@
import Foundation
import SLazeCoreData
import SLazeKit
import CoreData
class Object: NSManagedObject {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Object> {
return NSFetchRequest<Object>(entityName: "Object")
}
@NSManaged public var id: String
@NSManaged public var value: Double
@NSManaged public var name: String?
/// Path pattern for our model API requests
public struct PathPattern {
static var model: String { return "/api/Objects/:modelId" }
static var models: String { return "/api/Objects" }
static var create: String { return "/api/Objects/:modelId/create" }
static var delete: String { return "/api/Objects/:modelId/delete" }
}
/// We are creating struct which represents Codable object of our API request
/// `EntityMappingCodable` is required
public struct ObjectRestful: EntityMappingCodable {
var id: String
var value: Double
var name: String?
/// We need provide NSManagedObject type for our serialization.
public static var entityType: NSManagedObject.Type {
return Object.self
}
/// By providing id attributes our model are updated/created/serialized
public var idAttributes: [EntityAttribute]? {
return [
("id",id)
]
}
/// init ObjectRestful with our Object clss
///
/// - Parameter model: model of our Object class
init(with model: Object) {
self.id = model.id
self.value = model.value
self.name = model.name
}
/// Fill CoreData object with our model response
public func fillObject(with model: NSManagedObject) {
guard let object = model as? Object else { return }
object.id = id
object.value = value
object.name = name
}
}
}

View File

@ -0,0 +1,22 @@
//
// SLazeKitCoreDataTests.swift
// SLazeCoreData
//
// Created by Shial on 11/7/18.
//
import Foundation
import Foundation
import XCTest
@testable import SLazeKitCoreData
class SLazeKitCoreDataTests: XCTestCase {
static var allTests = [
("test", test),
]
func test() {
XCTAssertTrue(true)
}
}