{"id":33893,"date":"2021-10-12T17:01:45","date_gmt":"2021-10-12T21:01:45","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=33893"},"modified":"2022-05-05T13:45:36","modified_gmt":"2022-05-05T17:45:36","slug":"download-progress-with-awaited-network-tasks","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2021\/10\/12\/download-progress-with-awaited-network-tasks\/","title":{"rendered":"Download Progress With Awaited Network Tasks"},"content":{"rendered":"<p><a href=\"https:\/\/khanlou.com\/2021\/10\/download-progress-with-awaited-network-tasks\/\">Soroush Khanlou<\/a>:<\/p>\n<blockquote cite=\"https:\/\/khanlou.com\/2021\/10\/download-progress-with-awaited-network-tasks\/\">\n<p>One would think the <code>URLSessionTaskDelegate<\/code> would have some affordance that calls you back when new bytes come in, but if that exists, I couldn&rsquo;t find it.<\/p>\n<p>However, iOS 15 brings a new API that can be used for this purpose &mdash; a byte-by-byte asynchronous for loop that can do something every time a new byte comes in from the network &mdash;&nbsp;called <code>AsyncBytes<\/code>.<\/p>\n<p>[&#8230;]<\/p>\n<p>One question that this API raises: why does it call your block with every byte? The (very) old <code>NSURLConnectionDelegate<\/code> would give you updates with chunks of data, so why the change?<\/p>\n<p>[&#8230;]<\/p>\n<p>I think this example highlights something important about this new API. The file I was trying to download was about 20MB. That means my for loop is going to spin 20 million times. Because of that, it&rsquo;s extremely sensitive to any slow tasks that take place in the loop.<\/p>\n<p>[&#8230;]<\/p>\n<p>The last thing I did, which did work, is to keep a local variable for the progress, and then only update the progress in SwiftUI when the <code>fastRunningProgress<\/code> had advanced by a percent.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/twitter.com\/jl_hfl\/status\/1427302568896745472\">Joseph Lord<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/jl_hfl\/status\/1427302568896745472\">\n<p>For the record <code>AsyncSequence<\/code> blazing fast in beta 5. &#x1F3CE;&#x2764;&#xFE0F; Quicker to XOR the bytes in the file with <code>AsyncSequence<\/code> of bytes than to <code>Data(contentsOf:)<\/code> and iterate over the unsafe bytes<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/twitter.com\/Catfish_Man\/status\/1401617226168369152\">David Smith<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/Catfish_Man\/status\/1401617226168369152\">\n<p>I think one of the biggest things people are going to be startled by in my async Swift code is how often I write async functions that aren&rsquo;t (usually) asynchronous. It took me a few months and some advice from a coworker to internalize how useful this is.<\/p>\n<p>[&#8230;]<\/p>\n<p>The most obvious case this is useful is if you have some sort of cache: check the cache, if you hit do the thing immediately, if you miss, suspend and do the thing once you&rsquo;ve loaded what you needed into the cache.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/twitter.com\/Catfish_Man\/status\/1401983967042031622\">David<\/a> <a href=\"https:\/\/twitter.com\/Catfish_Man\/status\/1402371766584975362\">Smith<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/Catfish_Man\/status\/1402371766584975362\">\n<p>For <code>bytes<\/code>, <code>next()<\/code> looks approximately like this&#8230;<\/p>\n<pre>@inline(__always) @inlinable mutating func next() async -> UInt8 {\n    if bufferRange.isEmpty { await refillBuffer() \/* does suspend *\/ }\n    return buffer[bufferRange.popFirst()] \/* doesn't suspend *\/\n}<\/pre>\n<\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2021\/09\/21\/swift-5-5-released\/\">Swift 5.5 Released<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2021\/01\/20\/swift-asyncsequence\/\">Swift AsyncSequence<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Soroush Khanlou: One would think the URLSessionTaskDelegate would have some affordance that calls you back when new bytes come in, but if that exists, I couldn&rsquo;t find it. However, iOS 15 brings a new API that can be used for this purpose &mdash; a byte-by-byte asynchronous for loop that can do something every time a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2021-10-12T21:01:49Z","apple_news_api_id":"3e4b2c63-f7e0-497a-9a3a-f06d7e46fc14","apple_news_api_modified_at":"2022-05-05T17:45:45Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAg==","apple_news_api_share_url":"https:\/\/apple.news\/APkssY_fgSXqaOvBtfkb8FA","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[69,31,2078,30,2077,476,138,71,2200,901],"class_list":["post-33893","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-ios","tag-ios-15","tag-mac","tag-macos-12","tag-networking","tag-optimization","tag-programming","tag-swift-concurrency","tag-swift-programming-language"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/33893","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=33893"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/33893\/revisions"}],"predecessor-version":[{"id":33895,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/33893\/revisions\/33895"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=33893"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=33893"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=33893"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}