- WANG Shudao
May is over, and revenue is generally declining.
- iOS: Decreased by 18%
- Domestic Android: Decreased by 7.2%
- Google Play: Decreased by 20%
- Admob: Increased by 3%
- Except for Admob, all other sources of income have reached their lowest values so far in 2023. Business is tough 😭
The only thing worth celebrating is that there were no refunds for domestic Android throughout May 😎.
This week I have been optimizing the diary list.
Let me first talk about the Core Data model of this app. Due to the addition and changes of images, it has gone through five versions.
When it was first launched, there were only text entries without images.
Entry can be added a single image, and the images were stored in iCloud Drive. A new attribute:
imageUrl was used to store the file location. However, this has three disadvantages:
- Slow image loading: If the image was not available locally, it had to be downloaded from iCloud before displaying.
- Users may accidentally delete these image files in iCloud Drive.
- The iCloud Drive feature had to be enabled to save these images.
The image is saved as an attribute in Core Data, the corresponding new attribute is a Binary Data:
This perfectly solves the problems in Version 2. The only trouble is that the old images need to be imported into Core Data.
Entry can have multiple images (up to 9). A new Binary Data attribute:
images has been added to save the binary array of images.
With this update, Minimal Diary achieved its final form in terms of displaying text and images, or at least that's what I think.
Road to Optimization
However, soon there were user feedback indicating that the app would crash when entering the diary list of certain tags. The reason was that there were too many and large images, causing memory issues.
I anticipated that this situation would eventually occur as the number of user diaries increased, but I didn't expect it to happen so soon. To address this, I made two optimizations.
Implemented pagination for the diary list, showing 20 entries per page. When scrolling to the bottom, a manual "Load More" button appears. However, this still led to crashes because the data loaded into memory would continue to increase.
Building on top of the pagination, I introduced a memory warning feature. The principle behind this feature is to calculate the average memory usage of the current 20 data entries and the remaining available memory. If the remaining memory is less than the memory occupied by 20 data entries, there is a possibility of a crash when loading the next set of data. To prevent this, I implemented a mechanism to release the top 20 data entries before loading the next set. When the user scrolls to the top, a prompt appears indicating to "pull down to load previous data." Upon pulling down, the previous 20 entries are loaded while releasing the bottom 20 entries.
Essentially, this approach adds an offset similar to a database query on top of the pagination. So far, it has been effective, and I haven't received any crash reports related to this aspect. However, there are a few drawbacks to this method:
- Inaccurate memory calculation.
- The need to maintain an offset, which adds an additional pull-down operation.
- The loading and releasing of data entries can cause visual displacement in the list.
Some entries with a large number of images caused the UI to freeze during loading. To address this, I implemented two optimizations, one of which turned out to be a negative optimization in hindsight. The positive optimization involved reducing the image size and processing them as thumbnails during loading. However, handling thumbnails increased the loading time for diary entries. Consequently, I changed the loading of diary data to asynchronous loading using Swift's async/await for the first time. Since these changes affected the underlying data of all pages, it required an extensive refactoring of the diary data section throughout the entire project. Unfortunately, despite the increased code complexity, there was no significant performance improvement, and it created numerous pitfalls for future optimization work.
During the previous optimization process, I received assistance from fatbobman(). Later, other developers in fatbobman's Discord community also discussed similar issues, leading fatbobman to write an article titled "The Memory Usage Optimization Journey of a SwiftUI + Core Data App", which provided an ultimate solution.
Version 5+Optimization 4
This week all my work is based on this article to optimize the app.
First, the images are separated from the diaries and placed in an Entity associated with the diary. The change in the data model again requires rewriting all the previous code related to images, and solves the problems left by the previous asynchronous at the same time. Currently, the CRUD of the data is working, and the memory problem has been greatly improved. Because audio and video may be added in the future, the Entity newly built for images this time is specially added with several general attributes, hoping not to change the Entity in the future.
"Memory Usage Optimization Journey of SwiftUI + Core Data App",https://www.fatbobman.com/posts/memory-usage-optimization/