mhdhejazi / CoronaTracker

Coronavirus tracker app for iOS & macOS with maps & charts
https://coronatracker.samabox.com/
GNU General Public License v3.0
1.54k stars 290 forks source link

iOS 14 Widget #127

Open maxbrogno opened 4 years ago

maxbrogno commented 4 years ago

Any chance someone is working on a new iOS 14 home screen widget for the app? Would love to be able to have my local numbers easily visible.

mhdhejazi commented 4 years ago

Good idea. It'd be a great chance for learning too. I hope somebody jumps in and works on this.

gklka commented 3 years ago

I'd love to have this one too.

rhcpfan commented 3 years ago

Hello there!

I've started working on some iOS 14 widgets for CoronaTracker, but I've hit a problem: the peak memory usage for a widget cannot exceed 30 Mb.

Currently, getting all the data from DataManager.shared.download takes the memory usage to ~39 Mb. I've stripped out getting time series data from JHU (JHURepoDataService.shared.fetchTimeSerieses) and managed to reduce the memory footprint to ~ 31 Mb.

@mhdhejazi , is there a simple way to fetch some basic data (ex. just the dailyChange) for a single Region?

P.S: This is also the reason why the existing Today Extension is not working any more. P.P.S: Here's a sneak peak on how they currently look (the simulator does not have this limitation) 😉

gklka commented 3 years ago

Looks great!

rhcpfan commented 3 years ago

I think I managed to solve the issue...

By looking at memory allocations, I've found that JHURepoDataService.parseTimeSerieses(data:, completion:) had the heaviest memory footprint. This was mainly because it took all 270 header values that represented time series dates and stored them as strings. Then, for every value in every time series (probably hundreds of thousands) called

let dateStrings = headers.dropFirst(4)
if let date = dateFormatter.date(from: dateString) {
    for confirmedTimeSeries in confirmed {
        for column in confirmedTimeSeries.values.indices {
            let dateString = dateStrings[dateStrings.startIndex + column]
            if let date = dateFormatter.date(from: dateString) {
                ...
            }
        }
    }
}

All the calls to .date(from: dateString) increase a lot the amount of memory used (and computation time).

The solution was to parse only the header values as dates and use them later in the process (this also comes with a great performance improvement):

let dateStrings = headers.dropFirst(4)
let dateValues = dateStrings.compactMap({ dateFormatter.date(from: $0) })
if let date = dateFormatter.date(from: dateString) {
    for confirmedTimeSeries in confirmed {
        for column in confirmedTimeSeries.values.indices {
            let dateString = dateStrings[dateStrings.startIndex + column]
            let date = dateValues[column]
            ...
        }
    }
}

Managed to reduce the memory used by a widget to ~15Mb from ~39Mb 👍

I still have to do a bit of more UI polishing and create a pull request (most likely tomorrow).

mhdhejazi commented 3 years ago

Wow, great work there and a nice catch. When I created the app the dataset was relatively small (both horizontally and vertically), so I didn't see the memory and performance issue. Thank you for fixing that.

Another idea to improve performance and memory usage could be by only parsing the data relevant to the current region. It could be challenging to do that without a big refactoring, but you can take a look at it.

rhcpfan commented 3 years ago

@mhdhejazi - just made a PR that only addresses the memory reduction stuff (https://github.com/mhdhejazi/CoronaTracker/pull/133)

FelikZ commented 3 years ago

it seems its solved in https://github.com/mhdhejazi/CoronaTracker/pull/134 ?