In iOS 18 RC, and the iOS 18 simulator shipped with Xcode 16.0 RC, there is a regression where ModelContexts on the same ModelContainer do not sync changes. A minimal example is below, but briefly: create an object in context1. Retrieve and update that object in context2, then save context2. The changes cannot be found in context1 in iOS 18 RC, but can in iOS 17 and earlier betas of iOS 18.
I've submitted this as FB15092827 but am posting here for visibility to others. I'm going to have to scramble to see if I can mitigate this in our impacted app before iOS 18 launches. It's affecting us when doing background inserts in a ModelActor to populate our app UI, but you can see below the effects are seen even on the same thread in a very simple two-context example.
@Test("updates sync between contexts") func crossContextSync() async throws {
// overview:
// create an employee in context 1
// update the employee in context 2
// check that the update is available in context 1
let context1 = ModelContext(demoAppContainer)
let context2 = ModelContext(demoAppContainer)
// create an employee in context 1
let newEmployee = Employee(salary: 0)
context1.insert(newEmployee)
try context1.save()
#expect(newEmployee.salary == 0, "Created with salary 0")
// update the employee in context 2
let employeeID = newEmployee.uuid
let predicate: Predicate<Employee> = #Predicate<Employee> { employee in
employee.uuid == employeeID
}
let fetchedEmployee = try #require(try? context2.fetch(FetchDescriptor<Employee>(predicate: predicate)).first)
#expect(fetchedEmployee.uuid == newEmployee.uuid, "We got the correct employee in the new context")
let updatedSalary = 1
fetchedEmployee.salary = updatedSalary
try context2.save()
// FAILURE IS HERE. This passes in earlier iOS betas and in iOS 17.X
#expect(newEmployee.salary == updatedSalary, "Salary was update in context 1")
// Create a new modelContext on the same container, since the container does have the changes in it.
// By creating this new context we can get updated data and the test below passes in all iOS versions tested. This may be a mitigation path but creating new contexts any time you need to access data is painful.
let context3 = ModelContext(demoAppContainer)
let fetchedEmployeeIn3 = try #require(try? context3.fetch(FetchDescriptor<Employee>(predicate: predicate)).first)
#expect(fetchedEmployeeIn3.uuid == newEmployee.uuid, "We got the correct employee in the new context3")
#expect(fetchedEmployeeIn3.salary == updatedSalary, "Salary was update in context 1")
}
Code below if you want to build a working example, but the test above is very simple
let demoAppContainer = try! ModelContainer(for: Employee.self)
@main
struct ModelContextsNotSyncedToContainerApp: App {
init() {
}
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(demoAppContainer)
}
}
}
@Model
final class Employee {
var uuid: UUID = UUID()
var salary: Int
init(salary: Int = 0) {
self.salary = salary
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
We have an app that’s been in the store for a year, and the latest version was rejected for
Guideline 5.6 - Developer Code of Conduct. The app attempts to manipulate customers into making unwanted in-app purchases. Specifically, the app shows a one time offer for subscription when the user closes the initial subscription page launched upon opening the app.
Essentially we have a soft paywall at the end of onboarding, and if the user closes that paywall we pop a second paywall with a one-time offer with an introductory offer (2 months for $1, “Once you close your one-time offer, it’s gone!”). We’ve had this in the last few versions of the app, but not been flagged for it.
The strategy of having a one time offer after end of the onboarding paywall is not uncommon, and we are copying the approach used by many other apps in the app store. Limited time offers are a staple of marketing across all types of B2C stores.
Does anyone have any experience with this type of rejection? Should we stick to our approach and escalate to a phone call? Change the one-time offer to a “limited time offer” (e.g. reduced price for 12 hours) so there is less pressure to decide right then? It’s a strange rejection because it doesn’t seem like we are doing anythign unusual. An onboarding soft-paywall followed by a one time offer is less of a push than a hard paywall (which is approvable), and offers some people an appealing way to test the app before committing to pay for a subscription (which doesn’t offer free trials).
However, we don’t want to get flagged as a bad actor by the App Store reviewers.