Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(coredata): updating coredata and creating initalizers #395

Merged
merged 8 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ extension HomeRecommendationCellViewModel: RecommendationCellViewModel {

var timeToRead: String? {
guard let timeToRead = recommendation.item?.timeToRead,
timeToRead > 0 else {
timeToRead.intValue > 0 else {
return nil
}

Expand Down
32 changes: 12 additions & 20 deletions PocketKit/Sources/PocketKit/Home/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -593,36 +593,28 @@ extension HomeViewModel {
var contexts: [Context] = []

// SlateLineup Context
if let id = slateLineup.remoteID,
let requestID = slateLineup.requestID,
let experimentID = slateLineup.experimentID {
let context = SlateLineupContext(
id: id,
requestID: requestID,
experiment: experimentID
)
contexts.append(context)
}
let context = SlateLineupContext(
id: slateLineup.remoteID,
requestID: slateLineup.requestID,
experiment: slateLineup.experimentID
)
contexts.append(context)

// Slate context
if let slateID = slate.remoteID,
let requestID = slate.requestID,
let experimentID = slate.experimentID,
let slateIndex = slateLineup.slates?.index(of: slate) {
if let slateIndex = slateLineup.slates?.index(of: slate) {
let slateContext = SlateContext(
id: slateID,
requestID: requestID,
experiment: experimentID,
id: slate.remoteID,
requestID: slate.requestID,
experiment: slate.experimentID,
index: UIIndex(slateIndex)
)
contexts.append(slateContext)
}

// Recommendation context
if let recommendationID = recommendation.remoteID,
let recommendationIndex = slate.recommendations?.index(of: recommendation) {
if let recommendationIndex = slate.recommendations?.index(of: recommendation) {
let recommendationContext = RecommendationContext(
id: recommendationID,
id: recommendation.remoteID,
index: UIIndex(recommendationIndex)
)
contexts.append(recommendationContext)
Expand Down
36 changes: 13 additions & 23 deletions PocketKit/Sources/PocketKit/Home/SlateDetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,8 @@ class SlateDetailViewModel {
}

func refresh(_ completion: @escaping () -> Void) {
guard let slateID = slate.remoteID else {
return
}

Task {
try await source.fetchSlate(slateID)
try await source.fetchSlate(slate.remoteID)
completion()
}
}
Expand Down Expand Up @@ -201,25 +197,19 @@ extension SlateDetailViewModel {

var contexts: [Context] = []

if let remoteID = slate.remoteID,
let requestID = slate.requestID,
let experimentID = slate.experimentID {
let slateContext = SlateContext(
id: remoteID,
requestID: requestID,
experiment: experimentID,
index: UIIndex(0)
)
contexts.append(slateContext)
}
let slateContext = SlateContext(
id: slate.remoteID,
requestID: slate.requestID,
experiment: slate.experimentID,
index: UIIndex(0)
)
contexts.append(slateContext)

if let remoteID = recommendation.remoteID {
let recommendationContext = RecommendationContext(
id: remoteID,
index: UIIndex(indexPath.item)
)
contexts.append(recommendationContext)
}
let recommendationContext = RecommendationContext(
id: recommendation.remoteID,
index: UIIndex(indexPath.item)
)
contexts.append(recommendationContext)

return contexts + [
ContentContext(url: recommendationURL),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ extension SavedItem: ItemsListItem {
}

var timeToRead: Int? {
item.flatMap { Int($0.timeToRead) }
item?.timeToRead?.intValue
}

var domainMetadata: ItemsListItemDomainMetadata? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
import Foundation
import CoreData

@objc(Item)
public class Item: NSManagedObject {
@available(*, unavailable)
public init() {
fatalError()
}

@available(*, unavailable)
public init(context: NSManagedObjectContext) {
fatalError()
}

internal override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}

public init(context: NSManagedObjectContext,
givenURL: URL,
remoteID: String) {
let entity = NSEntityDescription.entity(forEntityName: "Item", in: context)!
super.init(entity: entity, insertInto: context)
self.givenURL = givenURL
self.remoteID = remoteID
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

import Foundation
import CoreData

extension Item {
@nonobjc
public class func fetchRequest() -> NSFetchRequest<Item> {
return NSFetchRequest<Item>(entityName: "Item")
}

@NSManaged public var article: Article?
@NSManaged public var datePublished: Date?
@NSManaged public var domain: String?
@NSManaged public var excerpt: String?
@NSManaged public var givenURL: URL
@NSManaged public var imageness: String?
@NSManaged public var isArticle: Bool
@NSManaged public var language: String?
@NSManaged public var remoteID: String
@NSManaged public var resolvedURL: URL?
@NSManaged public var timeToRead: NSNumber?
@NSManaged public var title: String?
@NSManaged public var topImageURL: URL?
@NSManaged public var videoness: String?
@NSManaged public var authors: NSOrderedSet?
@NSManaged public var domainMetadata: DomainMetadata?
@NSManaged public var images: NSOrderedSet?
@NSManaged public var recommendation: Recommendation?
@NSManaged public var savedItem: SavedItem?
@NSManaged public var syndicatedArticle: SyndicatedArticle?
}

// MARK: Generated accessors for authors
extension Item {
@objc(insertObject:inAuthorsAtIndex:)
@NSManaged public func insertIntoAuthors(_ value: Author, at idx: Int)

@objc(removeObjectFromAuthorsAtIndex:)
@NSManaged public func removeFromAuthors(at idx: Int)

@objc(insertAuthors:atIndexes:)
@NSManaged public func insertIntoAuthors(_ values: [Author], at indexes: NSIndexSet)

@objc(removeAuthorsAtIndexes:)
@NSManaged public func removeFromAuthors(at indexes: NSIndexSet)

@objc(replaceObjectInAuthorsAtIndex:withObject:)
@NSManaged public func replaceAuthors(at idx: Int, with value: Author)

@objc(replaceAuthorsAtIndexes:withAuthors:)
@NSManaged public func replaceAuthors(at indexes: NSIndexSet, with values: [Author])

@objc(addAuthorsObject:)
@NSManaged public func addToAuthors(_ value: Author)

@objc(removeAuthorsObject:)
@NSManaged public func removeFromAuthors(_ value: Author)

@objc(addAuthors:)
@NSManaged public func addToAuthors(_ values: NSOrderedSet)

@objc(removeAuthors:)
@NSManaged public func removeFromAuthors(_ values: NSOrderedSet)
}

// MARK: Generated accessors for images
extension Item {
@objc(insertObject:inImagesAtIndex:)
@NSManaged public func insertIntoImages(_ value: Image, at idx: Int)

@objc(removeObjectFromImagesAtIndex:)
@NSManaged public func removeFromImages(at idx: Int)

@objc(insertImages:atIndexes:)
@NSManaged public func insertIntoImages(_ values: [Image], at indexes: NSIndexSet)

@objc(removeImagesAtIndexes:)
@NSManaged public func removeFromImages(at indexes: NSIndexSet)

@objc(replaceObjectInImagesAtIndex:withObject:)
@NSManaged public func replaceImages(at idx: Int, with value: Image)

@objc(replaceImagesAtIndexes:withImages:)
@NSManaged public func replaceImages(at indexes: NSIndexSet, with values: [Image])

@objc(addImagesObject:)
@NSManaged public func addToImages(_ value: Image)

@objc(removeImagesObject:)
@NSManaged public func removeFromImages(_ value: Image)

@objc(addImages:)
@NSManaged public func addToImages(_ values: NSOrderedSet)

@objc(removeImages:)
@NSManaged public func removeFromImages(_ values: NSOrderedSet)
}
19 changes: 19 additions & 0 deletions PocketKit/Sources/Sync/CoreDataInitializers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Core Data Initializers

Our CoreData models have some properties that are defined as non-optional (non-nil). However Swift and CoreData do not behave how one would expect. Defining a non-optional field in CoreData will still produce a generated optional in Swift. This then gets even trickier because `@NSManaged` annotations make swift ignore non-nil rules.

The workaround for this is to define our own initlizers for all CoreData models and not allow the default initializers to compile. This directory contains all the initializers as extensions to our CoreData models to allow non-nil values so we can remove uncessary guard checks in our code.

These articles explain this problem well:
* Solution: https://tanaschita.com/20221114-how-to-handle-non-optional-core-data-values-in-swift/
* Other reference: https://www.atomicbird.com/blog/clash-of-the-optionals/


## Generating New Classes

To generate new classes from the model follow the following steps:

1. Select the PocketModel in the Project sidebar
2. From the Xcode menu bar, choose Editor > Create NSManagedObject Subclass.
3. Select your data model, then the appropriate entity, and choose where to save the files. Xcode places both a class and a properties file into your project.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

import Foundation
import CoreData

@objc(Recommendation)
public class Recommendation: NSManagedObject {
@available(*, unavailable)
public init() {
fatalError()
}

@available(*, unavailable)
public init(context: NSManagedObjectContext) {
fatalError()
}

internal override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}

public init(
context: NSManagedObjectContext,
remoteID: String
) {
let entity = NSEntityDescription.entity(forEntityName: "Recommendation", in: context)!
super.init(entity: entity, insertInto: context)
self.remoteID = remoteID
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
import Foundation
import CoreData

extension Recommendation {
@nonobjc
public class func fetchRequest() -> NSFetchRequest<Recommendation> {
return NSFetchRequest<Recommendation>(entityName: "Recommendation")
}

@NSManaged public var excerpt: String?
@NSManaged public var imageURL: URL?
@NSManaged public var remoteID: String
@NSManaged public var title: String?
@NSManaged public var item: Item?
@NSManaged public var slate: Slate?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
import Foundation
import CoreData

@objc(SavedItem)
public class SavedItem: NSManagedObject {
@available(*, unavailable)
public init() {
fatalError()
}

@available(*, unavailable)
public init(context: NSManagedObjectContext) {
fatalError()
}

internal override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}

public init(
context: NSManagedObjectContext,
url: URL,
remoteID: String? = nil
) {
let entity = NSEntityDescription.entity(forEntityName: "SavedItem", in: context)!
super.init(entity: entity, insertInto: context)
self.url = url
self.remoteID = remoteID
}
}
Loading