diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index c3cfdb2f6..348bc558a 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -164,6 +164,7 @@ 838F851C256B4AC400C3E614 /* icon_blank.icns in Resources */ = {isa = PBXBuildFile; fileRef = 838F851B256B4AC400C3E614 /* icon_blank.icns */; }; 838F851E256B4E5E00C3E614 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838F851D256B4E5E00C3E614 /* Sparkle.framework */; }; 838F851F256B4E8B00C3E614 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 838F851D256B4E5E00C3E614 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 83988F0E27BE0A5900A0E89A /* RedundantPlaylistDataStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 83988F0D27BE0A5900A0E89A /* RedundantPlaylistDataStore.m */; }; 8399D4E21805A55000B503B1 /* XmlContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399D4E01805A55000B503B1 /* XmlContainer.m */; }; 839DA7CF274A2D4C001B18E5 /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 839DA7CE274A2D4C001B18E5 /* NSDictionary+Merge.m */; }; 83A360B220E4E81D00192DAB /* Flac.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8303A30C20E4E3D000951EF8 /* Flac.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -974,6 +975,8 @@ 838F84FF25687C5C00C3E614 /* Cog-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Cog-Bridging-Header.h"; sourceTree = ""; }; 838F851B256B4AC400C3E614 /* icon_blank.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon_blank.icns; sourceTree = ""; }; 838F851D256B4E5E00C3E614 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = ThirdParty/Frameworks/Sparkle.framework; sourceTree = ""; }; + 83988F0C27BE0A5900A0E89A /* RedundantPlaylistDataStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RedundantPlaylistDataStore.h; sourceTree = ""; }; + 83988F0D27BE0A5900A0E89A /* RedundantPlaylistDataStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RedundantPlaylistDataStore.m; sourceTree = ""; }; 8399D4E01805A55000B503B1 /* XmlContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XmlContainer.m; sourceTree = ""; }; 8399D4E11805A55000B503B1 /* XmlContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XmlContainer.h; sourceTree = ""; }; 839DA7CB274A2D4C001B18E5 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Merge.h"; sourceTree = ""; }; @@ -1204,6 +1207,8 @@ 177EC01D0B8BC2CF0000BC8C /* TrackingSlider.m */, 8370D739277419D200245CE0 /* SQLiteStore.h */, 8370D73C277419F700245CE0 /* SQLiteStore.m */, + 83988F0C27BE0A5900A0E89A /* RedundantPlaylistDataStore.h */, + 83988F0D27BE0A5900A0E89A /* RedundantPlaylistDataStore.m */, ); path = Utils; sourceTree = ""; @@ -2511,6 +2516,7 @@ 179D03200E0CB2500064A77A /* DirectoryNode.m in Sources */, 179D03210E0CB2500064A77A /* FileIconCell.m in Sources */, 179D03220E0CB2500064A77A /* FileNode.m in Sources */, + 83988F0E27BE0A5900A0E89A /* RedundantPlaylistDataStore.m in Sources */, 179D03230E0CB2500064A77A /* FileTreeDataSource.m in Sources */, 179D03240E0CB2500064A77A /* FileTreeController.m in Sources */, 8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */, diff --git a/Playlist/PlaylistLoader.m b/Playlist/PlaylistLoader.m index d4ac0ea13..2a0d65e3f 100644 --- a/Playlist/PlaylistLoader.m +++ b/Playlist/PlaylistLoader.m @@ -34,6 +34,8 @@ #import "NSDictionary+Merge.h" +#import "RedundantPlaylistDataStore.h" + @implementation PlaylistLoader - (id)init { @@ -540,9 +542,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc NSLock *outLock = [[NSLock alloc] init]; NSMutableArray *outArray = [[NSMutableArray alloc] init]; + RedundantPlaylistDataStore *dataStore = [[RedundantPlaylistDataStore alloc] init]; __block NSLock *weakLock = outLock; __block NSMutableArray *weakArray = outArray; + __block RedundantPlaylistDataStore *weakDataStore = dataStore; { [load_info_indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *_Nonnull stop) { @@ -566,6 +570,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:entryMetadata]; [weakLock lock]; + entryInfo = [weakDataStore coalesceEntryInfo:entryInfo]; [weakArray addObject:weakPe]; [weakArray addObject:entryInfo]; [self setProgressBarStatus:progress]; diff --git a/Utils/RedundantPlaylistDataStore.h b/Utils/RedundantPlaylistDataStore.h new file mode 100644 index 000000000..e31b49cfd --- /dev/null +++ b/Utils/RedundantPlaylistDataStore.h @@ -0,0 +1,27 @@ +// +// RedundantPlaylistDataStore.h +// Cog +// +// Created by Christopher Snowhill on 2/16/22. +// + +// This is designed primarily for the PlaylistEntry info loader, to prevent +// memory overrun due to redundant blobs of data being duplicated repeatedly +// until the list is fully loaded. This instance will be discarded after the +// info is loaded, freeing up hopefully way less memory afterward than now. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RedundantPlaylistDataStore : NSObject { + NSMutableArray *stringStore; + NSMutableArray *artStore; +} + +- (id)init; +- (NSDictionary *)coalesceEntryInfo:(NSDictionary *)pe; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Utils/RedundantPlaylistDataStore.m b/Utils/RedundantPlaylistDataStore.m new file mode 100644 index 000000000..8118eb3e9 --- /dev/null +++ b/Utils/RedundantPlaylistDataStore.m @@ -0,0 +1,68 @@ +// +// RedundantPlaylistDataStore.m +// Cog +// +// Created by Christopher Snowhill on 2/16/22. +// + +// Coalesce an entryInfo dictionary from tag loading into a common data dictionary, to +// reduce the memory footprint of adding a lot of tracks to the playlist. + +#import "RedundantPlaylistDataStore.h" + +@implementation RedundantPlaylistDataStore + +- (id)init { + self = [super init]; + + if(self) { + stringStore = [[NSMutableArray alloc] init]; + artStore = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (NSString *)coalesceString:(NSString *)in { + if(in == nil) return in; + + NSUInteger index = [stringStore indexOfObject:in]; + if(index == NSNotFound) { + [stringStore addObject:in]; + return in; + } else { + return [stringStore objectAtIndex:index]; + } +} + +- (NSData *)coalesceArt:(NSData *)in { + if(in == nil) return in; + + NSUInteger index = [artStore indexOfObject:in]; + if(index == NSNotFound) { + [artStore addObject:in]; + return in; + } else { + return [artStore objectAtIndex:index]; + } +} + +- (NSDictionary *)coalesceEntryInfo:(NSDictionary *)entryInfo { + __block NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithCapacity:[entryInfo count]]; + + [entryInfo enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) { + if([[obj class] isSubclassOfClass:[NSString class]]) { + NSString *stringObj = (NSString *)obj; + [ret setObject:[self coalesceString:stringObj] forKey:key]; + } else if([[obj class] isSubclassOfClass:[NSData class]]) { + NSData *dataObj = (NSData *)obj; + [ret setObject:[self coalesceArt:dataObj] forKey:key]; + } else { + [ret setObject:obj forKey:key]; + } + }]; + + return [NSDictionary dictionaryWithDictionary:ret]; +} + +@end