Skip to content

Fix WordPressData runtime issues #24494

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

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 @@ -55,7 +55,7 @@
<fetchIndexElement property="blogs" type="Binary" order="ascending"/>
</fetchIndex>
</entity>
<entity name="AccountSettings" representedClassName="ManagedAccountSettings" syncable="YES">
<entity name="AccountSettings" representedClassName=".ManagedAccountSettings" syncable="YES">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swift-only types like ManagedAccountSettings are not present in the global namespace. You have to specify the module. . means "current module".

public class ManagedAccountSettings: NSManagedObject 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean the migration from version 154 (which has an incorrect module) to 155 is broken?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make this change (adding .) to all modal files?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, my mistake was not making the leap in understanding that Objective-C types are handled differently from Swift ones.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean the migration from version 154 (which has an incorrect module) to 155 is broken?

Maybe a unit test could clear this up? #24496

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crazytonyli you are onto it! ☝️

In version 154, we crash upon trying to init BlogAuthor with

Could not cast value of type 'NSManagedObject_BlogAuthor_' (0x6000012d5aa0) to 'WordPressData.BlogAuthor' (0x1616aea20).
image

The solution is to apply the same representedClassName change in 154:

diff --git a/Sources/WordPressData/Resources/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents b/Sources/WordPressData/Resources/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents
index 2267cb2313..292045b17d 100644
--- a/Sources/WordPressData/Resources/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents	
+++ b/Sources/WordPressData/Resources/WordPress.xcdatamodeld/WordPress 154.xcdatamodel/contents	
@@ -204,7 +204,7 @@
         </fetchIndex>
         <userInfo/>
     </entity>
-    <entity name="BlogAuthor" representedClassName="WordPress.BlogAuthor" syncable="YES">
+    <entity name="BlogAuthor" representedClassName=".BlogAuthor" syncable="YES">
         <attribute name="avatarURL" optional="YES" attributeType="String" syncable="YES"/>
         <attribute name="deletedFromBlog" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
         <attribute name="displayName" optional="YES" attributeType="String" syncable="YES"/>
image

See also CI from #24496

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed 2a17e73 in my PR that updates all the models. I used two bash commands, so I might have messed up something. Still, the test above now passes. Progress.

Copy link
Contributor Author

@kean kean Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first thing the app does is migrate from the production model (154). It never loads the represented entities in memory. It uses NSManagedObject and KVC for migration. It's impossible to retroactively change the models and there is also no need to. The app has representedClassName values in the previous models with class name that no longer even exist in the codebase – it's what the migration is for.

<attribute name="aboutMe" attributeType="String" syncable="YES"/>
<attribute name="blockEmailNotifications" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/>
<attribute name="displayName" attributeType="String" syncable="YES"/>
Expand Down Expand Up @@ -206,7 +206,7 @@
</fetchIndex>
<userInfo/>
</entity>
<entity name="BlogAuthor" representedClassName="BlogAuthor" syncable="YES">
<entity name="BlogAuthor" representedClassName=".BlogAuthor" syncable="YES">
<attribute name="avatarURL" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="deletedFromBlog" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="displayName" optional="YES" attributeType="String" syncable="YES"/>
Expand All @@ -217,7 +217,7 @@
<attribute name="username" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="authors" inverseEntity="Blog" syncable="YES"/>
</entity>
<entity name="BloggingPrompt" representedClassName="BloggingPrompt" syncable="YES">
<entity name="BloggingPrompt" representedClassName=".BloggingPrompt" syncable="YES">
<attribute name="additionalPostTags" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="[String]" syncable="YES"/>
<attribute name="answerCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="answered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
Expand All @@ -228,15 +228,15 @@
<attribute name="siteID" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="text" attributeType="String" defaultValueString="" syncable="YES"/>
</entity>
<entity name="BloggingPromptSettings" representedClassName="BloggingPromptSettings" syncable="YES">
<entity name="BloggingPromptSettings" representedClassName=".BloggingPromptSettings" syncable="YES">
<attribute name="isPotentialBloggingSite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="promptCardEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="promptRemindersEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="reminderTime" attributeType="String" defaultValueString="" syncable="YES"/>
<attribute name="siteID" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<relationship name="reminderDays" maxCount="1" deletionRule="Cascade" destinationEntity="BloggingPromptSettingsReminderDays" inverseName="settings" inverseEntity="BloggingPromptSettingsReminderDays" syncable="YES"/>
</entity>
<entity name="BloggingPromptSettingsReminderDays" representedClassName="BloggingPromptSettingsReminderDays" syncable="YES">
<entity name="BloggingPromptSettingsReminderDays" representedClassName=".BloggingPromptSettingsReminderDays" syncable="YES">
<attribute name="friday" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="monday" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="saturday" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
Expand All @@ -246,7 +246,7 @@
<attribute name="wednesday" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<relationship name="settings" maxCount="1" deletionRule="Cascade" destinationEntity="BloggingPromptSettings" inverseName="reminderDays" inverseEntity="BloggingPromptSettings" syncable="YES"/>
</entity>
<entity name="BlogSettings" representedClassName="BlogSettings" syncable="YES">
<entity name="BlogSettings" representedClassName=".BlogSettings" syncable="YES">
<attribute name="ampEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="ampSupported" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="commentsAllowed" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
Expand Down Expand Up @@ -347,19 +347,19 @@
</fetchIndex>
<userInfo/>
</entity>
<entity name="DiffAbstractValue" representedClassName="DiffAbstractValue" isAbstract="YES" syncable="YES">
<entity name="DiffAbstractValue" representedClassName=".DiffAbstractValue" isAbstract="YES" syncable="YES">
<attribute name="diffOperation" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="diffType" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="index" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="value" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="DiffContentValue" representedClassName="DiffContentValue" parentEntity="DiffAbstractValue" syncable="YES">
<entity name="DiffContentValue" representedClassName=".DiffContentValue" parentEntity="DiffAbstractValue" syncable="YES">
<relationship name="revisionDiff" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RevisionDiff" inverseName="contentDiffs" inverseEntity="RevisionDiff" syncable="YES"/>
</entity>
<entity name="DiffTitleValue" representedClassName="DiffTitleValue" parentEntity="DiffAbstractValue" syncable="YES">
<entity name="DiffTitleValue" representedClassName=".DiffTitleValue" parentEntity="DiffAbstractValue" syncable="YES">
<relationship name="revisionDiff" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RevisionDiff" inverseName="titleDiffs" inverseEntity="RevisionDiff" syncable="YES"/>
</entity>
<entity name="Domain" representedClassName="ManagedDomain" syncable="YES">
<entity name="Domain" representedClassName=".ManagedDomain" syncable="YES">
<attribute name="autoRenewalDate" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="autoRenewing" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="domainName" optional="YES" attributeType="String" syncable="YES"/>
Expand Down Expand Up @@ -508,7 +508,7 @@
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="categories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PageTemplateCategory" inverseName="layouts" inverseEntity="PageTemplateCategory" syncable="YES"/>
</entity>
<entity name="Person" representedClassName="ManagedPerson" syncable="YES">
<entity name="Person" representedClassName=".ManagedPerson" syncable="YES">
<attribute name="avatarURL" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="creationDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="displayName" attributeType="String" syncable="YES"/>
Expand All @@ -522,7 +522,7 @@
<attribute name="userID" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
<attribute name="username" attributeType="String" syncable="YES"/>
</entity>
<entity name="Plan" representedClassName="Plan" syncable="YES">
<entity name="Plan" representedClassName=".Plan" syncable="YES">
<attribute name="features" attributeType="String" syncable="YES"/>
<attribute name="groups" attributeType="String" syncable="YES"/>
<attribute name="icon" attributeType="String" syncable="YES"/>
Expand All @@ -536,12 +536,12 @@
<attribute name="supportPriority" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="tagline" attributeType="String" syncable="YES"/>
</entity>
<entity name="PlanFeature" representedClassName="PlanFeature" syncable="YES">
<entity name="PlanFeature" representedClassName=".PlanFeature" syncable="YES">
<attribute name="slug" attributeType="String" syncable="YES"/>
<attribute name="summary" attributeType="String" syncable="YES"/>
<attribute name="title" attributeType="String" syncable="YES"/>
</entity>
<entity name="PlanGroup" representedClassName="PlanGroup" syncable="YES">
<entity name="PlanGroup" representedClassName=".PlanGroup" syncable="YES">
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="slug" attributeType="String" syncable="YES"/>
Expand Down Expand Up @@ -579,7 +579,7 @@
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="postTypes" inverseEntity="Blog" syncable="YES"/>
</entity>
<entity name="PublicizeConnection" representedClassName="PublicizeConnection" syncable="YES">
<entity name="PublicizeConnection" representedClassName=".PublicizeConnection" syncable="YES">
<attribute name="connectionID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="dateExpires" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="dateIssued" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
Expand Down Expand Up @@ -607,7 +607,7 @@
<attribute name="toBePublicizedCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<relationship name="blog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Blog" inverseName="publicizeInfo" inverseEntity="Blog"/>
</entity>
<entity name="PublicizeService" representedClassName="PublicizeService" syncable="YES">
<entity name="PublicizeService" representedClassName=".PublicizeService" syncable="YES">
<attribute name="connectURL" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="detail" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalUsersOnly" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
Expand Down Expand Up @@ -644,7 +644,7 @@
<fetchIndexElement property="path" type="Binary" order="ascending"/>
</fetchIndex>
</entity>
<entity name="ReaderCard" representedClassName="ReaderCard" syncable="YES">
<entity name="ReaderCard" representedClassName=".ReaderCard" syncable="YES">
<attribute name="sortRank" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
<relationship name="post" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReaderPost" inverseName="card" inverseEntity="ReaderPost" syncable="YES"/>
<relationship name="sites" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ReaderSiteTopic" inverseName="cards" inverseEntity="ReaderSiteTopic" syncable="YES"/>
Expand Down Expand Up @@ -790,7 +790,7 @@
<attribute name="organizationID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="slug" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="Revision" representedClassName="Revision" syncable="YES">
<entity name="Revision" representedClassName=".Revision" syncable="YES">
<attribute name="postAuthorId" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="postContent" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="postDateGmt" optional="YES" attributeType="String" syncable="YES"/>
Expand All @@ -802,7 +802,7 @@
<attribute name="siteId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<relationship name="diff" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="RevisionDiff" inverseName="revision" inverseEntity="RevisionDiff" syncable="YES"/>
</entity>
<entity name="RevisionDiff" representedClassName="RevisionDiff" syncable="YES">
<entity name="RevisionDiff" representedClassName=".RevisionDiff" syncable="YES">
<attribute name="fromRevisionId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="toRevisionId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="totalAdditions" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
Expand All @@ -811,7 +811,7 @@
<relationship name="revision" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Revision" inverseName="diff" inverseEntity="Revision" syncable="YES"/>
<relationship name="titleDiffs" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="DiffTitleValue" inverseName="revisionDiff" inverseEntity="DiffTitleValue" syncable="YES"/>
</entity>
<entity name="Role" representedClassName="Role" syncable="YES">
<entity name="Role" representedClassName=".Role" syncable="YES">
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="slug" attributeType="String" syncable="YES"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import Foundation
import WordPressKit

/// A collection of global variables and singletons that the app wants access to.
///
struct AppEnvironment {
/// - warning: Soft-deprecated.
public struct AppEnvironment {

// MARK: - Globals

/// A type to create derived context, save context, etc...
let contextManager: CoreDataStackSwift
public let contextManager: CoreDataStackSwift

/// The base url to use for WP.com api requests
let wordPressComApiBase: URL
public let wordPressComApiBase: URL

/// The mainContext that has concurrency type NSMainQueueConcurrencyType and should be used
/// for UI elements and fetched results controllers.
var mainContext: NSManagedObjectContext {
public var mainContext: NSManagedObjectContext {
return contextManager.mainContext
}

// MARK: - Static current environment implementation

/// The current environment. Use this to access the app globals.
///
static private(set) var current = AppEnvironment()
public static private(set) var current = AppEnvironment()

// MARK: - Initialization

Expand All @@ -40,7 +39,7 @@ extension AppEnvironment {
/// Creates a new Environment, changing just a subset of the current global dependencies.
///
@discardableResult
static func replaceEnvironment(
public static func replaceEnvironment(
contextManager: CoreDataStackSwift = AppEnvironment.current.contextManager,
wordPressComApiBase: URL = AppEnvironment.current.wordPressComApiBase) -> AppEnvironment {

Expand Down
9 changes: 9 additions & 0 deletions Sources/WordPressData/Swift/Bundle+WordPressData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

extension Bundle {
@objc public class var wordPressData: Bundle {
Bundle(for: BundleToken.self)
}
}

private final class BundleToken {}
4 changes: 2 additions & 2 deletions Sources/WordPressData/Swift/ContextManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift {

DDLogWarn("Migration required for persistent store.")

guard let modelFileURL = Bundle(for: ContextManager.self).url(forResource: "WordPress", withExtension: "momd") else {
guard let modelFileURL = Bundle.wordPressData.url(forResource: "WordPress", withExtension: "momd") else {
fatalError("Can't find WordPress.momd")
}

Expand Down Expand Up @@ -222,7 +222,7 @@ private extension ContextManager {

private extension ContextManager {
static func createPersistentContainer(storeURL: URL, modelName: String) -> NSPersistentContainer {
guard var modelFileURL = Bundle(for: ContextManager.self).url(forResource: "WordPress", withExtension: "momd") else {
guard var modelFileURL = Bundle.wordPressData.url(forResource: "WordPress", withExtension: "momd") else {
fatalError("Can't find WordPress.momd")
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/WordPressData/Swift/CoreDataHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ public extension CoreDataStack {
}

private func migrateDatabaseIfNecessary(at databaseLocation: URL) throws {
guard let modelFileURL = Bundle(for: ContextManager.self).url(forResource: "WordPress", withExtension: "momd"),
guard let modelFileURL = Bundle.wordPressData.url(forResource: "WordPress", withExtension: "momd"),
let objectModel = NSManagedObjectModel(contentsOf: modelFileURL) else {
return
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/WordPressData/Swift/CoreDataIterativeMigrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ private extension CoreDataIterativeMigrator {
}

static func model(for metadata: [String: Any]) throws -> NSManagedObjectModel? {
let bundle = Bundle(for: ContextManager.self)
let bundle = Bundle.wordPressData
guard let sourceModel = NSManagedObjectModel.mergedModel(from: [bundle], forStoreMetadata: metadata) else {
let description = "Failed to find source model for metadata: \(metadata)"
throw error(with: .noSourceModelForMetadata, description: description)
Expand All @@ -287,7 +287,7 @@ private extension CoreDataIterativeMigrator {
}

static func urlForModel(name: String, in directory: String?) -> URL? {
let bundle = Bundle(for: ContextManager.self)
let bundle = Bundle.wordPressData
var url = bundle.url(forResource: name, withExtension: "mom", subdirectory: directory)

if url != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import WordPressKit
/// Furthermore, sites eligible for unlimited sharing will still return a `PublicizeInfo` along with its sharing
/// limitations, but the numbers should be ignored (at least for now).
///
@objc public class PublicizeInfo: NSManagedObject {
@objc(PublicizeInfo)
public class PublicizeInfo: NSManagedObject {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following annotation implicitly adds a module name to the Objective-C class name.

@objc open class PublicizeInfo

(lldb) po NSStringFromClass(PublicizeInfo.self)
"WordPressData.PublicizeInfo"

This breaks most of our code that works with entities (it uses classNameWithoutNamespaces).

The fix is to manually set the global name:

@objc(PublicizeInfo)
open class PublicizeInfo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL.


public var sharingLimit: SharingLimit {
SharingLimit(remaining: Int(sharesRemaining), limit: Int(shareLimit))
Expand Down
3 changes: 2 additions & 1 deletion Sources/WordPressData/Swift/ReaderAbstractTopic.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Foundation
import CoreData

@objc open class ReaderAbstractTopic: NSManagedObject {
@objc(ReaderAbstractTopic)
open class ReaderAbstractTopic: NSManagedObject {
// Relations
@NSManaged open var posts: [ReaderPost]

Expand Down
3 changes: 2 additions & 1 deletion Sources/WordPressData/Swift/ReaderCrossPostMeta.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Foundation
import CoreData

@objc open class ReaderCrossPostMeta: NSManagedObject {
@objc(ReaderCrossPostMeta)
open class ReaderCrossPostMeta: NSManagedObject {
// Relations
@NSManaged open var post: ReaderPost

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

@objc open class ReaderDefaultTopic: ReaderAbstractTopic {
@objc(ReaderDefaultTopic)
open class ReaderDefaultTopic: ReaderAbstractTopic {
override open class var TopicType: String {
return "default"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

@objc open class ReaderListTopic: ReaderAbstractTopic {
@objc(ReaderListTopic)
open class ReaderListTopic: ReaderAbstractTopic {
@NSManaged open var isOwner: Bool
@NSManaged open var isPublic: Bool
@NSManaged open var listDescription: String
Expand Down
Loading