mergesort / Boutique

✨ A magical persistence library (and so much more) for state-driven iOS and Mac apps ✨
https://build.ms/boutique/docs
MIT License
899 stars 43 forks source link

[Question] store.items empty at startup #48

Closed flexaddicted closed 1 year ago

flexaddicted commented 1 year ago

Hello,

I'm using the following code in order to publish a Bool value that represents the fact a NewsLetter has been bookmarked or not.

@MainActor
final class DetailViewModel: ObservableObject {
    @Published private(set) var bookmarked = false

    @Stored(in: .newsLetterStore) var newsLetters

    func initializeBookmark(_ newsLetter: NewsLetter) {
        print("Initially Store contains \($newsLetters.items)")
        bookmarked = $newsLetters.items.contains(newsLetter)
    }

    // Other code here
}

I'm noticing that at startup items is empty and gets populated in a later stage. I think the issue related to https://github.com/mergesort/Boutique/issues/16 but now sure how to fix it.

initializeBookmark is called every time the user enters the detail page of a newsletter and a bookmark icon needs to change its state.

Any suggestion? Lorenzo

mergesort commented 1 year ago

Hey @flexaddicted, if I'm understanding your question the latest release of Boutique should offer a solution. I think the problem you're running into is that initially the Store is empty, and so the initial state of it isn't what you'd expect it to be. At the time of #16 Boutique has no async initializer for Store, but now there is one to help resolve the ambiguity between "no items have loaded" and "your items loaded and the state is some default".

If you use the async initializer and await itemsHaveLoaded, you should be able to get an answer in your view model for whether an item is truly bookmarked at the time you're trying to render it. You can check out the sample code in the link I provided for how that property can be used.

Hope that makes sense and helps, let me know if I've understood the problem correctly!

flexaddicted commented 1 year ago

Hello @mergesort, thank you very much for your reply.

First of all, let me say your work is really cool and I love Boutique.

Yes, this is what I was looking for. I'll refactor a little bit in order to accomodate this new API.

I have just one additional question: Awaiting for itemsHaveLoaded or for init methods means that items have been loaded entirely in memory. Do you think it could be possible to have performance downsides if the number of elements becomes big (right now I cannot quantify "big")?

Thanks, Lorenzo

mergesort commented 1 year ago

Thanks a lot @flexaddicted, that's really kind of you to say and I'm glad to hear you're enjoying Boutique!

To answer your question, the answer really depends on the size of your models. One thing I've noted in the readme and in this discussion is that I would not be loading binary data (images, files, data) into Boutique, at least not in an excessive manner. For example it's not wise to use Boutique to Store a timeline of 1000 images, because as you note, those images would have to be loaded into memory.

But otherwise I haven't seen any reasonable performance issues. Let's say we take a complex model like a tweet, as provided by Twitter.

{
  "data": [
    {
      "conversation_id": "1304102743196356610",
      "id": "1307025659294674945",
      "possibly_sensitive": false,
      "public_metrics": {
        "retweet_count": 11,
        "reply_count": 2,
        "like_count": 70,
        "quote_count": 1
      },
      "entities": {
        "urls": [
          {
            "start": 74,
            "end": 97,
            "url": "https://t.co/oeF3ZHeKQQ",
            "expanded_url": "https://dev.to/twitterdev/understanding-the-new-tweet-payload-in-the-twitter-api-v2-1fg5",
            "display_url": "dev.to/twitterdev/und…",
            "images": [
              {
                "url": "https://pbs.twimg.com/news_img/1317156296982867969/2uLfv-Bh?format=jpg&name=orig",
                "width": 1128,
                "height": 600
              },
              {
                "url": "https://pbs.twimg.com/news_img/1317156296982867969/2uLfv-Bh?format=jpg&name=150x150",
                "width": 150,
                "height": 150
              }
            ],
            "status": 200,
            "title": "Understanding the new Tweet payload in the Twitter API v2",
            "description": "Twitter recently announced the new Twitter API v2, rebuilt from the ground up to deliver new features...",
            "unwound_url": "https://dev.to/twitterdev/understanding-the-new-tweet-payload-in-the-twitter-api-v2-1fg5"
          }
        ]
      },
      "text": "Here’s an article that highlights the updates in the new Tweet payload v2 https://t.co/oeF3ZHeKQQ",
      "in_reply_to_user_id": "2244994945",
      "created_at": "2020-09-18T18:36:15.000Z",
      "author_id": "2244994945",
      "referenced_tweets": [
        {
          "type": "replied_to",
          "id": "1304102743196356610"
        }
      ],
      "lang": "en",
      "source": "Twitter Web App"
    }
  ],
  "includes": {
    "users": [
      {
        "created_at": "2013-12-14T04:35:55.000Z",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1283786620521652229/lEODkLTh_normal.jpg",
        "entities": {
          "url": {
            "urls": [
              {
                "start": 0,
                "end": 23,
                "url": "https://t.co/3ZX3TNiZCY",
                "expanded_url": "https://developer.twitter.com/en/community",
                "display_url": "developer.twitter.com/en/community"
              }
            ]
          },
          "description": {
            "hashtags": [
              {
                "start": 17,
                "end": 28,
                "tag": "TwitterDev"
              },
              {
                "start": 105,
                "end": 116,
                "tag": "TwitterAPI"
              }
            ]
          }
        },
        "id": "2244994945",
        "verified": true,
        "location": "127.0.0.1",
        "description": "The voice of the #TwitterDev team and your official source for updates, news, and events, related to the #TwitterAPI.",
        "pinned_tweet_id": "1293593516040269825",
        "username": "TwitterDev",
        "public_metrics": {
          "followers_count": 513961,
          "following_count": 2039,
          "tweet_count": 3635,
          "listed_count": 1672
        },
        "name": "Twitter Dev",
        "url": "https://t.co/3ZX3TNiZCY",
        "protected": false
      }
    ],
    "tweets": [
      {
        "conversation_id": "1304102743196356610",
        "id": "1304102743196356610",
        "possibly_sensitive": false,
        "public_metrics": {
          "retweet_count": 31,
          "reply_count": 12,
          "like_count": 104,
          "quote_count": 4
        },
        "entities": {
          "mentions": [
            {
              "start": 146,
              "end": 158,
              "username": "suhemparack"
            }
          ],
          "urls": [
            {
              "start": 237,
              "end": 260,
              "url": "https://t.co/CjneyMpgCq",
              "expanded_url": "https://twitter.com/TwitterDev/status/1304102743196356610/video/1",
              "display_url": "pic.twitter.com/CjneyMpgCq"
            }
          ],
          "hashtags": [
            {
              "start": 8,
              "end": 19,
              "tag": "TwitterAPI"
            }
          ]
        },
        "attachments": {
          "media_keys": [
            "13_1303848070984024065"
          ]
        },
        "text": "The new #TwitterAPI includes some improvements to the Tweet payload. You’re probably wondering — what are the main differences? 🧐\n\nIn this video, @SuhemParack compares the v1.1 Tweet payload with what you’ll find using our v2 endpoints. https://t.co/CjneyMpgCq",
        "created_at": "2020-09-10T17:01:37.000Z",
        "author_id": "2244994945",
        "lang": "en",
        "source": "Twitter Media Studio"
      }
    ]
  }
}

This payload is 4,915 bytes, so let's just call it 5KB to make the math easier. If you were to load 200 tweets that would be 1MB of data in ram, 2,000 that would be 10MB, 20,000 would be 100MB, and so on. You may have a more complex use case than loading 20,000 tweets, or more complex models that scale poorly, and in that case you'll have to use your best judgment. But I suspect that Boutique should handle 95-99% of the persistence use cases iOS/macOS apps have, even without Core Data style faulting.

Hope that helps!

flexaddicted commented 1 year ago

Hello @mergesort! Thank you very much for your reply.