Problem Overview
While developing an iOS music player app, I encountered an issue where adding a song to a new playlist inadvertently removed it from its original playlist. This occurred due to incorrect management of bi-directional relationships between songs and playlists.
The relationship setup:
- A
MusicModel
represents a song and has aplaylists
property linking to the playlists it belongs to. - A
PlaylistModel
represents a playlist and has asongs
property linking to its songs.
Original Code
Adding Songs to a Playlist
private func addSelectedSongs() {
let songsToAdd = allSongs.filter { selectedSongs.contains($0.id) }
for song in songsToAdd {
playlist.songs.append(song)
song.playlists.append(playlist)
}
playlist.updatedAt = Date()
try? modelContext.save()
}
Removing Songs from a Playlist
private func removeSong(_ song: MusicModel) {
playlist.songs.removeAll { $0.id == song.id }
song.playlists.removeAll { $0.id == playlist.id }
playlist.updatedAt = Date()
try? modelContext.save()
}
Issue with the Original Code
- Direct Modification of Relationships: Adding a playlist to a song (
song.playlists.append(playlist)
) unintentionally overwrote existing playlist associations.
Updated Code
Updated addSelectedSongs
private func addSelectedSongs() {
let songsToAdd = allSongs.filter { selectedSongs.contains($0.id) }
for song in songsToAdd {
// Ensure the song is not already in the playlist
if !playlist.songs.contains(where: { $0.id == song.id }) {
playlist.songs.append(song)
}
// Ensure the playlist is not already in the song
if !song.playlists.contains(where: { $0.id == playlist.id }) {
song.playlists.append(playlist)
}
}
playlist.updatedAt = Date()
try? modelContext.save()
}
Updated removeSong
private func removeSong(_ song: MusicModel) {
// Remove the song from the current playlist
playlist.songs.removeAll { $0.id == song.id }
// Remove the current playlist from the song
song.playlists.removeAll { $0.id == playlist.id }
playlist.updatedAt = Date()
try? modelContext.save()
}
Idea to fix the issue
- Preserves Existing Associations:
- The updated
addSelectedSongs
method checks if the song or playlist already exists in the relationship before appending. This prevents overwriting existing associations.
- The updated
- Scoped Removal:
- The updated
removeSong
method only removes the current playlist from the song’splaylists
, ensuring other playlist associations remain intact.
- The updated
- Ensures Data Integrity:
- The changes ensure that a song can belong to multiple playlists simultaneously without any unintended side effects.