Swift Core Data - removeObject() in NSMutableOrderedSet unexpected behavior - swift

I have been chewing on this for a week now, and unless I am missing a very fundamental concept in Swift, this seems like a pretty serious bug.
I have a CoreData model with one-to-many relationships like so:
City -> has many Street
Also each entity has a name property of type string.
The corresponding classes are:
class City: NSManagedObject {
#NSManaged var name: String
#NSManaged var streets: NSOrderedSet
}
class Street: NSManagedObject {
#NSManaged var name: String
}
And here comes the really weird stuff:
var city:City = getCities().first!
var streets = city.streets.array as! [Street]
var streetToDelete:Street? = nil
//This works
streetToDelete = streets.first
//This is not working !!!
for street in streets {
if(street.name == "champs elysees"){
streetToDelete = street
}
}
deleteStreet(city, street:streetToDelete!)
And the deleteStreet function:
func deleteStreet(var city:City, var street:Street){
var streets = city.streets
var streetsSet:NSMutableOrderedSet = streets.mutableCopy() as! NSMutableOrderedSet
streetsSet.removeObject(street)
city.streets = streetsSet.copy() as! NSOrderedSet
saveContext()
}
So if I use streets.first, the deletion works as accepted.
But if I use the foreach loop instead nothing happens - no error is thrown, and no street is deleted. (off course I need the foreach loop to find a specific street and delete it).
How come the .first and the foreach are different?
They suppose to return me a pointer to a street in both cases.
(getCities() is a simple, straight from the book, fetch operation)
Update:
After further investigation, it seems that some how the iteration itself over the streets array causes the issue.

Related

How to use Core Data Relationship in ForEach SwiftUI

Im facing issues with displaying my Core Data inside of SwiftUI because of relationships being Sets. What is the best way to go about displaying a many relationship set inside of a SwiftUI ForEach loop?
For instance, I have two entities in core date: Entry & Position. Entry can contain many positions, and each position can only belong to one entry.
I have the Entry as a #binding var on the view that is suppose to display the entry's Positions. I would ideally like to access the positions directly from this entry variable, but because positions are a Set, I get the following error:
error 'ForEach' requires that 'Set' conform to 'RandomAccessCollection'
#Binding private var entry: Entry?
ForEach(entry?.positions) { position in
Text("position here")
}
Solution One:
Or, I could do a fetch request for all positions, then filter out all the ones that do not belong to the entity, I do not like this idea as I would be fetching potentially thousands of Positions to only get a few. (or am i thinking of this wrong? Im new to core data, coming from realm because of swiftui)
Although this could work if I could do a fetch ONLY on the #binding entry var, and fetch all its positions as sorted fetched results, but I'm not sure there is a way to do this.
Maybe like this, or would this be a performance issue if there was potentially thousands of entry's each with 10-20+ positions? and could objectID be used this way, and would it still be unique if the entry was moved into another journal?:
#Binding private var entry: Entry?
#FetchRequest(
entity: Position.entity(),
sortDescriptors: [],
predicate: NSPredicate(formate: "entry.objectID == %#", self.entry.objectID)
) var positions: FetchedResults<Position>
Solution Two:
I thought of adding an attribute to positions like 'date', this way positions could be compared and sorted? But not sure how this could be updated with SwiftUI, as it would be done only once in the init().
let list = entry.wrappedValue?.positions?.sorted()
Core Data Models:
public class Entry: NSManagedObject, Identifiable {
// MARK: - ATTRIBUTES
#NSManaged public var date: Date
// MARK: - RELATIONSHIPS
#NSManaged public var journal: Journal?
#NSManaged public var positions: Set<Position>?
}
public class Position: NSManagedObject, Identifiable {
// MARK: - RELATIONSHIPS
#NSManaged public var entry: Entry?
}
How would you go about solving this problem? Keep in mind on the view where the positions are being listed, that this view can add, delete, and modify positions, so the solution should allow SwiftUI to reload the view and update the positions when changes are made.
#JoakimDanielson comment works like a charm, but requires a few tweaks.
Wrapping the set in the array initializer works like this, but requires optional sets to be unwrapped. I was surprised to find force unwrapping does not cause a crash even if the set is nil? Maybe someone could explain this?
ForEach(Array(entry.positions!)) { position in
Text("Position")
}
The next issue was that the array would be randomized everytime the set of positions changed due to sets being unordered. So by conforming Position to the Comparable Protocol solved this. I decided it made the most sense to sort positions by date, so I updated the model like so:
public class Position: NSManagedObject, Identifiable {
// MARK: - ATTRIBUTES
#NSManaged public var date: Date
// MARK: - RELATIONSHIPS
#NSManaged public var entry: Entry?
}
extension Position: Comparable {
static func < (lhs: Position, rhs: Position) -> Bool {
lhs.date < rhs.date
}
}
Then the ForEach could be sorted and looks like this:
ForEach(Array(entry.positions!).sorted()) { position in
Text("\(position.date)")
}
Some other solutions I found but are not ideal for reasons mentioned in original post, but they do work, is to either use a fetch request customized inside the view init like so:
#FetchRequest var positions: FetchedResults<Position>
init(entry: Entry) {
var predicate: NSPredicate?
// Do some kind of control flow for customizing the predicate here.
predicate = NSPredicate(formate: "entry == %#", entry)
self._positions = FetchRequest(
entity: Position.entity(),
sortDescriptors: [],
predicate: predicate
)
}
or create an "middle man" View Model bound to #ObservedObject that converts core data managed objects to useable view data. Which to me makes the least sense because it will basically contain a bunch of redundant information already found inside the core data managed object, and I like the idea of the core data managed object also being the single source of truth.
I found myself using this solution frequently so I added an extension to the CoreData object (see below for an example using Entry/Position types). This also has the added benefit of handling optional relationships and simply returning an empty array in that case.
extension Entry {
func arrayOfPositions() -> [Position] {
if let items = self.positions as? Set<Position> {
return items.sorted(by: {$0.date < $1.date})
} else {
return []
}
}
}
So that instead of unsafely and cumbersomely writing:
ForEach(Array(entry.positions! as! Set<Position>).sorted(by: {$0.date < $1.date})) { item in
Text(item.description)
}
You can safely use:
ForEach(entry.arrayOfPositions()) { item in
Text(item.description)
}
Simply pass a customised FetchRequest param to the View containing the #FetchRequest property wrapper.
struct PositionsView: View {
let entry: Entry
var body: some View {
PositionsList(positions: FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Positions.date, ascending: true)], predicate: NSPredicate(format: "entry = %#", entry)))
}
}
struct PositionsList : View {
#FetchRequest var positions: FetchedResults<Positions>
var body: some View {
ForEach(positions) { position in
Text("\(position.date!, formatter: positionFormatter)")
}
}
}
For more detail, see this answer.

Cast between types using only protocols?

I have 2 structs that are only related by protocols. I'm trying to cast between the two types since they share a protocol, but I get a runtime error.
This is what I'm trying to do:
protocol A {
var id: String { get }
var name: String { get }
var isActive: Bool { get set }
}
struct Entity: A {
let id: String
var name: String
var isActive: Bool
func someFun() {
print("\(name), active: \(isActive)")
}
}
struct ViewModel: A {
let id: String
var name: String
var isActive: Bool
}
let vm = ViewModel(id: "abc", name: "John Doe", isActive: true)
let pt = vm as A
let et = pt as! Entity
print(et.someFun()) //crash
The error I get is:
Could not cast value of type '__lldb_expr_87.ViewModel' (0x111835638) to '__lldb_expr_87.Entity' (0x111835550).
This is a real bummer because if I have millions of records, I don't want to have to loop through each one to manually convert one by one. I was hoping to implicitly cast magically like this:
let entity = vm as A as Entity
let entities = vms as [A] as [Entity]
Any performance-conscious way to pass objects between boundaries without looping through each one?
As vadian stated in his comment, you can't typecast like this. To perform what you are trying to do by typecasting, you would need to do something like this:
let entity = Entity(id: vm.id, name: vm.name, isActive: vm.isActive)
let entities = vms.map {
return Entity(id: $0.id, name: $0.name, isActive: $0.isActive
}
The reason why you are crashing is because typecasting doesn't change the properties and methods that an object actually has, only the methods and properties they have access to. If you performed the following, you would lose access to your someFun() method:
let entityAsA = Entity(id: "id", name: "name", isActive: true) as A
However, the following will compile and run without crashing:
(entityAsA as! Entity).someFun()
So, when you attempt to cast from ViewModel to A to Entity, it doesn't change the way the value was created in memory (without access to a function called someFun() ) and you'll crash every time.

deep copy for array of objects in swift

I have this class named Meal
class Meal {
var name : String = ""
var cnt : Int = 0
var price : String = ""
var img : String = ""
var id : String = ""
init(name:String , cnt : Int, price : String, img : String, id : String) {
self.name = name
self.cnt = cnt
self.price = price
self.img = img
self.id = id
}
}
and I have an array of Meal :
var ordered = [Meal]()
I want to duplicate that array and then do some changes to the Meal instances in one of them without changing the Meal instances in the second one, how would I make a deep copy of it?
This search result didn't help me
How do I make a exact duplicate copy of an array?
Since ordered is a swift array, the statement
var orderedCopy = ordered
will effectively make a copy of the original array.
However, since Meal is a class, the new array will contain references
to the same meals referred in the original one.
If you want to copy the meals content too, so that changing a meal in one array will not change a meal in the other array, then you must define Meal as a struct, not as a class:
struct Meal {
...
From the Apple book:
Use struct to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they are passed around in your code, but classes are passed by reference.
To improve on #Kametrixom answer check this:
For normal objects what can be done is to implement a protocol that supports copying, and make the object class implements this protocol like this:
protocol Copying {
init(original: Self)
}
extension Copying {
func copy() -> Self {
return Self.init(original: self)
}
}
And then the Array extension for cloning:
extension Array where Element: Copying {
func clone() -> Array {
var copiedArray = Array<Element>()
for element in self {
copiedArray.append(element.copy())
}
return copiedArray
}
}
and that is pretty much it, to view code and a sample check this gist
You either have to, as #MarioZannone mentioned, make it a struct, because structs get copied automatically, or you may not want a struct and need a class. For this you have to define how to copy your class. There is the NSCopying protocol which unifies that on the ObjC world, but that makes your Swift code "unpure" in that you have to inherit from NSObject. I suggest however to define your own copying protocol like this:
protocol Copying {
init(original: Self)
}
extension Copying {
func copy() -> Self {
return Self.init(original: self)
}
}
which you can implement like this:
class Test : Copying {
var x : Int
init() {
x = 0
}
// required initializer for the Copying protocol
required init(original: Test) {
x = original.x
}
}
Within the initializer you have to copy all the state from the passed original Test on to self. Now that you implemented the protocol correctly, you can do something like this:
let original = Test()
let stillOriginal = original
let copyOriginal = original.copy()
original.x = 10
original.x // 10
stillOriginal.x // 10
copyOriginal.x // 0
This is basically the same as NSCopying just without ObjC
EDIT: Sadly this yet so beautiful protocol works very poorly with subclassing...
A simple and quick way is to map the original array into the new copy:
let copyOfPersons: [Person] = allPersons.map({(originalPerson) -> Person in
let newPerson = Person(name: originalPerson.name, age: originalPerson.age)
return newPerson
})
The new Persons will have different pointers but same values.
Based on previous answer here
If you have nested objects, i.e. subclasses to a class then what you want is True Deep Copy.
//Example
var dogsForAdoption: Array<Dog>
class Dog{
var breed: String
var owner: Person
}
So this means implementing NSCopying in every class(Dog, Person etc).
Would you do that for say 20 of your classes? what about 30..50..100? You get it right? We need native "it just works!" way. But nope we don't have one. Yet.
As of now, Feb 2021, there is no proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your class conforms to codable
class Dog: Codable{
var breed : String = "JustAnyDog"
var owner: Person
}
Create this helper class
class DeepCopier {
//Used to expose generic
static func Copy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need true deep copy of your object, like this:
//Now suppose
let dog = Dog()
guard let clonedDog = DeepCopier.Copy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
clonedDog.breed = "rottweiler"
//Also clonedDog.owner != dog.owner, as both the owner : Person have dfferent memory allocations
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our object. Just make sure all your Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.

Weak references in Swift playground don't work as expected

I have been following the weak referencing example from the Intermediate Swift WWDC session in a Playground. I modified the code slightly as follows:
class Apartment {
let address: Int
init(address: Int) {
self.address = address
}
weak var tenant: Person?
}
class Person {
let name: String
init(name: String){
self.name = name
}
weak var home: Apartment?
func moveIn(apt: Apartment) {
self.home = apt
apt.tenant = self
}
}
var renters = ["John Appleseed": Person(name: "John Appleseed")]
var apts = [16: Apartment(address: 16)]
renters["John Appleseed"]!.moveIn(apts[16]!)
renters["John Appleseed"] = nil // memory should be released here
// then apts[16].tenant should be nil
if let tenantName = apts[16]!.tenant?.name {
// this should only execute if the Person object is still in memory
println("\(tenantName) lives at apartment number \(apts[16]!.address)")
} else {
// and this line should execute if the memory is released as we expect
println("Nobody lives at apartment number \(apts[16]!.address)")
}
// Console output in Playground: John Appleseed lives at apartment number 16
// Console output in standalone app: Nobody lives at apartment number 16
From my understanding of weak referencing, the memory allocated for the instance of Person should be released when it is removed from the renters dictionary because the only other reference to it is weak. However, the output of the programme is different if it is run as a standalone command line application vs. in a Playground (see comments).
I believe the top-level function (REPL/playground) is keeping a strong reference to facilitate interactive behavior, and cleaning up when the frame returns. This behavior eliminates memory leaks in the interactive environment.
I copied Viktor's simple example and used the xcrun swift REPL.
In REPL mode, I wrapped the logic in a function and it works as expected. If/when you care when the memory is cleaned up, I would suggest wrapping your logic in a function.
// declaration of the types
class Person {
let name: String
weak var home: Apartment?
init(pName: String){
name = pName
}
}
class Apartment {
let postalCode: Int
init(pPostalCode: Int) {
postalCode = pPostalCode
}
}
func testArc() {
// create Person object
var personJulius: Person = Person(pName: "Julius")
// create Apartment object
var apartmentBerlin: Apartment? = Apartment(pPostalCode: 10777)
// connect Apartment object and Person object
personJulius.home = apartmentBerlin
// Set only strong reference of Apartment object to nil
apartmentBerlin = nil
// Person object should now have nil as home
if personJulius.home != nil {
println("Julius does live in a destroyed apartment")
} else {
println("everything as it should")
}
}
//outputs "everything as it should"
testArc()
I guess that the Playground itself keeps a strong reference to the object, so the code behaves differently? If that's the case, this could cause some unexpected problems!
I tried an a bit less complex setup of the code.
But did have the same problem in the Playground file, but not in a real command line project.
In a command line project, the output was everything as it should, and in the playground it was Julius does live in a destroyed apartment.
import Cocoa
// declaration of the types
class Person {
let name: String
weak var home: Apartment?
init(pName: String){
name = pName
}
}
class Apartment {
let postalCode: Int
init(pPostalCode: Int) {
postalCode = pPostalCode
}
}
// create Person object
var personJulius: Person = Person(pName: "Julius")
// create Apartment object
var apartmentBerlin: Apartment? = Apartment(pPostalCode: 10777)
// connect Apartment object and Person object
personJulius.home = apartmentBerlin
// Set only strong reference of Apartment object to nil
apartmentBerlin = nil
// Person object should now have nil as home
if personJulius.home != nil {
println("Julius does live in a destroyed apartment")
} else {
println("everything as it should")
}
It is not only weak reference. In playground, the deinit does not work. Since set a variable to nil can not let deinit run, it is not the time weak reference should work.
class MyClass {
init() {
println("ready")
}
deinit {
println("OK")
}
}
var aClass: MyClass?
aClass
aClass = MyClass()
aClass = nil
Update 3: As of 11.3.1: Playground seems to be deiniting objects as expected, as far as I can tell. My original and out of date answer follows: In Xcode 10.1 Playgrounds, I can confirm that deinits are still behaving strangely and I can't use Playgrounds to test whether things are being properly deallocated. Update 1: From another similar thread, I learned that Xcode>New>Project>macOS>Command Line Tool, is a relatively lightweight way to create a generic testing environment that works fine for testing deallocation.
class Person {
let name: String
init(named name: String) { self.name = name }
var house: House?
deinit { print("\(name) is being deinitialized") }
}
class House {
let address: String
init(address: String) { self.address = address }
weak var tenant: Person?
deinit { print("House \(address) is being deinitialized") }
}
func move(_ person: Person, into house: House){
house.tenant = person
person.house = house
}
When Person and House are unconnected, deinits work properly.
However, if I move Buffy into the house, and delete Buffy, because tenant is weak, the Buffy object should be deinited and tenant set to nil. As you can see neither happens.
Even after I delete house (line 38), neither are deinited.
Weak references are behaving like strong references in the Playground.
Wrapping the Run code in a function does not change anything in this example. Update 2: In Xcode 11, As wbennet suggests above, if you wrap your run code in a func and call it, deallocations work for weak references as defined, in Playground.

Strong reference cycles in Swift

I'm looking at the example from the “Unowned References and Implicitly Unwrapped Optional Properties” section of the book “The Swift Programming Language.”
Their example code is
class Country {
let name: String
let capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
This works if I want to deal exclusively with Countries and the only purpose of the City type is to be a capital of a Country. But what happens if I want to create a City?
This creates a runtime exception because no reference to the City's Country is retained since it is an unowned variable:
var chicago = City(name:"Chicago", country: Country(name: "USA", capitalName: "Washington DC"))
chicago.country.name // Playground execution failed: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
How would I allow something like this without creating a Strong Reference Cycle?
There are two typical solutions:
If you want to primarily deal with cities, invert the relationship so that City has a strong reference to Country, and Country points back to an unowned instance.
If you want to have cities and countries as primary objects that cross reference each other, put all cities and countries into collections (or other form of store that owns them), and make both references weak. That way they don't own each other, and you don't have a cycle.
The best way to avoid retain cycles is to consider who owns every object. Objects can own each other, but that should be a clear hierarchy (i.e. a tree). If you have connections that go sidewards and and upwards in the hierarchy, make them weak or unowned.
Solution one is the upwards case, solution two is sidewards case.
Edit
A third option is, to have Country own a collection of all its cities. I think that makes most sense in this simple case, but it means the Country needs to create all cities in it's initialization, or have a method that adds cities.
Here's an example for the second case. It's quite complex, probably too much so for this simple case, but it illustrates extracting a common owner. I would normally use it if there are a lot of cross references. (Think relational database. The records don't own each other.)
class Country {
let name: String
weak var capitalCity: City?
init(name: String) {
self.name = name
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country, isCapital: Bool) {
self.name = name
self.country = country
if isCapital {
country.capitalCity = self
}
}
}
class Planet {
var countries: [Country] = []
var cities: [City] = []
}
let earth = Planet()
earth.countries = [
Country(name: "USA"),
Country(name: "Canada"),
]
earth.cities = [
City(name: "Washington DC", country: earth.countries[0], isCapital: true),
City(name: "Chicago", country: earth.countries[0], isCapital: false),
City(name: "Ottawa", country: earth.countries[1], isCapital: true),
]
In your example, nobody is owning the Country instance. That means it gets deallocated (freed) immediately.
var country = Country(name: "USA", capitalName: "Washington DC")
var chicago = City(name:"Chicago", country: country)
chicago.country.name
will fix it because our coutry variable will keep USA from deallocating
If you use an unowned reference, you always have to keep a strong reference somewhere else.