{"id":9841,"date":"2026-08-30T15:15:00","date_gmt":"2026-08-30T12:15:00","guid":{"rendered":"https:\/\/handoli.com\/index.php\/2026\/08\/30\/why-swift-is-introducing-a-warning-for-weak-captures-within-nested-closures\/"},"modified":"2026-09-02T00:02:01","modified_gmt":"2026-09-01T21:02:01","slug":"why-swift-is-introducing-a-warning-for-weak-captures-within-nested-closures","status":"publish","type":"post","link":"https:\/\/handoli.com\/index.php\/2026\/08\/30\/why-swift-is-introducing-a-warning-for-weak-captures-within-nested-closures\/","title":{"rendered":"Why Swift is introducing a warning for weak captures within nested closures"},"content":{"rendered":"<p>New in Swift 6.4 (which at the time of writing is in beta as part of Xcode 27) is a warning which is emitted when a nested closure performs a <code>weak<\/code> capture while the parent closure implicitly captures that object as a <em>strong reference<\/em>.<\/p>\n<p>At first glance, that new diagnostic could be a bit puzzling, since it might seem unnecessary to have to specify such a weak capture twice (or, alternatively, move it to the parent closure\u2019s capture list), but it turns out that it\u2019s an incredibly useful new warning that can help us avoid a quite common kind of memory management bug.<\/p>\n<p>Take the following code as an example. It performs a nested <code>weak self<\/code> capture within the closure passed to the call to <code>loadNearbyFriends<\/code>, while not (explicitly) capturing <code>self<\/code> at all within the parent closure:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">@MainActor final class<\/span> NearbyFriendsViewModel {\n    <span class=\"s-keyword\">private(set) var<\/span> friends = [<span class=\"s-type\">Friend<\/span>]()\n\n    <span class=\"s-keyword\">private var<\/span> timer: <span class=\"s-type\">Timer<\/span>?\n    <span class=\"s-keyword\">private let<\/span> service: <span class=\"s-type\">FriendsService<\/span>\n    \n    ...\n\n    <span class=\"s-keyword\">@MainActor deinit<\/span> {\n        timer?.<span class=\"s-call\">invalidate<\/span>()\n    }\n\n    <span class=\"s-keyword\">func<\/span> startScanning() {\n        timer = <span class=\"s-type\">Timer<\/span>.<span class=\"s-call\">scheduledTimer<\/span>(\n            withTimeInterval: <span class=\"s-number\">3<\/span>,\n            repeats: <span class=\"s-keyword\">true<\/span>,\n            block: { [service] <span class=\"s-keyword\">_ in<\/span>\n                service.<span class=\"s-call\">loadNearbyFriends<\/span> { [<span class=\"s-keyword\">weak self<\/span>] friends <span class=\"s-keyword\">in\n                    self<\/span>?.<span class=\"s-property\">friends<\/span> = friends\n                }\n            }\n        )\n    }\n}<\/code><\/pre>\n<p>When using Xcode 26 and Swift 6.3, the above code is successfully compiled without any warnings. However, it\u2019s actually causing our <code>NearbyFriendsViewModel<\/code> to be trapped in a retain cycle, which might seem quite strange given that we haven\u2019t performed any strong captures of <code>self<\/code> \u2014 at least not <em>explicitly<\/em>.<\/p>\n<p>However, it turns out that when we perform a <code>weak self<\/code> capture within a nested closure (like we do above), then the Swift compiler implicitly inserts a <em>strong<\/em> <code>self<\/code> capture within the parent closure unless it performs an explicit capture of its own. Within the above <code>NearbyFriendsViewModel<\/code> example, that means that our <code>Timer<\/code> actually captures <code>self<\/code>, which in turn retains that object through its <code>timer<\/code> property, and there we go \u2014 a classic retain cycle.<\/p>\n<p>If we instead switch to Xcode 27 and Swift 6.4, then we can see that we now actually get a warning when attempting to compile the above code:<\/p>\n<pre><code class=\"no-highlight\">'weak' ownership of capture 'self' differs from implicitly-captured\nstrong reference in outer scope<\/code><\/pre>\n<p>That\u2019s great, because it clearly shows us where the problem is. So, let\u2019s go ahead and fix it. In this specific case, since we\u2019re not actually using <code>self<\/code> within the outer closure, we can simply move our <code>weak self<\/code> capture to that closure instead:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">@MainActor final class<\/span> NearbyFriendsViewModel {\n    ...\n\n    <span class=\"s-keyword\">func<\/span> startScanning() {\n        timer = <span class=\"s-type\">Timer<\/span>.<span class=\"s-call\">scheduledTimer<\/span>(\n            withTimeInterval: <span class=\"s-number\">3<\/span>,\n            repeats: <span class=\"s-keyword\">true<\/span>,\n            block: { [<span class=\"s-keyword\">weak self<\/span>, service] <span class=\"s-keyword\">_ in<\/span>\n                service.<span class=\"s-call\">loadNearbyFriends<\/span> { friends <span class=\"s-keyword\">in\n                    self<\/span>?.<span class=\"s-property\">friends<\/span> = friends\n                }\n            }\n        )\n    }\n}<\/code><\/pre>\n<p>That successfully breaks our retain cycle, and resolves the warning that the Swift 6.4 compiler previously gave us.<\/p>\n<p>Another option would be to perform two separate <code>weak self<\/code> captures, which would be a good solution if our outer closure actually needed to use <code>self<\/code> in any way, such as in this case:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">@MainActor final class<\/span> NearbyFriendsViewModel {\n    ...\n\n    <span class=\"s-keyword\">func<\/span> startScanning() {\n        timer = <span class=\"s-type\">Timer<\/span>.<span class=\"s-call\">scheduledTimer<\/span>(\n            withTimeInterval: <span class=\"s-number\">3<\/span>,\n            repeats: <span class=\"s-keyword\">true<\/span>,\n            block: { [<span class=\"s-keyword\">weak self<\/span>] <span class=\"s-keyword\">_ in\n                guard let self else<\/span> { <span class=\"s-keyword\">return<\/span> }\n\n                <span class=\"s-call\">logScanningDidStart<\/span>()\n\n                service.<span class=\"s-call\">loadNearbyFriends<\/span> { [<span class=\"s-keyword\">weak self<\/span>] friends <span class=\"s-keyword\">in\n                    self<\/span>?.<span class=\"s-property\">friends<\/span> = friends\n                }\n            }\n        )\n    }\n    \n    <span class=\"s-keyword\">private nonisolated func<\/span> logScanningDidStart() {\n        ...\n    }\n}<\/code><\/pre>\n<p>Finally, in situations where capturing <code>self<\/code> strongly doesn\u2019t actually lead to a retain cycle, we can silence the new warning for nested <code>weak<\/code> captures by explicitly adding <code>self<\/code> to the outer closure\u2019s capture list, such as when creating a <code>Task<\/code> within the following example:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">@MainActor final class<\/span> NearbyFriendsViewModel {\n    ...\n    <span class=\"s-keyword\">private let<\/span> cache: <span class=\"s-type\">FriendsCache<\/span>\n    ...\n\n    <span class=\"s-keyword\">func<\/span> reloadIfNeeded() {\n        <span class=\"s-type\">Task<\/span> { [<span class=\"s-keyword\">self<\/span>] <span class=\"s-keyword\">in<\/span>\n            <span class=\"s-keyword\">guard await<\/span> !cache.<span class=\"s-property\">isValid<\/span> <span class=\"s-keyword\">else<\/span> { <span class=\"s-keyword\">return<\/span> }\n\n            service.<span class=\"s-call\">loadNearbyFriends<\/span> { [<span class=\"s-keyword\">weak self<\/span>] friends <span class=\"s-keyword\">in\n                self<\/span>?.<span class=\"s-property\">friends<\/span> = friends\n            }\n        }\n    }\n}<\/code><\/pre>\n<p class=\"info\">Above it\u2019s perfectly fine to capture <code>self<\/code> strongly within our task\u2019s closure, since once the cache check has been performed and our call to <code>loadNearbyFriends<\/code> has been dispatched, then that closure will be deallocated.<\/p>\n<p>It\u2019s great to see new diagnostics added to Swift that causes warnings to be omitted for common mistakes, anti-patterns and potential memory issues. Although such diagnostics might require us to have to slightly <em>over-specify<\/em> our code in certain places, requiring us to be explicit about potentially problematic <code>self<\/code> captures is definitely a good thing, since it\u2019s such a common source of retain cycles and other memory management bugs.<\/p>\n<p>Thanks for reading!<\/p>","protected":false},"excerpt":{"rendered":"<p>New in Swift 6.4 (which at the time of writing is in beta as part of Xcode 27) is a warning which is emitted when a nested closure performs a weak capture while the parent closure implicitly captures that object as a strong reference. At first glance, that new diagnostic could be a bit puzzling, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":9891,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rop_custom_images_group":[],"rop_custom_messages_group":[],"rop_publish_now":"initial","rop_publish_now_accounts":[],"rop_publish_now_history":[],"rop_publish_now_status":"pending","footnotes":""},"categories":[1,15],"tags":[],"class_list":["post-9841","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-explore","category-world"],"_links":{"self":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9841","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/comments?post=9841"}],"version-history":[{"count":1,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9841\/revisions"}],"predecessor-version":[{"id":9892,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9841\/revisions\/9892"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/media\/9891"}],"wp:attachment":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/media?parent=9841"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/categories?post=9841"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/tags?post=9841"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}