Using Alecrim Core Data with Swift 3 - swift

I'm having trouble with Alecrim Core Data in Xcode 8 Beta. The DataContext and DataContextOptions seem to be missing from the Swift_3 branch. Grasping at straws, I just updated the files from the master branch to Swift3 syntax hoping the functionality hadn't changed too much. But when it tries to read data I get an error message "Cannot fetch without an NSManagedObjectContext in scope".
I've done as much triage as I can think of. Breakpoints set at the spot that creates an NSManagedObjectContext and I can see it. The place that creates the fetchRequest has been updated with the new NSFetchReqeust syntax (although I see no sign of a MOC there).
Here's my link into the Alecrim lib DataContext:
let dataContext = DataContext()
extension DataContext
{
public var collections: Table<CoreDataCollection> { return Table<CoreDataCollection>(context: self) }
public var expressions: Table<CoreDataExpression> { return Table<CoreDataExpression>(context: self) }
public var fileAssets: Table<CoreDataFileAsset> { return Table<CoreDataFileAsset>(context: self) }
public var purchases: Table<CoreDataPurchase> { return Table<CoreDataPurchase>(context: self) }
public var reeConfigs: Table<CoreDataReeConfig> { return Table<CoreDataReeConfig>(context: self) }
public var stickers: Table<CoreDataSticker> { return Table<CoreDataSticker>(context: self) }
}
And the part that attempts to get data:
for item in dataContext.reeConfigs {
let reeConfigVO = ReeConfigVO()
reeConfigVO.initFromCoreData(item)
items.append(reeConfigVO)
}
I'm not sure if this will be helpful but the part of Alecrim that's tossing the error:
// MARK: - GenericQueryable
extension TableProtocol {
public final func execute() -> [Self.Element] {
do {
return try self.toFetchRequest().execute() as [Self.Element]
}
catch let error {
AlecrimCoreDataError.handleError(error)
}
}
}
// MARK: - CoreDataQueryable
extension TableProtocol {
public final func toFetchRequest<ResultType: NSFetchRequestResult>() -> NSFetchRequest<ResultType> {
let fetchRequest = NSFetchRequest<ResultType>()
fetchRequest.entity = self.entityDescription
fetchRequest.fetchOffset = self.offset
fetchRequest.fetchLimit = self.limit
fetchRequest.fetchBatchSize = (self.limit > 0 && self.batchSize > self.limit ? 0 : self.batchSize)
fetchRequest.predicate = self.predicate
fetchRequest.sortDescriptors = self.sortDescriptors
return fetchRequest
}
}
Does anyone have experience with Alecrim in Swift 3 that can help figure out what's going wrong?
Thanks,
Mike

I found the answer I was looking for. I'm not sure this is what Alecrim wants to be done because, as I mentioned, the swift_3 branch doesn't even contain DataContext or DataContextOptions. However... if you have brought those files in from the main branch and updated them to swift 3 as I have and you have problems with the error "Cannot fetch without an MSManagedObjectContext in scope" here's how to fix it in the "TableProtocol.swift" file:
extension TableProtocol {
public final func execute() -> [Self.Element] {
do {
return try self.context.fetch(self.toFetchRequest()) as! [Self.Element]
//return try self.toFetchRequest().execute() as [Self.Element]
}
catch let error {
AlecrimCoreDataError.handleError(error)
}
}
}
self.context is the NSManagedObjectContext (or a derived type i.e. our DataContext) that is needed for this to work. The as! [Self.Element] is crucial because Swift uses it to infer the RestultType in the function that builds the NSFetchRequest
Hope this helps.

Related

Swift: HEREMaps geocoding sharedSdkEngineNotInstantiated error being thrown

So I'm using the HERE iOS SDK. For some reason, the try block isn't working, I keep getting the error printing out that is 'sharedSdkEngineNotInstantiated'. The getCoordinates function is what I'm trying to test after instantiation--I included it so that you guys aren't left guessing what the end goal is. Thanks for any and all help!
class functions: NSObject, ObservableObject {
var searchEngine: SearchEngine?
override init() {
do {
try searchEngine = SearchEngine()
} catch let engineInstantiationError {
print("Failed to make the search engine. The cause was \(engineInstantiationError)")
}
}
func getCoordinates5(from address: String) {
guard searchEngine != nil else {
return
}
let searchOptions = SearchOptions(maxItems: 10)
let query = TextQuery(address, near: GeoCoordinates(latitude: 40.7128, longitude: 74.0060))
_ = searchEngine!.search(textQuery: query, options: searchOptions) { (searchError, searchResultItems) in
guard searchResultItems != nil else {
return
}
for index in 0..<searchResultItems!.count {
let number = index + 1
print("Location \(number): \(searchResultItems![index])")
}
}
}
}
As explained here, you should instantiate the sdk manually since probably when your code is executed you don't have any map loaded yet (showing the map will automatically instantiate the sdk). So all you need is
// We must explicitly initialize the HERE SDK if no MapView is present.
do {
try SDKInitializer.initializeIfNecessary()
} catch {
fatalError("Failed to initialize HERE SDK")
}
before instantiating the SearchEngine

Observing realm changes are not firing

In my application I have a custom RealmDatabase class. It initializes the Realm database for me.
public class RealmDatabase: Database {
let realm: Realm
//
// I also provide a shared() singleton
//
public init(fileURL: URL) {
let config = Realm.Configuration(fileURL: fileURL)
Realm.Configuration.defaultConfiguration = config
realm = try! Realm()
}
public func observe<T>(_ block: #escaping ((T) -> Void), for object: T.Type) where T : Storable {
realm.objects(object).observe { (changes) in
print("Changes: ", changes)
}
}
}
Now, I also wrote a class called SyncEngine so that I can start syncing with CloudKit. The class looks like this:
public class SyncEngine: NSObject {
private let database: Database
public init(database: Database) {
self.database = database
super.init()
}
public func start() {
database.observe({ restaurant in
print("changes!")
}, for: Restaurant.self)
}
}
Now, in my AppDelegate I do the following:
let database = RealmDatabase.shared()
let syncEngine = SyncEngine(database: database)
syncEngine.start()
The problem, though, is that my observer is never fired and print("Changes: ", changes) is never printed to the console. I don't know what I'm doing wrong, though. Any ideas?
You're discarding the observation as you create it. To solve this, you need to retain the NotificationToken returned by observe.
var token: NotificationToken?
func start() {
token = database.observe { changes in ... }
}

How to fix "Cannot invoke 'createEvent' with an argument list of type '(eventStore: EventStoring)'"

I'm working with EventKit's EKEventStore and I want to mock it and also EKEvent.
But I don't know how to abstract EKEvent's init(eventStore: EKEventStore) and other methods properly.
protocol EventStoring {
associated type Event: EventStoreEvent where Event.MatchingEventStore == Self
func save(_ event: Event, span: EKSpan, commit: Bool) throws
// Other methods of EKEventStore I use
}
extension EKEventStore: EventStoring {
typealias Event = EKEvent
}
protocol EventStoreEvent {
associatedtype MatchingEventStore: EventStoring
static func createEvent(eventStore: MatchingEventStore) -> Self
}
extension EKEvent: EventStoreEvent {
typealias MatchingEventStore = EKEventStore
static func createEvent(eventStore: MatchingEventStore) -> Self {
return EKEvent(eventStore: eventStore) as! Self
}
}
Here the errors are: "'Self' is only available in a protocol or as the result of a method in a class; did you mean 'EKEvent'?"
and: "Cannot convert return expression of type 'EKEvent' to return type 'Self'"
class GenericEventManger<StoreEvent: EventStoreEvent> {
var store: EventStoring
required init(with eventStore: EventStoring) {
self.store = eventStore
}
func createEvent() -> StoreEvent {
let eventStoreEvent: EventStoreEvent = StoreEvent.createEvent(eventStore: store)
// Then some code where I configure the event...
try store.save(eventStoreEvent, span: .thisEvent, commit: true)
}
}
On the seventh last line the error is: Cannot invoke 'createEvent' with an argument list of type '(eventStore: EventStoring)'
And on the third last it is: Cannot invoke 'save' with an argument list of type '(StoreEvent, span: EKSpan, commit: Bool)'
Update Since I adapted the recommendation from Dan another problem of the same kind arose in my implementation, so I updated my question
I guess with the Help of Dan I figured out a solution for two of the Problems so far, but I have not testet it thoroughly yet:
First I changed the type of the store property of GenericStoreManager like Dan recommended
class GenericStoreManger<StoreEvent: EventStoreEvent> {
var store: StoreEvent.MatchingEventStore
func createEvent() -> StoreEvent {
let eventStoreEvent: EventStoreEvent = StoreEvent.createEvent(eventStore: store)
// Then some code where I configure the event...
try store.save(eventStoreEvent as! StoreEvent.MatchingEventStore.Event, span: .thisEvent, commit: true)
}
...
}
and than I changed how I get my return value in extension EKEvent: EventStoreEvent so it would work also whit subclasses of EKEvent
extension EKEvent: EventStoreEvent {
typealias MatchingEventStore = EKEventStore
static func createEvent(eventStore: MatchingEventStore) -> Self {
return self.init(eventStore: eventStore)
}
}

Swift: Realm array empty after writing object

First of all, i'm new to Swift, so probably I have made some stupid mistake, but I have tried everything and I can't figure it out why this is happening.
So let me start. List property inside realm object is not being stored. That is first problem and second problem is that object I'm calling after adding object to realm loses it property value.
Here is the picture, so you guys can understand better what I'm saying
And now this is output:
PRODUCT BEFORE UPDATE: 34
Added new object
PRODUCT AFTER UPDATE:
I will show how my DBManager class looks:
import Foundation
import RealmSwift
class DBManager {
private var database:Realm
static let sharedInstance = DBManager()
private init() {
database = try! Realm()
}
func getDataFromDB() -> Results<Product> {
let results: Results<Product> = database.objects(Product.self)
return results
}
func getSingleDataFromDB(primaryKey: String) -> Product {
let product: Product = database.object(ofType: Product.self, forPrimaryKey: primaryKey)!
return product
}
func addData(object: Product) {
try! database.write {
database.add(object)
print("Added new object")
}
}
func updateData(object: Product) {
try! database.write {
database.add(object, update: true)
}
}
func deleteAllFromDatabase() {
try! database.write {
database.deleteAll()
}
}
func deleteFromDb(object: Product) {
try! database.write {
database.delete(object)
}
}
}
So If someone is seeing something that I'm not seeing, please let me know because I have lost many hours finding solution for this problem.
Thank you in advance

Blank constant when trying to get list of classes that have adopted a Protocol

I am trying to get a list of classes that have adopted a certain Protocol Migration: Preparation, and then to append those classes into an array. Here is the function in question:
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
var migrationsList = [Preparation.Type]()
var count = UInt32(0)
let classList = objc_copyClassList(&count)!
for i in 0..<Int(count) {
let classInfo = ClassInfo(classList[i])!
if let cls = classInfo.classObject as? Migration.Type {
migrationsList.append(cls)
print(cls.description)
}
}
return migrationsList
}
}
In principle all that should work, but when debugging I note that the classInfo variable is referring to each class in the iteration, but when assigning and casting in the if let as line, the constant cls is always blank - neither a value/class nor nil, just completely blank.
Any idea what I got wrong with that code?
I am also open to suggestions for any better way to get a list of all classes that have adopted a particular protocol...
EDIT: I forgot to provide the code for ClassInfo
import Foundation
struct ClassInfo: CustomStringConvertible, Equatable {
let classObject: AnyClass
let className: String
init?(_ classObject: AnyClass?) {
guard classObject != nil else { return nil }
self.classObject = classObject!
let cName = class_getName(classObject)!
self.className = String(cString: cName)
}
var superclassInfo: ClassInfo? {
let superclassObject: AnyClass? = class_getSuperclass(self.classObject)
return ClassInfo(superclassObject)
}
var description: String {
return self.className
}
static func ==(lhs: ClassInfo, rhs: ClassInfo) -> Bool {
return lhs.className == rhs.className
}
}
I can't explain why cls is always blank, like I said in my comment it's something I run into every time I'm dealing with meta types. As for making the code work as intended, I found this q&a and updated it with Swift 3 to get this code which should cover your situation. It's important to stress that this will only work if you correctly expose Swift to the Objective-C runtime.
Drop this code anywhere and call print(Migrations.getMigrations()) from a convenient entry point.
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
return getClassesImplementingProtocol(p: Preparation.self) as! [Preparation.Type]
}
static func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
let classes = objc_getClassList()
var ret = [AnyClass]()
for cls in classes {
if class_conformsToProtocol(cls, p) {
ret.append(cls)
}
}
return ret
}
static func objc_getClassList() -> [AnyClass] {
let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
let actualClassCount:Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
var classes = [AnyClass]()
for i in 0 ..< actualClassCount {
if let currentClass: AnyClass = allClasses[Int(i)] {
classes.append(currentClass)
}
}
allClasses.deallocate(capacity: Int(expectedClassCount))
return classes
}
}
class Migration: Preparation {
}
#objc
protocol Preparation {
}