Quantcast
Channel: Core Data – Michael Tsai
Viewing all 217 articles
Browse latest View live

Xcode 11 Beta

$
0
0

Xcode 11 Beta Release Notes:

Xcode 11 beta supports development with SwiftUI.

Xcode supports uploading apps from the Organizer window or from the command line with xcodebuild or xcrun altool. Application Loader is no longer included with Xcode.

[…]

LaunchServices on macOS now respects the selected Xcode when launching Instruments, Simulator, and other developer tools embedded within Xcode.

[…]

Editors can be added to any window without needing the Assistant Editor.

[…]

Run script phases and custom build rules may declare and emit a dependencies file, in the Makefile-style .d format output used by some compilers and build tools.

[…]

XCFrameworks make it possible to bundle a binary framework or library for multiple platforms —including iOS devices, iOS simulators, and UIKit for Mac — into a single distributable .xcframework bundle that your developers can use within their own applications.

[…]

When a data model configuration supports CloudKit, the data model editor performs additional validation to ensure the model conforms to the requirements for Core Data CloudKit support.

[…]

A view controller method annotated with the new @IBSegueAction attribute can be used to create a segue’s destination view controller in code, using a custom initializer with any required values. This makes it possible to use view controllers with non-optional initialization requirements in storyboards.

[…]

LLDB’s Python scripting is now based on Python 3. If you are using Python extensions that aren’t compatible with Python 3, they will break.

[…]

Xcode 11’s source editor introduces a mini map of the file. The mini map includes legible text for Mark:, highlighted lines with errors and warnings, source control changes, breakpoints, and highlighted Find results.

[…]

Test Plans are a new way to manage which tests run, and how those tests run. Schemes can reference multiple test plans, and define a default test plan for automation.

[…]

XCTest includes augmented performance testing capabilities with the new measureWithMetrics:options:block: method and related methods.

Plus an unwrap method.

Peter Steinberger:

There’s a new sidebar!

Jordan Rose:

I’ve been living on the new Xcode 11 source editing UI and it’s great. The full/assistant/comparison/authors/history pentachotomy is gone; now it’s “editors with sidebars” and “comparison mode”. And the inline diff means I don’t jump into comparison mode nearly as often.

Peter Steinberger:

Guess the device support trick no longer works/needs an update?

Tanner Bennett:

Goodbyyyyyye .xcodeproj! If you haven't heard, you can open Package.swift directly into Xcode now. I assume it just generates the project file in a temporary location.

You can even run tests!

Joe Fabisevich:

Xcode 11 has QuickSpec built in?

Previously:


Persistent History Tracking in Core Data

$
0
0

Steffen Ryll:

At WWDC ’17, Apple introduced a number of new Core Data features, one of which is Persistent History Tracking or NSPersistentHistory. But as of the time of writing, its API is still undocumented. Thus, the only real reference is the What’s New in Core Data WWDC session.

Since Persistent History Tracking makes sharing an NSPersistentStore across multiple processes and is one of my favorite new Core Data features, it is unfortunate that it mostly seems to fall of the radar.

The purpose of this post is to give a real-world example on how to use it and what makes it so great.

That was written a year and a half ago, and NSPersistentHistory remains a really cool feature that’s under-discussed and under-documented. Some resources I’ve found are:

Here are some things I figured out by exploring:

  • The history is stored directly in the same SQLite database(s) as the persistent store.
  • It uses tables that look kind of like Core Data tables, only with a different prefix.
  • But you couldn’t create them yourself using Core Data, since the same column can store the primary key for different types of entities (you would think NSObjectIDAttributeType could do that, but it actually can’t be used in stores), and likewise for the columns that store the tombstone values.
  • The tables are updated using SQLite triggers, which are again not directly exposed in Core Data (though this year’s new derived attributes also use them).
  • The triggers are fired for all database changes, so unlike the managed object context’s change tracking, they also work for batch updates and deletions (and, presumably, the forthcoming batch insertions).
  • The tables look very compact, with repeated string values interned and the list of modified columns stored as a bit vector.
  • Core Data automatically updates the schema of the history tracking tables when you do a migration.
  • Enabling history tracking does not change the version of your model. But, in practice, you’ll get incorrect results if you don’t enable it consistently.
  • Setting an attribute to be preserved after deletion (i.e. for the tombstone) does change the model’s version hash, however.
  • There’s no public API to set this flag on an attribute in the model, only a checkbox in Xcode. However, you can use key-value coding to set or query NSPropertyDescription.preserveValueOnDeletionInPersistentHistory.
  • So, overall, it seems tricky to use persistent history on a store that will be shared with OS versions that don’t support history tracking. You might have to roll your own in that case.
  • Querying and pruning the history works as you would expect.
  • The NSPersistentHistoryTransaction.objectIDNotification() does not generate a NSManagedObjectContextDidSaveNotification, but rather a private NSManagedObjectContextDidSaveObjectIDsNotification notification.
  • Rather than containing full objects under keys like NSUpdatedObjectsKey, it contains object IDs under keys like updated_objectIDs. This is a bit unexpected, because NSManagedObjectContext is already documented to support NSManagedObjectID or NSURL objects under the NSUpdatedObjectsKey key.
  • In any case, you get IDs because it isn’t storing the changed values. Instead, when merging, it fetches the latest values from the store.
  • This makes sense given the data model, but it means that, perhaps counterintuitively, merging will update all the attributes, not just those those changed in the transaction that generated the notification. And they’ll be updated to the current values, which may be much newer than the ones at the time of the transaction. This is not version control, just a way to see what has changed.

The Curious Case of the Core Data Crash

$
0
0

Sean Heber:

iOS routinely terminates apps for all sorts of reasons, including using too much memory or taking too long to do background processing. I don’t know why this information isn’t included in Xcode’s Organizer, but it’s a critical piece of a debugging puzzle.

[…]

But one result let to Tech Note 2151, with the code listed at the end under Other Exception Types:

The exception code 0xdead10cc indicates that an application has been terminated by the OS because it held on to a file lock or sqlite database lock during suspension.

[…]

I discovered that Twitterrific was sometimes closed while it was doing a network download and iOS left it running in the background long enough for the network request to finish. But not long enough for the database update to finish.

Sean Heber:

Converting to NSURLSession wasn’t very hard and we did it a long time ago. However something had been bothering me and I could never quite pin it down. Specifically, it really felt like network tasks that started while foregrounded and ended while backgrounded were less reliable.

[…]

I eventually determined that sometimes a request would fail to start and the error code returned by the NSURLSessionDataTask would be something odd like NSURLErrorNetworkConnectionLost or the ever-helpful NSURLErrorUnknown.

[…]

I scoured the NSURLSessions docs looking for a clue and came across something I had missed all the times before - a property called shouldUseExtendedBackgroundIdleMode “In addition to requesting that the connection be kept open … when the app moves to the background.”

Waitaminute! What is this? Does this mean there’s now an assumption that the connection will NOT be kept open when moving into the background?! Holy buckets on a wagon.

It appears that they REALLY want you to use the special background support in NSURLSession. In fact they seem to say to go ahead and use it all the time - don’t try to make it conditional.

[…]

And just like that, the flakiness seemed to be gone. For years I made the assumption that an open NSURLSession connection would be allowed to finish as long as you had a background task active like NSURLConnection did - but NOPE it doesn’t do that by default!

Core Data Derived Attributes

$
0
0

Scott Perry:

Documentation for Core Data’s new derived attributes feature is up!

It’s a really cool feature.

NSDerivedAttributeDescription (see also: derivationExpression):

Use derived attributes to optimize fetch performance[…]

[…]

Data recomputes derived attributes when you save a context. A managed object’s property does not reflect unsaved changes until you save the context and refresh the object.

That makes sense given that the derivation is implemented in the SQLite store using triggers. And you wouldn’t want every change to cause a fetch for properties that you might not need right away.

However, you have to be careful to manually refresh the objects that you care about because, since at least macOS 10.14, there’s a bug where NSFetchRequest’s shouldRefreshRefetchedObjects option doesn’t work (FB6161838). It fetches the right objects, but their properties may be stale. See, for example, this code:

import Foundation
import CoreData

class Entity: NSManagedObject {
    @NSManaged var attribute: String
}

let attribute = NSAttributeDescription()
attribute.name = "attribute"
attribute.attributeType = .stringAttributeType
let entityDescription = NSEntityDescription()
entityDescription.name = "Entity"
entityDescription.properties = [attribute]
entityDescription.managedObjectClassName = Entity.className()
let model = NSManagedObjectModel()
model.entities = [entityDescription]

let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
// Also happens with SQLite store
try! coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: [:])

let writeContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
writeContext.persistentStoreCoordinator = coordinator
let readContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
readContext.persistentStoreCoordinator = coordinator

let writeEntity = Entity(entity: entityDescription, insertInto: writeContext)
writeContext.performAndWait {
    writeEntity.attribute = "Old"
    try! writeContext.save()
}

var readEntity: Entity? = nil
readContext.performAndWait {
    let request = NSFetchRequest<Entity>(entityName: entityDescription.name!)
    readEntity = try! readContext.fetch(request).first!
    // Initially the attribute should be Old, and that's what's printed
    print(readEntity!.attribute)
}

writeContext.performAndWait {
    writeEntity.attribute = "New"
    try! writeContext.save()
}

readContext.performAndWait {
    let request = NSFetchRequest<Entity>(entityName: entityDescription.name!)
    request.shouldRefreshRefetchedObjects = true
    _ = try! readContext.fetch(request)
    // Now the attribute should be New, but it is still Old
    print(readEntity!.attribute)

    readContext.refresh(readEntity!, mergeChanges: false)
    // However, manually refreshing does update it to New
    print(readEntity!.attribute)
}

In this example, you could instead use notifications to merge changes from one context into the other. But that wouldn’t work with derived attributes since their changes aren’t reported in notifications.

Core Data Lab 1.0

$
0
0

Ron Elemans (via Mike Rundle):

The Core Data viewer app we had in mind should be able to filter data in any way we like, show related data defined by Core Data relationships, allow to edit and delete data, show any type of web data automatically, and present all data conform the object model including binary and transient fields.

A database viewer is of course not complete without obvious features like a metadata viewer, which allows you to inspect all aspects of the Core Data Object model, and export functions, which allow you to export any data selection as CSV or JSON file.

But there is more. The app should also be able to show all Core Data apps that we ever started in an iOS, iPadOS, tvOS or watchOS simulator in a handy overview, together of course with the related database. And we are always curious how ‘other’ apps uses Core Data. So our Core Data app should be able to find the database for a given Core Data app, and the other way around.

[…]

Another nice feature we implemented is a data change tracker, which lets you see in a graphical way how a Core Data app mutates a database.

The initial version already looks better than the previous such apps I tried. I can’t believe it’s only $10. The developer was very responsive to the feature requests and bugs that I sent in.

See also: CoreDataUtility, Core Data Explorer.

Previously:

iOS Developer Survey

$
0
0

Dave Verwer:

The iOS Developer Community Survey is the largest public survey of Apple platform developers ever undertaken. Data collection happened over four weeks between 6th December 2019 and the 7th January 2020. In that month, 2,290 people filled in the questionnaire. This site presents the raw data collected, along with analysis and opinion based on that data.

Dave Verwer:

Almost 70% of people are writing 100% of their personal/hobby Apple platform code in Swift. Given that company/team restrictions and the impact of an existing codebase is much less of an issue in personal/hobby projects. I think this question is a good indicator of developer interest in the language, and what this tells me is that Swift is dominating.

When it comes to apps written for a company, you might expect the number to fall. It does, but not by much.

Larger companies seem to use more Swift than smaller ones.

Dave Verwer:

An average satisfaction of 8.3 is obviously very high, even more so when you think of how critical we developers can be about the languages we use!

[…]

But I believe there are some slightly worrying signals revealed by this question. Only ~66% of people think that Swift is in good hands at Apple? Only ~59% of people believe that the evolution process is working well?

I’m generally like how the language has been evolving, but the progress on tooling and reliability have been frustrating.

The most interesting questions to me are that 75.5% say that they have a “Completely separate/independent codebases for each mobile platform” (I expected much lower) and that 60% say they would use SwiftUI in a new app to ship soon (also expected lower).

Interest in Mac development seems to be low, and respondents who were interested in Mac development preferred Catalyst and SwiftUI to AppKit, which does not bode well for the quality of future apps.

Apple has been talking a lot about machine learning and augmented reality and adding lots of stuff to the frameworks, but interest seems relatively low.

42% of apps were completely free or donationware, with 21% using subscriptions.

Making Swift Properties Overridable Only in Debug Builds

$
0
0

John Sundell:

Occasionally, we might want to override certain properties in order to facilitate testing, to be able to work on a new feature, or to debug a problem.

[…]

Here’s a way to mitigate that problem, using Swift’s new property wrappers feature in combination with the DEBUG compiler flag. By creating a DebugOverridable property wrapper, we can enforce that the properties that we wish to override during testing and development are not actually overridden within any of our code that we’re shipping to production[…]

This is a neat trick, though unfortunately property wrappers don’t work with @NSManaged. What I have been doing is having my tests use underscored versions of the properties. These are a declared in an extension, which is conditionally compiled only when the TEST flag is set.

I’d still like to see Swift’s access controls reworked to make testing easier. @testable import doesn’t really do the job because it only works for symbols that are already visible at the package level. So you can only use private for stuff that will never be used from tests.

Previously:

Why NetNewsWire Is Fast

$
0
0

Brent Simmons (tweet):

The parsers are fast — but we also do our best to skip parsing entirely when we can. There are two ways we do that.

We use conditional GET, which gives the server the chance to respond with a 304 Not Modified, and no content, when a feed hasn’t changed since the last time we asked for it.

This is wonderful in theory, but it doesn’t seem to work consistently with my blog. I’ve tested it, and the logs show that a few percent of NetNewsWire users are getting 304-cached content, but the vast majority are not. This may be a WordPress issue.

WP Super Cache:

Supercache doesn’t support 304 header checks in Expert mode but does support it in Simple mode.

I think at one point it worked when I hacked WP Super Cache to cache feeds using mod_rewrite, but currently I’m using the unmodified version in Simple mode.

Back to Simmons:

The same API that marks a single article as read is used to mark 10,000 articles as read. This way the database is updated once, the unread counts are updated once, and we push just one action on the undo stack.

The Cocoa frameworks can provide all sorts of notification and undo functionality almost for free, but to get bulk operations right you need to do it by hand.

Previously:


Marking Unused Required Swift Initializers As Unavailable

$
0
0

Jesse Squires:

However, if you do not use Interface Builder, then init(coder:) is irrelevant and will never be called. It is annoying boilerplate. But the real problem is that Xcode (and presumably other editors) will offer init(coder:) as an auto-complete option when initializing your view or view controller. That is not ideal, because it is not a valid way to initialize your custom view or view controller. Luckily, you can use Swift’s @available attribute to prevent this, which also has the benefit of more clearly communicating that you should not use this initializer.

It’s annoying how each of my view and managed object subclasses has to reimplement a required initializer that I never intend to call.

Backing Up Core Data Stores

$
0
0

Tom Harrington:

Once you use this method, the persistent store you migrated is removed from the persistent store coordinator. It also adds the newly-migrated store to the coordinator. So now the store is using the new copy instead of the old one. That’s not good for a backup, since the new copy is the backup you just made that you want to leave alone. It’s also a potential app crasher, since any managed objects you already fetched came from a persistent store that’s no longer available.

He recommends using a second, temporary persistent store coordinator as the source for the migration.

User-defined Order in SQL

$
0
0

Joe Nelson (via Hacker News):

The most natural first attempt is to add an auto-incrementing integer column to track each item’s position[…] It requires updating a bunch of rows to insert one between others.

[…]

What if we store the position of each row using float or numeric values rather than int or bigint? This would allow squeezing new elements between others, rather than shifting items forward to make room. […] However floating point numbers have limited precision.

[…]

Non-negative fractions actually form a binary tree, with every fraction (in lowest terms) appearing at a unique node. […] The terms of these fractions are expressed in lowest terms and grow slowly at each insertion. For instance you can see from the tree diagram earlier that inserting between 1 and 0 toward 0 generates 1/2, 1/3, 1/4 … which can go a very long time because numerators and denominators in pg_rational each get 32 bits of storage.

Other approaches:

  • Using a string column lets you always add rows in between other rows without ever having to renumber the world because the strings can be arbitrarily long.
  • A linked list is efficient for updating the rows but doesn’t work well with partial fetching.

Ordered relationships in Core Data seem to use the basic integer approach. I’ve not used this feature much because it’s always seemed risky to rely on it. For many years it was buggy, NSOrderedSet still isn’t available in Swift, and CloudKit doesn’t support ordered relationships.

Previously:

Core Data Growing Pains

$
0
0

Whitney Young:

Today I’m going to discuss nested context support in Core Data and various issues that exist with them. Some of these issues are minor bugs. Others may be considered functionally correct by the Core Data team, but result in unexpected behavior. Regardless, they add up to nested contexts being a feature you should avoid completely (as of this writing).

One of the surprising issues is that Apple specifically recommends using nested contexts to do work in the background, yet if you use a nested context on a background thread it will block your user interface.

Developers Dish on iCloud’s Challenges

$
0
0

Lex Friedman:

Implementing the first two syncing approaches isn’t easy, but developers tell Macworld that the third approach, for syncing Core Data, is what Steve Jobs might call “a bag of hurt”—it’s extremely complex. While iOS 6 will offer developers some significant improvements for behind-the-scenes iCloud syncing, the challenges such syncing presents will likely remain difficult.

Are there any applications from Apple that use Core Data iCloud syncing?

Update (2012-07-24): Or, Matt Stevens asks, Core Data at all?

Update (2012-10-26): Troy Harris and Reddit on Core Data and iCloud.

Replacing vs. Migrating Core Data Stores

$
0
0

Apple Frameworks Engineer:

Additionally you should almost never use NSPersistentStoreCoordinator’s migratePersistentStore method but instead use the newer replacePersistentStoreAtURL. (you can replace emptiness to make a copy). The former loads the store into memory so you can do fairly radical things like write it out as a different store type. It pre-dates iOS. The latter will perform an APFS clone where possible.

Tom Harrington:

[This] method is almost totally undocumented, so you’re on your own working out how to use it. The dev forums post mentioned above is from summer 2020. The replacePersistentStore(...) method was introduced five years earlier in iOS 9, but the forum post was the first time most of the information appeared.

[This] is the first suggestion I’ve seen that migratePersistentStore(...) might not be a good idea anymore. It’s not deprecated and I haven’t seen any previous source recommending against its use.

There are some comments in the header.

Incidentally you won’t find this if you’re using Swift and ⌘-click on the function name. You need to find the Objective-C header. One way to do this in Xcode is to press ⌘-shift-O and start typing the class name.

[…]

Its declaration says it can throw. I tried intentionally causing some errors but it never threw. For example, what if sourceURL points to a nonexistent file? That seems like it would throw, especially since the function doesn’t return anything to indicate success or failure. It doesn’t throw, although there’s a console message reading Restore error: invalidSource("Source URL must exist").

He’s figured out a lot, though other important details like the APFS support remain a mystery.

Tom Harrington:

The demo app I’ve been using is now on GitHub. You can take a look here. Or go directly to the diff of replacing migrate with replace here.

[…]

The backup process is simpler than it used to be, because replace doesn’t have the same side-effect that migrate did of unloading the persistent store.

[…]

Even though the migrate and replace methods seem pretty similar, the semantics are slightly different when the destination is a currently-loaded store. My new restore code reflects that.

Previously:

Making NSFetchRequest.fetchBatchSize Work With Swift

$
0
0

Apple Frameworks Engineer:

Set in Swift is an immutable value type. We do not recommend making Core Data relationships typed this way despite the obvious convenience. Core Data makes heavy use of Futures, especially for relationship values. These are reference types expressed as NSSet. The concrete instance is a future subclass however. This lets us optimize memory and performance across your object graph. Declaring an accessor as Set forces an immediate copy of the entire relationship so it can be an immutable Swift Set. This loads the entire relationship up front and fulfills the Future all the time, immediately. You probably do not want that.

It’s so convenient, though, and often it doesn’t matter because it’s a small relationship or one that you will be fully accessing anyway. Perhaps the answer is to provide a duplicate set of NSSet accessors for use when you want the lazy behavior enabled by the class cluster.

Similarly for fetch requests with batching enabled, you do not want a Swift Array but instead an NSArray to avoid making an immediate copy of the future.

Needless to say, the documentation doesn’t mention this, but it does do a good job of explaining what fetchBatchSize does:

If you set a nonzero batch size, the collection of objects returned when an instance of NSFetchRequest is executed is broken into batches. When the fetch is executed, the entire request is evaluated and the identities of all matching objects recorded, but only data for objects up to the batchSize will be fetched from the persistent store at a time. The array returned from executing the request is a proxy object that transparently faults batches on demand. (In database terms, this is an in-memory cursor.)

You can use this feature to restrict the working set of data in your application. In combination with fetchLimit, you can create a subrange of an arbitrary result set.

Under the hood, this works by eagerly fetching the object IDs and lazily fetching and caching the objects, in batches, as they are accessed. The implementation is more optimized than what you could implement yourself, passing the object IDs to SQLite via temporary tables rather than as parameters to the SQL statement. There are some caveats to be aware of:

  • If you’re using a coordinator with multiple stores, it will get the sorting right, fetching multiple batches and merging them together without ever doing a giant fetch. However, it does seem to eventually load all the objects into memory at once, which mostly defeats the purpose of batching. If you can hold everything in memory but just prefer not to, I guess you could refault all the objects after the sorting has completed and let the special array bring them back as needed. Or, you can avoid combining fetchBatchSize with multiple stores and instead use a dictionary fetch request to get just the object IDs and the properties needed for sorting, save the IDs, and manually fetch batches of full objects as needed.

  • I’m a little worried that there are bugs related to multiple stores. Disassembling _PFBatchFaultingArray shows code that anticipates sometimes receiving more object IDs than it expected to fetch, and this has occurred in the wild. It’s looks as if Core Data is querying the Z_PK without regard for which store it’s supposed to be in. However, I tried to reproduce this situation by deliberately creating objects in multiple stores with the same Z_PK and everything seemed to work as expected on macOS 10.15.7.

So, how do you get the optimized fetchBatchSize behavior when using Swift? The Apple engineer suggests using an NSArray, which I take to mean casting the result of the fetch via as NSArray to disabling automatic bridging and give your Swift code the original NSArray. However, my experience is that this doesn’t work. All the objects get fetched before your code even accesses the array. I think it’s because the special as behavior is for disabling bridging when calling Objective-C APIs from Swift, but NSManagedObjectContext.fetch(_:) is an overlay method implemented in Swift, not just a renaming of -[NSManagedObjectContext executeFetchRequest:error:].

This can be worked around by using an Objective-C category to expose the original method:

@interface NSManagedObjectContext (MJT)
- (nullable NSArray *)mjtExecuteFetchRequest:(NSFetchRequest *)request error:(NSError **)error;
@end

@implementation NSManagedObjectContext (MJT)
- (nullable NSArray *)mjtExecuteFetchRequest:(NSFetchRequest *)request error:(NSError **)error {
    return [self executeFetchRequest:request error:error];
}
@end

Then you can implement a fetching method that preserves the batching behavior:

public extension NSManagedObjectContext {
    func fetchNSArray<T: NSManagedObject>(_ request: NSFetchRequest<T>) throws -> NSArray {
        // @SwiftIssue: Doesn't seem like this cast should be necessary.
        let protocolRequest = request as! NSFetchRequest<NSFetchRequestResult>        
        return try mjtExecute(protocolRequest) as NSArray
    }

    func fetch<T: NSManagedObject>(_ request: NSFetchRequest<T>,
                                   batchSize: Int) throws -> MJTBatchFaultingCollection<T> {
        request.fetchBatchSize = batchSize
        return MJTBatchFaultingCollection(array: try fetchNSArray(request))
    }
}

The first method gives you the NSArray, but that is not very ergonomic to use from Swift. First, you have to cast the objects back to your NSManagedObject subclass. Second, it doesn’t behave well when an object is deleted (or some other SQLite error occurs) between your fetch and when Core Data tries to fulfill the fault.

If you’re using Swift, you can’t catch the NSObjectInaccessibleException, so you should be using context.shouldDeleteInaccessibleFaults = true. This means that instead of an exception you get a sort of tombstone object that’s of the right class, but with all its properties erased.

But it’s hard to remember to check for that each time you use one of the objects in the NSArray, and you probably don’t want to accidentally operate on the empty properties. So the second method uses a helper type to try to make the abstraction less leaky, always giving you either a valid, non-fault object or nil:

public struct MJTBatchFaultingCollection<T: NSManagedObject> {
    let array: NSArray
    let bounds: Range<Int>

    // array is presumed to be a _PFBatchFaultingArray from a fetch request
    // using fetchBatchSize.
    public init(array: NSArray, bounds: Range<Int>? = nil) {
        self.array = array
        self.bounds = bounds ?? 0..<array.count
    }
}

extension MJTBatchFaultingCollection: RandomAccessCollection {
    public typealias Element = T?
    public typealias Index = Int
    public typealias SubSequence = MJTBatchFaultingCollection<T>
    public typealias Indices = Range<Int>
    
    public var startIndex: Int { bounds.lowerBound }
    public var endIndex: Int { bounds.upperBound }
       
    public subscript(position: Index) -> T? {
        guard
            let possibleFault = array[position] as? T,
            let context = possibleFault.managedObjectContext,
            // Unfault so that isDeleted will detect an inaccessible object.
            let object = try? context.existingObject(with: possibleFault.objectID),
            let t = object as? T else { return nil }
        return t.isDeleted ? nil : t
    }

    public subscript(bounds: Range<Index>) -> SubSequence {
        MJTBatchFaultingCollection<T>(array: array, bounds: bounds)
    }
}

extension MJTBatchFaultingCollection: CustomStringConvertible {
    public var description: String {
        // The default implementation would realize all the objects by printing
        // the underlying NSArray.
        return "<MJTBatchFaultingCollection<\(T.self)> bounds: \(bounds)>"
    }
}

It’s still a bit leaky, because you have to be careful to only access the collection from the context’s queue. But this is somewhat obvious because it has a separate type, so you’ll get an error if you try to pass it to a method that takes an Array.

The batch faulting behavior and batch size are preserved if you iterate over the collection or slice it. (When iterating the NSArray directly, small batch sizes don’t work as expected because NSFastEnumerationIterator will always load at least 16 objects at a time.)

Previously:


How to Set Up Core Data and CloudKit

$
0
0

Becky Hansmeyer (tweet):

Turns out, if you want to sync Core Data-backed data between devices and have those changes reflected in your UI in a timely manner, you have some more work to do. To figure out what that work is, you can’t look at Apple’s Core Data templates. You have to look at their sample code.

[…]

Don’t be like me: make sure your schema is deployed.

After launch, remember that you still have to do this every time you change your Core Data model, before you release your update to testers or the App Store. If your production CloudKit schema doesn’t properly correspond to your production Core Data model, syncing is going to break in all kinds of terrifying ways.

Previously:

Swift Overloads That Differ Only in Async

$
0
0

Gwendal Roué:

I wish users could use the same method names in both synchronous and asynchronous contexts.

[…]

But I face “Invalid redeclaration” compiler errors, as expected according to the proposal.

[…]

Should I rename my async variants with some funny name? await asyncRead()? But the proposal itself wants to avoid C#’s pervasive Async suffix.

[…]

[The] new Core Data apis described in the WWDC21 conference Bring Core Data concurrency to Swift and SwiftUI face the same problem. They worked around the overload error by defining async methods with a different signature[…]

He found a workaround using the non-public @_disfavoredOverload attribute.

Ben Trumbull:

Methods in the same module cannot overload only on async-ness, but methods bridged from ObjC or in a different Swift module may.

Previously:

iCloudCoreDataStarter and Temporary IDs Bug

$
0
0

Chad Etzel (tweet):

In the course of building Sticker Doodle (which you should go download right now), I ran into many brick walls and learned way too much about Core Data, iCloud sync, Collection and Table Views, and Diffable Data Sources.

There is documentation for each of those individually, but I could find no clear and simple example project that ties them all together in a neat little bow.

Well, that changes today.

[…]

Before .save() is called on the managed object context, this temporary ID can be used to fetch the object, refer to it, etc… it acts like a normal NSManagedObjectID -- HOWEVER, after .save() is called on the managed object context, 2 things are supposed to happen:

  1. NSManagedObjects with temporary IDs are supposed to be assigned a permanent ID and those objects updated in memory with the new ID.

  2. The managed object context forgets all temporary IDs and attempting to use them to identify an object will fail.

There seems to be a bug (in iOS 14 and iOS 15 as of my latest testing) where sometimes Step 1 will not actually happen and newly inserted and saved objects will still have a temporary ID!! This has led to all sorts of unexpected and frustrating behavior until I figured out what was actually going on. There are several developer forum posts and Stack Overflow questions regarding the same behavior, so I am not the only one that has experienced this bug.

Previously:

Swift Playgrounds 4

$
0
0

Juli Clover (Hacker News):

Apple today released Swift Playgrounds 4, an update to the Swift Playgrounds app that’s been in the works for some time. The newest version of the app allows iPhone and iPad apps to be created directly on an iPad without the need for a Mac.

Swift Playgrounds 4 includes App Store Connect integration for uploading a finished app to the App Store , plus there is an App Preview feature that shows live updates as you make changes.

Steve Troughton-Smith:

While you can monetize your app as a paid-upfront app just fine, there’s no access to In-App Purchase, which feels like an unnecessary restriction on people learning to develop iOS apps through Playgrounds — paid-upfront is extremely hard to make work even for experienced devs

Marcin Krzyzanowski:

Tim Cook: We have 60 apps on the App Store. They go through the same rules that the 1.7 million do

also Tim Cook’s company app from the App Store:

Playgrounds Entitlements

Previously:

Update (2021-12-20): Damien Petrilli:

So you can’t even use Core Data in Swift Playgrounds 4? (+ no git)

“We can’t wait to see what you are going to ship with it”

Let’s be honest, Playgrounds is so limited that most Apps would be rejected during the App review for being too simple.

You can use Core Data, but you have to create the managed object model in code. I like to do that, anyway, but it’s not very friendly for beginners. Not having version control is an even more serious problem.

Riley Testut:

Here’s the full code to export .ipa’s from Swift Playgrounds 4.

Clash of the Optionals

$
0
0

Tom Harrington:

What is an “optional” anyway? It depends who you ask. Swift will give you one answer, but Core Data has other ideas. So what happens when you bring them together?

[…]

Guess what, you just broke Swift’s initializer rules! Specifically, you initialized an object but didn’t provide a values for all of its non-optional properties. Nobody stopped you because @NSManaged says the rules don’t apply. You’ve just built a bomb.

[…]

Swift knows that initialization finished and there’s a non-optional property. It’s impossible for non-optional properties to be nil at this point, according to Swift. You can’t even check for nil values, that’s how definite this is. So it’s… not nil?

[…]

There is a way to check for nil first, but you have to pretty much forget that you’re using Swift first and fall back on key-value coding.

Making all your Core Data properties optional in Swift complicates the bulk of your code unnecessarily. But making them non-optional creates other problems, as above. I’ve also run into trouble with bridging changing nil to NSNull, which Core Data doesn’t like as a property value.

Previously:

Viewing all 217 articles
Browse latest View live