[{"data":1,"prerenderedAt":3470},["ShallowReactive",2],{"navigation_docs":3,"-ios-ios-interview1":173,"-ios-ios-interview1-surround":3465},[4,22,29,36,107,138,145,161],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"博客","i-lucide-pen-line","\u002Fblog","0.blog",[10,12,17],{"title":5,"path":7,"stem":11,"icon":6},"0.blog\u002Findex",{"title":13,"path":14,"stem":15,"icon":16},"创建基于 UIKit 的原生 iOS 项目","\u002Fblog\u002Fcreate-uikit-ios-project","0.blog\u002F01.create-uikit-ios-project","i-lucide-app-window",{"title":18,"path":19,"stem":20,"icon":21},"Lockify：把账号和密码留在自己的手机里","\u002Fblog\u002Flockify","0.blog\u002F02.lockify","i-lucide-lock",{"title":23,"path":24,"stem":25,"children":26,"icon":28},"年度总结","\u002Fannual-review","1.annual-review\u002Findex",[27],{"title":23,"path":24,"stem":25,"icon":28},"i-lucide-git-commit",{"title":30,"path":31,"stem":32,"children":33,"icon":35},"故事","\u002Fstories","2.stories\u002Findex",[34],{"title":30,"path":31,"stem":32,"icon":35},"i-lucide-feather",{"title":37,"icon":38,"path":39,"stem":40,"children":41},"Android","i-simple-icons-android","\u002Fandroid","3.android",[42,44,49],{"title":37,"path":39,"stem":43,"icon":38},"3.android\u002Findex",{"title":45,"path":46,"stem":47,"icon":48},"初探Navigation","\u002Fandroid\u002Fcompose-navigation","3.android\u002F01.compose-navigation","i-lucide-route",{"title":50,"icon":51,"path":52,"stem":53,"children":54},"安卓代码加固","i-lucide-shield","\u002Fandroid\u002Fhidden-hardening","3.android\u002F02.hidden-hardening\u002Findex",[55,58,62,67,72,77,82,87,92,97,102],{"title":56,"path":52,"stem":53,"icon":57},"安卓代码加固：总览","i-lucide-map",{"title":59,"path":60,"stem":61,"icon":21},"安卓代码加固（1）：加密DEX","\u002Fandroid\u002Fhidden-hardening\u002Fencrypt-dex","3.android\u002F02.hidden-hardening\u002F01.encrypt-dex",{"title":63,"path":64,"stem":65,"icon":66},"安卓代码加固（2）：Native解密","\u002Fandroid\u002Fhidden-hardening\u002Fnative-decrypt","3.android\u002F02.hidden-hardening\u002F02.native-decrypt","i-lucide-cpu",{"title":68,"path":69,"stem":70,"icon":71},"安卓代码加固（3）：内存加载","\u002Fandroid\u002Fhidden-hardening\u002Fmemory-load","3.android\u002F02.hidden-hardening\u002F03.memory-load","i-lucide-memory-stick",{"title":73,"path":74,"stem":75,"icon":76},"安卓代码加固（4）：指令分发","\u002Fandroid\u002Fhidden-hardening\u002Fopcode-dispatch","3.android\u002F02.hidden-hardening\u002F04.opcode-dispatch","i-lucide-hash",{"title":78,"path":79,"stem":80,"icon":81},"安卓代码加固（5）：XOR混淆","\u002Fandroid\u002Fhidden-hardening\u002Fxor-obfuscate","3.android\u002F02.hidden-hardening\u002F05.xor-obfuscate","i-lucide-shuffle",{"title":83,"path":84,"stem":85,"icon":86},"安卓代码加固（6）：密钥外置","\u002Fandroid\u002Fhidden-hardening\u002Fkey-external","3.android\u002F02.hidden-hardening\u002F06.key-external","i-lucide-key",{"title":88,"path":89,"stem":90,"icon":91},"安卓代码加固（7）：依赖隔离","\u002Fandroid\u002Fhidden-hardening\u002Fdep-isolate","3.android\u002F02.hidden-hardening\u002F07.dep-isolate","i-lucide-layers",{"title":93,"path":94,"stem":95,"icon":96},"安卓代码加固（8）：构建校验","\u002Fandroid\u002Fhidden-hardening\u002Fbuild-verify","3.android\u002F02.hidden-hardening\u002F08.build-verify","i-lucide-shield-check",{"title":98,"path":99,"stem":100,"icon":101},"安卓代码加固（9）：防录屏","\u002Fandroid\u002Fhidden-hardening\u002Fflag-secure","3.android\u002F02.hidden-hardening\u002F09.flag-secure","i-lucide-eye-off",{"title":103,"path":104,"stem":105,"icon":106},"安卓代码加固（10）：ABI门禁","\u002Fandroid\u002Fhidden-hardening\u002Fabi-gate","3.android\u002F02.hidden-hardening\u002F10.abi-gate","i-lucide-git-compare",{"title":108,"icon":109,"path":110,"stem":111,"children":112},"iOS","i-simple-icons-apple","\u002Fios","4.ios",[113,115,120,125,129,133],{"title":108,"path":110,"stem":114,"icon":109},"4.ios\u002Findex",{"title":116,"path":117,"stem":118,"icon":119},"iOS面试题1","\u002Fios\u002Fios-interview1","4.ios\u002F01.ios-interview1","i-lucide-sparkles",{"title":121,"path":122,"stem":123,"icon":124},"设置启动图","\u002Fios\u002Fset-launch-screen","4.ios\u002F02.set-launch-screen","i-lucide-image",{"title":126,"path":127,"stem":128,"icon":48},"路由管理","\u002Fios\u002Frouting","4.ios\u002F03.routing",{"title":130,"path":131,"stem":132,"icon":91},"状态管理","\u002Fios\u002Fstate-management","4.ios\u002F04.state-management",{"title":134,"path":135,"stem":136,"icon":137},"事件传递","\u002Fios\u002Fevent-delivery","4.ios\u002F05.event-delivery","i-lucide-pointer",{"title":139,"path":140,"stem":141,"children":142,"icon":144},"Flutter","\u002Fflutter","5.flutter\u002Findex",[143],{"title":139,"path":140,"stem":141,"icon":144},"i-simple-icons-flutter",{"title":146,"icon":147,"path":148,"stem":149,"children":150,"page":160},"面试","i-lucide-graduation-cap","\u002Finterview","6.interview",[151,156],{"title":152,"path":153,"stem":154,"icon":155},"Flutter面试题1","\u002Finterview\u002Fflutter-interview1","6.interview\u002F01.flutter-interview1","i-lucide-boxes",{"title":157,"path":158,"stem":159,"icon":66},"Flutter面试题2","\u002Finterview\u002Fflutter-interview2","6.interview\u002F02.flutter-interview2",false,{"title":162,"icon":163,"path":164,"stem":165,"children":166},"算法学习","i-lucide-binary","\u002Falgorithm","7.algorithm",[167,169],{"title":162,"path":164,"stem":168,"icon":163},"7.algorithm\u002Findex",{"title":170,"path":171,"stem":172},"算法基础1：复杂度分析","\u002Falgorithm\u002Ftime-space-complexity","7.algorithm\u002F01.time-space-complexity",{"id":174,"title":116,"body":175,"description":3443,"extension":3444,"links":3445,"meta":3453,"navigation":3460,"path":117,"seo":3461,"stem":118,"__hash__":3464},"docs\u002F4.ios\u002F01.ios-interview1.md",{"type":176,"value":177,"toc":3412},"minimark",[178,182,190,195,198,236,239,242,271,283,286,289,296,299,354,361,527,542,572,580,584,589,605,667,673,710,714,717,720,754,760,767,774,777,780,787,790,944,960,963,1120,1123,1130,1213,1219,1226,1384,1392,1411,1419,1452,1549,1561,1564,1567,1579,1585,1596,1717,1727,1737,1740,1743,1750,1808,1824,1841,1853,1903,1909,1912,1938,1941,1948,1952,1960,2071,2086,2104,2263,2285,2290,2516,2531,2534,2546,2550,2553,2663,2666,2669,2672,2678,2681,2684,2690,2693,2696,2832,2835,2838,2842,2848,2856,2860,2869,2880,2884,2892,2897,2901,2904,2968,2991,2998,3002,3008,3016,3020,3035,3048,3051,3063,3073,3076,3079,3169,3183,3187,3193,3202,3214,3217,3236,3246,3250,3260,3338,3342,3349,3352,3355,3358,3364,3373,3385,3398,3408],[179,180,181],"p",{},"这篇文章按「定义 + 场景 + 典型坑」记面试题。内存、线程、RunLoop、UI 渲染和架构后面接着补。",[179,183,184,185,189],{},"先记闭包。Swift 里叫 Closure，OC 里叫 Block。日常用 Swift，例子以 Swift 为主。OC 只留面试还会追问的三个词：三种 Block、copy、",[186,187,188],"code",{},"__block","。",[191,192,194],"h2",{"id":193},"怎么考怎么学","怎么考、怎么学",[179,196,197],{},"闭包这道题分五层。上一层能脱开笔记讲出来，再进下一层。",[199,200,201,209,218,224,230],"ol",{},[202,203,204,208],"li",{},[205,206,207],"strong",{},"基础层","：闭包是什么；Swift 的三种形态、逃逸和非逃逸；OC 的三种 Block 在内存的哪里，什么时候 copy。",[202,210,211,214,215,217],{},[205,212,213],{},"核心层","：变量怎么捕获；捕获列表和 ",[186,216,188],{}," 各做什么；循环引用为什么出现，怎么断。",[202,219,220,223],{},[205,221,222],{},"对比层","：和 Delegate、通知、函数指针 \u002F Target-Action 比，什么关系用哪个。",[202,225,226,229],{},[205,227,228],{},"底层深度层","：结构体、copy\u002Fdispose、forwarding、ARC 和 MRC 的差别。先认名词。细节折在文末，以后再补。",[202,231,232,235],{},[205,233,234],{},"工程场景层","：属性怎么声明；GCD、嵌套闭包、Timer、通知；对象释放后还去调用。",[179,237,238],{},"每一层准备三样东西：一句话、一段能自己敲出来的代码、一个坑。讲的时候先说结论，再说谁持有谁。循环引用在纸上画箭头，箭头表示强引用，两边都有箭头才是环。",[179,240,241],{},"复习就按这个顺序：",[199,243,244,247,254,265,268],{},[202,245,246],{},"盖住正文，按上面五层讲下面的二十秒版。",[202,248,249,250,253],{},"自己敲「捕获列表」和 ",[186,251,252],{},"makeStepper"," 两段代码，讲清输出。",[202,255,256,257,260,261,264],{},"画环，再讲 ",[186,258,259],{},"[weak self]","、",[186,262,263],{},"async","、Timer 三者的差别。",[202,266,267],{},"用「几个听众、要不要结果」选回调方式。",[202,269,270],{},"场景题先讲再对照。底层默认不讲，被问到再打开折叠。",[272,273,275,276,279,280,282],"tip",{"title":274},"二十秒版","闭包是一段能记住外部变量、并且能传来传去的代码，Swift 里是引用类型。先看逃不逃逸：存进属性或异步再调用就是逃逸，上下文在堆上。默认和外面共用变量；捕获列表是创建闭包时把值带进来。对象持有闭包，闭包又强引用 self，就是环，用 ",[186,277,278],{},"weak"," 断开闭包这边。丢给全局队列的 ",[186,281,263],{}," 只会把对象留到任务结束。一次回调用闭包，一组回调用 Delegate，广播用通知。",[191,284,285],{"id":285},"本质和类型",[179,287,288],{},"闭包是自带上下文的函数。定义它的那个环境里的变量，可以跟着它走，离开原作用域还能用。",[179,290,291,292,295],{},"Swift 里闭包是",[205,293,294],{},"引用类型","。两个变量指向同一个闭包时，捕获到的状态也是同一份。",[179,297,298],{},"语言里把它分成三种形态：",[300,301,302,318],"table",{},[303,304,305],"thead",{},[306,307,308,312,315],"tr",{},[309,310,311],"th",{},"形态",[309,313,314],{},"有没有名字",[309,316,317],{},"会不会捕获",[319,320,321,333,343],"tbody",{},[306,322,323,327,330],{},[324,325,326],"td",{},"全局函数",[324,328,329],{},"有",[324,331,332],{},"不捕获周围的变量",[306,334,335,338,340],{},[324,336,337],{},"嵌套函数",[324,339,329],{},[324,341,342],{},"可以捕获外层函数的变量",[306,344,345,348,351],{},[324,346,347],{},"闭包表达式",[324,349,350],{},"没有，写在调用处",[324,352,353],{},"可以捕获",[179,355,356,357,360],{},"日常写的是第三种。尾随闭包、",[186,358,359],{},"$0"," 只是写法，面试不问这个。",[362,363,368],"pre",{"className":364,"code":365,"language":366,"meta":367,"style":367},"language-swift shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","let double: (Int) -> Int = { $0 * 2 }\n\nfunc load(done: @escaping (Data) -> Void) {\n    done(Data())\n}\n\nload { data in\n    print(data.count)\n}\n","swift","",[186,369,370,418,425,469,482,488,493,508,522],{"__ignoreMap":367},[371,372,375,379,383,387,391,394,397,400,403,406,409,412,415],"span",{"class":373,"line":374},"line",1,[371,376,378],{"class":377},"sbssI","let",[371,380,382],{"class":381},"sTEyZ"," double: ",[371,384,386],{"class":385},"sMK4o","(",[371,388,390],{"class":389},"sBMFI","Int",[371,392,393],{"class":385},")",[371,395,396],{"class":385}," ->",[371,398,399],{"class":389}," Int",[371,401,402],{"class":385}," =",[371,404,405],{"class":385}," {",[371,407,408],{"class":381}," $0 ",[371,410,411],{"class":385},"*",[371,413,414],{"class":377}," 2",[371,416,417],{"class":385}," }\n",[371,419,421],{"class":373,"line":420},2,[371,422,424],{"emptyLinePlaceholder":423},true,"\n",[371,426,428,432,436,438,442,445,448,451,454,457,459,461,464,466],{"class":373,"line":427},3,[371,429,431],{"class":430},"spNyl","func",[371,433,435],{"class":434},"s2Zo4"," load",[371,437,386],{"class":385},[371,439,441],{"class":440},"sjYur","done",[371,443,444],{"class":381},": ",[371,446,447],{"class":385},"@",[371,449,450],{"class":430},"escaping",[371,452,453],{"class":385}," (",[371,455,456],{"class":381},"Data",[371,458,393],{"class":385},[371,460,396],{"class":385},[371,462,463],{"class":389}," Void",[371,465,393],{"class":385},[371,467,468],{"class":385}," {\n",[371,470,472,475,477,479],{"class":373,"line":471},4,[371,473,474],{"class":434},"    done",[371,476,386],{"class":385},[371,478,456],{"class":434},[371,480,481],{"class":385},"())\n",[371,483,485],{"class":373,"line":484},5,[371,486,487],{"class":385},"}\n",[371,489,491],{"class":373,"line":490},6,[371,492,424],{"emptyLinePlaceholder":423},[371,494,496,499,501,504],{"class":373,"line":495},7,[371,497,498],{"class":434},"load",[371,500,405],{"class":385},[371,502,503],{"class":381}," data ",[371,505,507],{"class":506},"s7zQu","in\n",[371,509,511,514,516,519],{"class":373,"line":510},8,[371,512,513],{"class":434},"    print",[371,515,386],{"class":385},[371,517,518],{"class":381},"data.count",[371,520,521],{"class":385},")\n",[371,523,525],{"class":373,"line":524},9,[371,526,487],{"class":385},[179,528,529,530,533,534,537,538,541],{},"面试更常把闭包分成",[205,531,532],{},"逃逸","和",[205,535,536],{},"非逃逸","。函数参数上的闭包默认非逃逸：这个函数返回之前就会把它调用完。要存进属性、异步再调、或者交给别人稍后用，参数标 ",[186,539,540],{},"@escaping","。写在属性上的函数类型本来就会留到函数外面，不用再标。",[179,543,544,545,548,549,551,552,554,555,558,559,562,563,189,566,568,569,571],{},"非逃逸闭包里访问自己的成员，可以不写 ",[186,546,547],{},"self","。逃逸闭包要写 ",[186,550,547],{},"，或者把 ",[186,553,547],{}," 放进捕获列表。编译器是在提醒：这里会把对象留住。捕获列表写成 ",[186,556,557],{},"[self]"," 或 ",[186,560,561],{},"[unowned self]"," 之后，体内访问成员可以不带 ",[186,564,565],{},"self.",[186,567,259],{}," 里的 ",[186,570,547],{}," 仍然是可选的。",[362,573,578],{"className":574,"code":576,"language":577,"meta":367},[575],"language-text","非逃逸                         逃逸\n函数返回前一定调用完            存进属性、异步、作为返回值\n上下文可以留在栈上              上下文放到堆上\n访问成员可以不写 self           要写 self，或放进捕获列表\n","text",[186,579,576],{"__ignoreMap":367},[581,582,583],"note",{},"面试点：Swift 先答逃不逃逸。对方要「三种类型」，再答全局函数、嵌套函数、闭包表达式。对方要是在说 Block，就改口答下面的 Global \u002F Stack \u002F Malloc。",[585,586,588],"h3",{"id":587},"oc-的三种-block","OC 的三种 Block",[179,590,591,592,595,596,260,599,260,602,189],{},"OC 按 Block ",[205,593,594],{},"当时住在哪","分类。调试器里的类名是 ",[186,597,598],{},"__NSGlobalBlock__",[186,600,601],{},"__NSStackBlock__",[186,603,604],{},"__NSMallocBlock__",[300,606,607,623],{},[303,608,609],{},[306,610,611,614,617,620],{},[309,612,613],{},"类型",[309,615,616],{},"在哪",[309,618,619],{},"什么时候是它",[309,621,622],{},"再 copy",[319,624,625,639,653],{},[306,626,627,630,633,636],{},[324,628,629],{},"Global Block",[324,631,632],{},"数据段",[324,634,635],{},"不用局部变量",[324,637,638],{},"还是它自己",[306,640,641,644,647,650],{},[324,642,643],{},"Stack Block",[324,645,646],{},"栈",[324,648,649],{},"用了局部变量，刚创建出来",[324,651,652],{},"搬到堆上，变成 Malloc Block",[306,654,655,658,661,664],{},[324,656,657],{},"Malloc Block",[324,659,660],{},"堆",[324,662,663],{},"Stack Block 被 copy 之后",[324,665,666],{},"引用计数加一",[179,668,669,670,672],{},"只用全局变量或静态变量，是 Global。一旦用到函数里的局部变量（包括 ",[186,671,547],{},"），创建时就是 Stack。",[362,674,678],{"className":675,"code":676,"language":677,"meta":367,"style":367},"language-objc shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","int n = 1;\nvoid (^block)(void) = ^{\n    NSLog(@\"%d\", n);\n};\n\u002F\u002F ARC：赋给 strong 变量时，编译器已经 copy，调试器里多半是 Malloc Block\n\u002F\u002F MRC：这里还是 Stack Block，函数返回后再调用，栈帧已经没了\n","objc",[186,679,680,685,690,695,700,705],{"__ignoreMap":367},[371,681,682],{"class":373,"line":374},[371,683,684],{},"int n = 1;\n",[371,686,687],{"class":373,"line":420},[371,688,689],{},"void (^block)(void) = ^{\n",[371,691,692],{"class":373,"line":427},[371,693,694],{},"    NSLog(@\"%d\", n);\n",[371,696,697],{"class":373,"line":471},[371,698,699],{},"};\n",[371,701,702],{"class":373,"line":484},[371,703,704],{},"\u002F\u002F ARC：赋给 strong 变量时，编译器已经 copy，调试器里多半是 Malloc Block\n",[371,706,707],{"class":373,"line":490},[371,708,709],{},"\u002F\u002F MRC：这里还是 Stack Block，函数返回后再调用，栈帧已经没了\n",[585,711,713],{"id":712},"什么时候-copy","什么时候 copy",[179,715,716],{},"copy 做的事：把栈上的 Block 复制到堆上，好在创建它的函数返回之后继续用。",[179,718,719],{},"这些时候会把栈 Block 复制到堆上：",[199,721,722,728,738,744,751],{},[202,723,724,725,189],{},"手动 ",[186,726,727],{},"copy",[202,729,730,731,734,735,737],{},"ARC 里赋给 ",[186,732,733],{},"__strong"," 变量，或者作为返回值。编译器会插入 copy。赋给 ",[186,736,205],{}," 属性时同样会 copy。",[202,739,740,741,743],{},"MRC 里赋给声明成 ",[186,742,727],{}," 的属性。",[202,745,746,747,750],{},"传给会存起来再用的接口。",[186,748,749],{},"dispatch_async"," 内部会 copy。动画完成、通知的 block 观察者，系统会把它留到回调发生。",[202,752,753],{},"被另一个正在 copy 的 Block 捕获。外层搬到堆上时，内层跟着 copy。",[179,755,756,757,759],{},"这次 copy 会把捕获的 ",[186,758,188],{}," 变量一起搬到堆上。",[179,761,762,763,766],{},"Global Block 再 copy，得到的仍是原来的指针。已经在堆上的 Malloc Block 再 copy，只是引用计数加一。只在当前函数里同步调用、没有存下来的 Stack Block，留在栈上，函数返回就结束。MRC 里属性写成 ",[186,764,765],{},"retain","，栈 Block 还停在栈上，函数返回后再调用就是野指针。",[179,768,769,770,773],{},"Swift 没有 ",[186,771,772],{},"Block_copy"," 这套 API。闭包要逃出当前函数时，编译器把上下文放到堆上，对应上面的 2、4、5。被捕获之后还要修改的局部变量，会放进堆上的一块存储（box），闭包和外面共用这一份。非逃逸闭包只在这次调用期间执行，闭包本身可以留在栈上。优化器要是能证明那块 box 不会逃出函数，也可以再放回栈。面试先讲：逃逸才必须上堆，可变捕获用 box。",[581,775,776],{},"面试点：copy 是栈搬堆。Global 再 copy 还是自己。Swift 用「逃不逃逸」回答同一件事。",[191,778,779],{"id":779},"变量捕获",[179,781,782,783,786],{},"闭包默认捕获的是",[205,784,785],{},"变量本身","。外面后来改了，闭包里读到新值；闭包里改了，外面也看得到。",[179,788,789],{},"局部变量本来跟着函数走，函数返回就没了。闭包还要在那之后用它，编译器就把变量放进堆上的 box。闭包持有这块 box，所以函数返回后计数还在。",[362,791,793],{"className":364,"code":792,"language":366,"meta":367,"style":367},"func makeStepper() -> () -> Int {\n    var i = 0\n    return {\n        i += 1\n        return i\n    }\n}\n\nlet a = makeStepper()\nlet b = a\nlet c = makeStepper()\na() \u002F\u002F 1\nb() \u002F\u002F 2，和 a 是同一个闭包\nc() \u002F\u002F 1，另一次调用，另一份 i\n",[186,794,795,816,830,837,848,856,861,865,869,883,896,910,922,933],{"__ignoreMap":367},[371,796,797,799,802,805,807,810,812,814],{"class":373,"line":374},[371,798,431],{"class":430},[371,800,801],{"class":434}," makeStepper",[371,803,804],{"class":385},"()",[371,806,396],{"class":385},[371,808,809],{"class":385}," ()",[371,811,396],{"class":385},[371,813,399],{"class":389},[371,815,468],{"class":385},[371,817,818,821,824,827],{"class":373,"line":420},[371,819,820],{"class":377},"    var",[371,822,823],{"class":381}," i ",[371,825,826],{"class":385},"=",[371,828,829],{"class":377}," 0\n",[371,831,832,835],{"class":373,"line":427},[371,833,834],{"class":506},"    return",[371,836,468],{"class":385},[371,838,839,842,845],{"class":373,"line":471},[371,840,841],{"class":381},"        i ",[371,843,844],{"class":385},"+=",[371,846,847],{"class":377}," 1\n",[371,849,850,853],{"class":373,"line":484},[371,851,852],{"class":506},"        return",[371,854,855],{"class":381}," i\n",[371,857,858],{"class":373,"line":490},[371,859,860],{"class":385},"    }\n",[371,862,863],{"class":373,"line":495},[371,864,487],{"class":385},[371,866,867],{"class":373,"line":510},[371,868,424],{"emptyLinePlaceholder":423},[371,870,871,873,876,878,880],{"class":373,"line":524},[371,872,378],{"class":377},[371,874,875],{"class":381}," a ",[371,877,826],{"class":385},[371,879,801],{"class":434},[371,881,882],{"class":385},"()\n",[371,884,886,888,891,893],{"class":373,"line":885},10,[371,887,378],{"class":377},[371,889,890],{"class":381}," b ",[371,892,826],{"class":385},[371,894,895],{"class":381}," a\n",[371,897,899,901,904,906,908],{"class":373,"line":898},11,[371,900,378],{"class":377},[371,902,903],{"class":381}," c ",[371,905,826],{"class":385},[371,907,801],{"class":434},[371,909,882],{"class":385},[371,911,913,916,918],{"class":373,"line":912},12,[371,914,915],{"class":434},"a",[371,917,804],{"class":385},[371,919,921],{"class":920},"sHwdD"," \u002F\u002F 1\n",[371,923,925,928,930],{"class":373,"line":924},13,[371,926,927],{"class":434},"b",[371,929,804],{"class":385},[371,931,932],{"class":920}," \u002F\u002F 2，和 a 是同一个闭包\n",[371,934,936,939,941],{"class":373,"line":935},14,[371,937,938],{"class":434},"c",[371,940,804],{"class":385},[371,942,943],{"class":920}," \u002F\u002F 1，另一次调用，另一份 i\n",[179,945,946,949,950,953,954,956,957,959],{},[186,947,948],{},"let b = a"," 没有复制一份新的 ",[186,951,952],{},"i","。闭包是引用类型，",[186,955,915],{}," 和 ",[186,958,927],{}," 指向同一个闭包。",[179,961,962],{},"同一次函数调用里的两个闭包，捕获的是同一个变量：",[362,964,966],{"className":364,"code":965,"language":366,"meta":367,"style":367},"func shared() -> (() -> Int, () -> Int) {\n    var i = 0\n    return (\n        { i += 1; return i },\n        { i += 10; return i }\n    )\n}\n\nlet (inc, jump) = shared()\ninc()  \u002F\u002F 1\njump() \u002F\u002F 11\ninc()  \u002F\u002F 12\n",[186,967,968,999,1009,1016,1042,1061,1066,1070,1074,1091,1101,1111],{"__ignoreMap":367},[371,969,970,972,975,977,979,982,984,986,989,991,993,995,997],{"class":373,"line":374},[371,971,431],{"class":430},[371,973,974],{"class":434}," shared",[371,976,804],{"class":385},[371,978,396],{"class":385},[371,980,981],{"class":385}," (()",[371,983,396],{"class":385},[371,985,399],{"class":389},[371,987,988],{"class":381},", ",[371,990,804],{"class":385},[371,992,396],{"class":385},[371,994,399],{"class":389},[371,996,393],{"class":385},[371,998,468],{"class":385},[371,1000,1001,1003,1005,1007],{"class":373,"line":420},[371,1002,820],{"class":377},[371,1004,823],{"class":381},[371,1006,826],{"class":385},[371,1008,829],{"class":377},[371,1010,1011,1013],{"class":373,"line":427},[371,1012,834],{"class":506},[371,1014,1015],{"class":385}," (\n",[371,1017,1018,1021,1023,1025,1028,1031,1034,1036,1039],{"class":373,"line":471},[371,1019,1020],{"class":385},"        {",[371,1022,823],{"class":381},[371,1024,844],{"class":385},[371,1026,1027],{"class":377}," 1",[371,1029,1030],{"class":381},"; ",[371,1032,1033],{"class":506},"return",[371,1035,823],{"class":381},[371,1037,1038],{"class":385},"}",[371,1040,1041],{"class":381},",\n",[371,1043,1044,1046,1048,1050,1053,1055,1057,1059],{"class":373,"line":484},[371,1045,1020],{"class":385},[371,1047,823],{"class":381},[371,1049,844],{"class":385},[371,1051,1052],{"class":377}," 10",[371,1054,1030],{"class":381},[371,1056,1033],{"class":506},[371,1058,823],{"class":381},[371,1060,487],{"class":385},[371,1062,1063],{"class":373,"line":490},[371,1064,1065],{"class":385},"    )\n",[371,1067,1068],{"class":373,"line":495},[371,1069,487],{"class":385},[371,1071,1072],{"class":373,"line":510},[371,1073,424],{"emptyLinePlaceholder":423},[371,1075,1076,1078,1080,1083,1085,1087,1089],{"class":373,"line":524},[371,1077,378],{"class":377},[371,1079,453],{"class":385},[371,1081,1082],{"class":381},"inc, jump",[371,1084,393],{"class":385},[371,1086,402],{"class":385},[371,1088,974],{"class":434},[371,1090,882],{"class":385},[371,1092,1093,1096,1098],{"class":373,"line":885},[371,1094,1095],{"class":434},"inc",[371,1097,804],{"class":385},[371,1099,1100],{"class":920},"  \u002F\u002F 1\n",[371,1102,1103,1106,1108],{"class":373,"line":898},[371,1104,1105],{"class":434},"jump",[371,1107,804],{"class":385},[371,1109,1110],{"class":920}," \u002F\u002F 11\n",[371,1112,1113,1115,1117],{"class":373,"line":912},[371,1114,1095],{"class":434},[371,1116,804],{"class":385},[371,1118,1119],{"class":920},"  \u002F\u002F 12\n",[585,1121,1122],{"id":1122},"捕获列表",[179,1124,1125,1126,1129],{},"捕获列表在闭包",[205,1127,1128],{},"创建时","求值，把当时的值带进来。",[362,1131,1133],{"className":364,"code":1132,"language":366,"meta":367,"style":367},"var n = 0\nlet snap = { [n] in n }\nlet live = { n }\nn = 10\nsnap() \u002F\u002F 0\nlive() \u002F\u002F 10\n",[186,1134,1135,1147,1168,1183,1193,1203],{"__ignoreMap":367},[371,1136,1137,1140,1143,1145],{"class":373,"line":374},[371,1138,1139],{"class":377},"var",[371,1141,1142],{"class":381}," n ",[371,1144,826],{"class":385},[371,1146,829],{"class":377},[371,1148,1149,1151,1154,1156,1158,1161,1164,1166],{"class":373,"line":420},[371,1150,378],{"class":377},[371,1152,1153],{"class":381}," snap ",[371,1155,826],{"class":385},[371,1157,405],{"class":385},[371,1159,1160],{"class":381}," [n] ",[371,1162,1163],{"class":506},"in",[371,1165,1142],{"class":381},[371,1167,487],{"class":385},[371,1169,1170,1172,1175,1177,1179,1181],{"class":373,"line":427},[371,1171,378],{"class":377},[371,1173,1174],{"class":381}," live ",[371,1176,826],{"class":385},[371,1178,405],{"class":385},[371,1180,1142],{"class":381},[371,1182,487],{"class":385},[371,1184,1185,1188,1190],{"class":373,"line":471},[371,1186,1187],{"class":381},"n ",[371,1189,826],{"class":385},[371,1191,1192],{"class":377}," 10\n",[371,1194,1195,1198,1200],{"class":373,"line":484},[371,1196,1197],{"class":434},"snap",[371,1199,804],{"class":385},[371,1201,1202],{"class":920}," \u002F\u002F 0\n",[371,1204,1205,1208,1210],{"class":373,"line":490},[371,1206,1207],{"class":434},"live",[371,1209,804],{"class":385},[371,1211,1212],{"class":920}," \u002F\u002F 10\n",[179,1214,1215,1216,189],{},"值类型是拷进闭包的。这个名字不能再赋值，编译器会说 ",[186,1217,1218],{},"n is an immutable capture",[179,1220,1221,1222,1225],{},"类实例拷的是引用，对象不会被深拷贝。",[186,1223,1224],{},"[box]"," 持有创建闭包时的那个对象。外面把变量换成另一个对象，闭包里还是原来的。对象的属性仍然可以改。",[362,1227,1229],{"className":364,"code":1228,"language":366,"meta":367,"style":367},"final class Box {\n    var value: Int\n    init(_ value: Int) { self.value = value }\n}\n\nvar box = Box(1)\nlet liveBox = { box.value }\nlet snapBox = { [box] in box.value }\nbox = Box(2)\nliveBox() \u002F\u002F 2，捕获的是变量 box\nsnapBox() \u002F\u002F 1，持有创建时那个对象\n",[186,1230,1231,1244,1254,1286,1290,1294,1312,1328,1348,1364,1374],{"__ignoreMap":367},[371,1232,1233,1236,1239,1242],{"class":373,"line":374},[371,1234,1235],{"class":430},"final",[371,1237,1238],{"class":430}," class",[371,1240,1241],{"class":389}," Box",[371,1243,468],{"class":385},[371,1245,1246,1248,1251],{"class":373,"line":420},[371,1247,820],{"class":377},[371,1249,1250],{"class":381}," value: ",[371,1252,1253],{"class":389},"Int\n",[371,1255,1256,1259,1261,1264,1268,1270,1272,1274,1276,1279,1281,1284],{"class":373,"line":427},[371,1257,1258],{"class":430},"    init",[371,1260,386],{"class":385},[371,1262,1263],{"class":434},"_",[371,1265,1267],{"class":1266},"sHdIc"," value",[371,1269,444],{"class":381},[371,1271,390],{"class":389},[371,1273,393],{"class":385},[371,1275,405],{"class":385},[371,1277,1278],{"class":381}," self.value ",[371,1280,826],{"class":385},[371,1282,1283],{"class":381}," value ",[371,1285,487],{"class":385},[371,1287,1288],{"class":373,"line":471},[371,1289,487],{"class":385},[371,1291,1292],{"class":373,"line":484},[371,1293,424],{"emptyLinePlaceholder":423},[371,1295,1296,1298,1301,1303,1305,1307,1310],{"class":373,"line":490},[371,1297,1139],{"class":377},[371,1299,1300],{"class":381}," box ",[371,1302,826],{"class":385},[371,1304,1241],{"class":434},[371,1306,386],{"class":385},[371,1308,1309],{"class":377},"1",[371,1311,521],{"class":385},[371,1313,1314,1316,1319,1321,1323,1326],{"class":373,"line":495},[371,1315,378],{"class":377},[371,1317,1318],{"class":381}," liveBox ",[371,1320,826],{"class":385},[371,1322,405],{"class":385},[371,1324,1325],{"class":381}," box.value ",[371,1327,487],{"class":385},[371,1329,1330,1332,1335,1337,1339,1342,1344,1346],{"class":373,"line":510},[371,1331,378],{"class":377},[371,1333,1334],{"class":381}," snapBox ",[371,1336,826],{"class":385},[371,1338,405],{"class":385},[371,1340,1341],{"class":381}," [box] ",[371,1343,1163],{"class":506},[371,1345,1325],{"class":381},[371,1347,487],{"class":385},[371,1349,1350,1353,1355,1357,1359,1362],{"class":373,"line":524},[371,1351,1352],{"class":381},"box ",[371,1354,826],{"class":385},[371,1356,1241],{"class":434},[371,1358,386],{"class":385},[371,1360,1361],{"class":377},"2",[371,1363,521],{"class":385},[371,1365,1366,1369,1371],{"class":373,"line":885},[371,1367,1368],{"class":434},"liveBox",[371,1370,804],{"class":385},[371,1372,1373],{"class":920}," \u002F\u002F 2，捕获的是变量 box\n",[371,1375,1376,1379,1381],{"class":373,"line":898},[371,1377,1378],{"class":434},"snapBox",[371,1380,804],{"class":385},[371,1382,1383],{"class":920}," \u002F\u002F 1，持有创建时那个对象\n",[179,1385,1386,956,1388,1391],{},[186,1387,278],{},[186,1389,1390],{},"unowned"," 只写在捕获列表里，用来决定这份引用怎么持有对象。列表里可以写多个，每个名字各算各的。",[179,1393,1394,1396,1397,1399,1400,956,1403,1406,1407,1410],{},[186,1395,259],{}," 之后，闭包里的 ",[186,1398,547],{}," 是可选的。",[186,1401,1402],{},"guard let self else { return }",[186,1404,1405],{},"guard let self = self else { return }"," 是一件事：解包出来的强引用只活在",[205,1408,1409],{},"这一次调用","里。调用结束就松开，捕获列表仍然是弱引用。",[1412,1413,1415,1416,1418],"warning",{"title":1414},"两套捕获分开记","Swift 默认和外面共用变量。OC 默认把创建时的值拷进 Block，外面再改，Block 里还是旧的，而且不能在 Block 里给这个局部变量赋值。OC 要共用、要修改，加 ",[186,1417,188],{},"。Swift 要创建时的那一份，写捕获列表。",[362,1420,1422],{"className":675,"code":1421,"language":677,"meta":367,"style":367},"__block int n = 1;\nvoid (^block)(void) = ^{\n    n = 2;\n};\nblock();\n\u002F\u002F 外面的 n 也是 2\n",[186,1423,1424,1429,1433,1438,1442,1447],{"__ignoreMap":367},[371,1425,1426],{"class":373,"line":374},[371,1427,1428],{},"__block int n = 1;\n",[371,1430,1431],{"class":373,"line":420},[371,1432,689],{},[371,1434,1435],{"class":373,"line":427},[371,1436,1437],{},"    n = 2;\n",[371,1439,1440],{"class":373,"line":471},[371,1441,699],{},[371,1443,1444],{"class":373,"line":484},[371,1445,1446],{},"block();\n",[371,1448,1449],{"class":373,"line":490},[371,1450,1451],{},"\u002F\u002F 外面的 n 也是 2\n",[300,1453,1454,1469],{},[303,1455,1456],{},[306,1457,1458,1460,1463,1466],{},[309,1459],{},[309,1461,1462],{},"创建之后外面改了变量",[309,1464,1465],{},"闭包里能不能改这个局部变量",[309,1467,1468],{},"对象怎么留",[319,1470,1471,1485,1502,1519,1533],{},[306,1472,1473,1476,1479,1482],{},[324,1474,1475],{},"Swift 默认",[324,1477,1478],{},"闭包里是新值",[324,1480,1481],{},"能，和外面是同一份",[324,1483,1484],{},"类实例被强引用",[306,1486,1487,1493,1496,1499],{},[324,1488,1489,1490],{},"Swift ",[186,1491,1492],{},"[n]",[324,1494,1495],{},"闭包里是创建时的值",[324,1497,1498],{},"这份拷贝不能再赋值",[324,1500,1501],{},"类实例是再持有当时那个对象",[306,1503,1504,1508,1511,1513],{},[324,1505,1489,1506],{},[186,1507,259],{},[324,1509,1510],{},"—",[324,1512,1510],{},[324,1514,1515,1516],{},"不持有，对象没了就是 ",[186,1517,1518],{},"nil",[306,1520,1521,1524,1527,1530],{},[324,1522,1523],{},"OC 默认",[324,1525,1526],{},"Block 里仍是旧值",[324,1528,1529],{},"不能",[324,1531,1532],{},"copy 到堆上时 retain",[306,1534,1535,1540,1543,1546],{},[324,1536,1537,1538],{},"OC ",[186,1539,188],{},[324,1541,1542],{},"两边是同一份",[324,1544,1545],{},"能",[324,1547,1548],{},"ARC 下对象仍会 retain",[581,1550,1551,1552,1554,1555,1557,1558,1560],{},"面试点：先写 ",[186,1553,1197],{}," \u002F ",[186,1556,1207],{}," 这段，说明捕获列表在创建时取值。再补一句：这和 OC 的默认捕获是反过来的，",[186,1559,188],{}," 才接近 Swift 的默认行为。",[191,1562,1563],{"id":1563},"循环引用",[179,1565,1566],{},"闭包容易成环，是三件事叠在一起：",[1568,1569,1570,1573,1576],"ul",{},[202,1571,1572],{},"闭包是引用类型，存进属性就会被强引用。",[202,1574,1575],{},"闭包默认强引用它用到的对象。",[202,1577,1578],{},"业务里又经常把回调存在对象自己身上。",[362,1580,1583],{"className":1581,"code":1582,"language":577,"meta":367},[575],"控制器 ──强引用──▶ onFinish\n   ▲                  │\n   └────强捕获 self───┘\n",[186,1584,1582],{"__ignoreMap":367},[179,1586,1587,1588,1591,1592,1595],{},"两条都是强引用，控制器的引用计数下不来，",[186,1589,1590],{},"deinit"," 不走。Delegate 习惯写成 ",[186,1593,1594],{},"weak var delegate","，闭包没有这个约定，所以同样的关系更容易漏。",[362,1597,1599],{"className":364,"code":1598,"language":366,"meta":367,"style":367},"final class Screen {\n    var onFinish: (() -> Void)?\n\n    func setup() {\n        onFinish = {\n            self.refresh()\n        }\n    }\n\n    func refresh() {}\n    deinit { print(\"deinit\") }\n}\n",[186,1600,1601,1612,1629,1633,1645,1654,1664,1669,1673,1677,1689,1713],{"__ignoreMap":367},[371,1602,1603,1605,1607,1610],{"class":373,"line":374},[371,1604,1235],{"class":430},[371,1606,1238],{"class":430},[371,1608,1609],{"class":389}," Screen",[371,1611,468],{"class":385},[371,1613,1614,1616,1619,1622,1624,1626],{"class":373,"line":420},[371,1615,820],{"class":377},[371,1617,1618],{"class":381}," onFinish: ",[371,1620,1621],{"class":385},"(()",[371,1623,396],{"class":385},[371,1625,463],{"class":389},[371,1627,1628],{"class":385},")?\n",[371,1630,1631],{"class":373,"line":427},[371,1632,424],{"emptyLinePlaceholder":423},[371,1634,1635,1638,1641,1643],{"class":373,"line":471},[371,1636,1637],{"class":430},"    func",[371,1639,1640],{"class":434}," setup",[371,1642,804],{"class":385},[371,1644,468],{"class":385},[371,1646,1647,1650,1652],{"class":373,"line":484},[371,1648,1649],{"class":381},"        onFinish ",[371,1651,826],{"class":385},[371,1653,468],{"class":385},[371,1655,1656,1659,1662],{"class":373,"line":490},[371,1657,1658],{"class":381},"            self.",[371,1660,1661],{"class":434},"refresh",[371,1663,882],{"class":385},[371,1665,1666],{"class":373,"line":495},[371,1667,1668],{"class":385},"        }\n",[371,1670,1671],{"class":373,"line":510},[371,1672,860],{"class":385},[371,1674,1675],{"class":373,"line":524},[371,1676,424],{"emptyLinePlaceholder":423},[371,1678,1679,1681,1684,1686],{"class":373,"line":885},[371,1680,1637],{"class":430},[371,1682,1683],{"class":434}," refresh",[371,1685,804],{"class":385},[371,1687,1688],{"class":385}," {}\n",[371,1690,1691,1694,1696,1699,1701,1704,1707,1709,1711],{"class":373,"line":898},[371,1692,1693],{"class":430},"    deinit",[371,1695,405],{"class":385},[371,1697,1698],{"class":434}," print",[371,1700,386],{"class":385},[371,1702,1703],{"class":385},"\"",[371,1705,1590],{"class":1706},"sfazB",[371,1708,1703],{"class":385},[371,1710,393],{"class":385},[371,1712,417],{"class":385},[371,1714,1715],{"class":373,"line":912},[371,1716,487],{"class":385},[179,1718,1719,1722,1723,1726],{},[186,1720,1721],{},"setup()"," 之后，这个 ",[186,1724,1725],{},"Screen"," 不会释放。",[179,1728,1729,1730,1732,1733,1736],{},"非逃逸闭包在函数返回前就调用完，属性上留不住它，构不成这种环。结构体是值：逃逸闭包不能把正在修改的 ",[186,1731,547],{}," 借走，编译器直接报 ",[186,1734,1735],{},"escaping closure captures mutating 'self' parameter","。要带数据走，用捕获列表把当时的值拷进去。会和闭包互相持有的是类。",[585,1738,1739],{"id":1739},"怎么断",[179,1741,1742],{},"断的是闭包对对象的那条强引用，或者断对象对闭包的那条。",[179,1744,1745,1749],{},[205,1746,1747,189],{},[186,1748,259],{}," 闭包不持有对象。对象可能先没，用之前先解包。网络回调、动画完成、跨页面回传，用这个。",[362,1751,1753],{"className":364,"code":1752,"language":366,"meta":367,"style":367},"onFinish = { [weak self] in\n    guard let self else { return }\n    self.refresh()\n}\n",[186,1754,1755,1774,1795,1804],{"__ignoreMap":367},[371,1756,1757,1760,1762,1764,1767,1769,1772],{"class":373,"line":374},[371,1758,1759],{"class":381},"onFinish ",[371,1761,826],{"class":385},[371,1763,405],{"class":385},[371,1765,1766],{"class":381}," [",[371,1768,278],{"class":377},[371,1770,1771],{"class":381}," self] ",[371,1773,507],{"class":506},[371,1775,1776,1779,1782,1785,1788,1790,1793],{"class":373,"line":420},[371,1777,1778],{"class":506},"    guard",[371,1780,1781],{"class":377}," let",[371,1783,1784],{"class":381}," self ",[371,1786,1787],{"class":506},"else",[371,1789,405],{"class":385},[371,1791,1792],{"class":506}," return",[371,1794,417],{"class":385},[371,1796,1797,1800,1802],{"class":373,"line":427},[371,1798,1799],{"class":381},"    self.",[371,1801,1661],{"class":434},[371,1803,882],{"class":385},[371,1805,1806],{"class":373,"line":471},[371,1807,487],{"class":385},[179,1809,1810,1811,1813,1814,1816,1817,1819,1820,1823],{},"解包出来的 ",[186,1812,547],{}," 在这次 ",[186,1815,1661],{}," 期间是强引用。",[186,1818,1661],{}," 返回后松开。",[186,1821,1822],{},"onFinish"," 的捕获仍然是弱的，环不在。",[179,1825,1826,1830,1831,1834,1835,1838,1839,189],{},[205,1827,1828,189],{},[186,1829,561],{}," 也不持有对象，调用时直接当对象还在。对象已经释放再进来，进程崩溃，信息是 ",[186,1832,1833],{},"Attempted to read an unowned reference but object was already deallocated","。闭包的寿命被对象包住、不会被单独传出去时用它，比如只存在对象自己的 ",[186,1836,1837],{},"lazy"," 属性里。拿不准对象会不会先释放，用 ",[186,1840,278],{},[179,1842,1843,1846,1847,1849,1850,1852],{},[205,1844,1845],{},"一次性回调，用完把属性清空。"," 先取出来，再置 ",[186,1848,1518],{},"，然后调用。属性这条强引用先断，回调里再给 ",[186,1851,1822],{}," 赋值也不会和旧的搅在一起。",[362,1854,1856],{"className":364,"code":1855,"language":366,"meta":367,"style":367},"func finish() {\n    let done = onFinish\n    onFinish = nil\n    done?()\n}\n",[186,1857,1858,1869,1882,1892,1899],{"__ignoreMap":367},[371,1859,1860,1862,1865,1867],{"class":373,"line":374},[371,1861,431],{"class":430},[371,1863,1864],{"class":434}," finish",[371,1866,804],{"class":385},[371,1868,468],{"class":385},[371,1870,1871,1874,1877,1879],{"class":373,"line":420},[371,1872,1873],{"class":377},"    let",[371,1875,1876],{"class":381}," done ",[371,1878,826],{"class":385},[371,1880,1881],{"class":381}," onFinish\n",[371,1883,1884,1887,1889],{"class":373,"line":427},[371,1885,1886],{"class":381},"    onFinish ",[371,1888,826],{"class":385},[371,1890,1891],{"class":385}," nil\n",[371,1893,1894,1896],{"class":373,"line":471},[371,1895,474],{"class":381},[371,1897,1898],{"class":385},"?()\n",[371,1900,1901],{"class":373,"line":484},[371,1902,487],{"class":385},[179,1904,1905,1906,1908],{},"捕获列表里写 ",[186,1907,557],{}," 是显式的强引用，环还在。写了方括号，不代表已经拆环。",[585,1910,1911],{"id":1911},"延寿和环",[362,1913,1915],{"className":364,"code":1914,"language":366,"meta":367,"style":367},"DispatchQueue.main.async {\n    self.refresh()\n}\n",[186,1916,1917,1926,1934],{"__ignoreMap":367},[371,1918,1919,1922,1924],{"class":373,"line":374},[371,1920,1921],{"class":381},"DispatchQueue.main.",[371,1923,263],{"class":434},[371,1925,468],{"class":385},[371,1927,1928,1930,1932],{"class":373,"line":420},[371,1929,1799],{"class":381},[371,1931,1661],{"class":434},[371,1933,882],{"class":385},[371,1935,1936],{"class":373,"line":427},[371,1937,487],{"class":385},[179,1939,1940],{},"主队列持有闭包，闭包持有控制器。控制器不持有主队列。任务跑完，闭包释放，控制器只是多活到这次刷新结束。",[179,1942,1943,1944,1947],{},"控制器自己的属性持有队列时，任务还排着的那一阵，",[186,1945,1946],{},"控制器 → 队列 → 闭包 → 控制器"," 连得上。任务结束，队列放掉闭包，这条链断开。一直不结束的定时源才会挂住。",[585,1949,1951],{"id":1950},"lazytimer通知","lazy、Timer、通知",[179,1953,1954,1956,1957,1959],{},[186,1955,1837],{}," 闭包第一次被访问时创建，存在属性里，又捕获了 ",[186,1958,547],{},"：",[362,1961,1963],{"className":364,"code":1962,"language":366,"meta":367,"style":367},"final class Profile {\n    let name: String\n\n    lazy var label: () -> String = {\n        \"用户 \\(self.name)\"\n    }\n\n    init(name: String) { self.name = name }\n}\n",[186,1964,1965,1976,1986,1990,2012,2031,2035,2039,2067],{"__ignoreMap":367},[371,1966,1967,1969,1971,1974],{"class":373,"line":374},[371,1968,1235],{"class":430},[371,1970,1238],{"class":430},[371,1972,1973],{"class":389}," Profile",[371,1975,468],{"class":385},[371,1977,1978,1980,1983],{"class":373,"line":420},[371,1979,1873],{"class":377},[371,1981,1982],{"class":381}," name: ",[371,1984,1985],{"class":389},"String\n",[371,1987,1988],{"class":373,"line":427},[371,1989,424],{"emptyLinePlaceholder":423},[371,1991,1992,1995,1998,2001,2003,2005,2008,2010],{"class":373,"line":471},[371,1993,1994],{"class":430},"    lazy",[371,1996,1997],{"class":377}," var",[371,1999,2000],{"class":381}," label: ",[371,2002,804],{"class":385},[371,2004,396],{"class":385},[371,2006,2007],{"class":389}," String",[371,2009,402],{"class":385},[371,2011,468],{"class":385},[371,2013,2014,2017,2020,2023,2026,2028],{"class":373,"line":484},[371,2015,2016],{"class":385},"        \"",[371,2018,2019],{"class":1706},"用户 ",[371,2021,2022],{"class":385},"\\(",[371,2024,2025],{"class":381},"self.name",[371,2027,393],{"class":385},[371,2029,2030],{"class":385},"\"\n",[371,2032,2033],{"class":373,"line":490},[371,2034,860],{"class":385},[371,2036,2037],{"class":373,"line":495},[371,2038,424],{"emptyLinePlaceholder":423},[371,2040,2041,2043,2045,2048,2050,2053,2055,2057,2060,2062,2065],{"class":373,"line":510},[371,2042,1258],{"class":430},[371,2044,386],{"class":385},[371,2046,2047],{"class":440},"name",[371,2049,444],{"class":381},[371,2051,2052],{"class":389},"String",[371,2054,393],{"class":385},[371,2056,405],{"class":385},[371,2058,2059],{"class":381}," self.name ",[371,2061,826],{"class":385},[371,2063,2064],{"class":381}," name ",[371,2066,487],{"class":385},[371,2068,2069],{"class":373,"line":524},[371,2070,487],{"class":385},[179,2072,2073,2076,2077,2080,2081,2083,2084,189],{},[186,2074,2075],{},"Profile → label → Profile","。这段闭包只从 ",[186,2078,2079],{},"Profile"," 身上拿，用 ",[186,2082,561],{},"。它可能被别人存下来、在对象释放后调用，用 ",[186,2085,259],{},[179,2087,2088,2089,2091,2092,2094,2095,2097,2098,568,2100,2103],{},"重复触发的 Timer：RunLoop 持有 Timer，Timer 持有闭包。闭包再强引用 ",[186,2090,547],{},"，而 ",[186,2093,547],{}," 持有 Timer，环就成立。",[186,2096,1590],{}," 走不到，写在 ",[186,2099,1590],{},[186,2101,2102],{},"invalidate"," 也不会执行。",[362,2105,2107],{"className":364,"code":2106,"language":366,"meta":367,"style":367},"final class Clock {\n    var timer: Timer?\n\n    func start() {\n        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in\n            self?.tick()\n        }\n    }\n\n    func tick() {}\n\n    deinit {\n        timer?.invalidate()\n    }\n}\n",[186,2108,2109,2120,2130,2134,2145,2192,2208,2212,2216,2220,2231,2235,2241,2254,2258],{"__ignoreMap":367},[371,2110,2111,2113,2115,2118],{"class":373,"line":374},[371,2112,1235],{"class":430},[371,2114,1238],{"class":430},[371,2116,2117],{"class":389}," Clock",[371,2119,468],{"class":385},[371,2121,2122,2124,2127],{"class":373,"line":420},[371,2123,820],{"class":377},[371,2125,2126],{"class":381}," timer: Timer",[371,2128,2129],{"class":385},"?\n",[371,2131,2132],{"class":373,"line":427},[371,2133,424],{"emptyLinePlaceholder":423},[371,2135,2136,2138,2141,2143],{"class":373,"line":471},[371,2137,1637],{"class":430},[371,2139,2140],{"class":434}," start",[371,2142,804],{"class":385},[371,2144,468],{"class":385},[371,2146,2147,2150,2152,2155,2158,2160,2163,2166,2168,2170,2173,2175,2179,2181,2183,2185,2187,2190],{"class":373,"line":484},[371,2148,2149],{"class":381},"        timer ",[371,2151,826],{"class":385},[371,2153,2154],{"class":381}," Timer.",[371,2156,2157],{"class":434},"scheduledTimer",[371,2159,386],{"class":385},[371,2161,2162],{"class":434},"withTimeInterval",[371,2164,2165],{"class":385},":",[371,2167,1027],{"class":377},[371,2169,988],{"class":381},[371,2171,2172],{"class":434},"repeats",[371,2174,2165],{"class":385},[371,2176,2178],{"class":2177},"sfNiH"," true",[371,2180,393],{"class":385},[371,2182,405],{"class":385},[371,2184,1766],{"class":381},[371,2186,278],{"class":377},[371,2188,2189],{"class":381}," self] _ ",[371,2191,507],{"class":506},[371,2193,2194,2197,2200,2203,2206],{"class":373,"line":490},[371,2195,2196],{"class":381},"            self",[371,2198,2199],{"class":385},"?",[371,2201,2202],{"class":381},".",[371,2204,2205],{"class":434},"tick",[371,2207,882],{"class":385},[371,2209,2210],{"class":373,"line":495},[371,2211,1668],{"class":385},[371,2213,2214],{"class":373,"line":510},[371,2215,860],{"class":385},[371,2217,2218],{"class":373,"line":524},[371,2219,424],{"emptyLinePlaceholder":423},[371,2221,2222,2224,2227,2229],{"class":373,"line":885},[371,2223,1637],{"class":430},[371,2225,2226],{"class":434}," tick",[371,2228,804],{"class":385},[371,2230,1688],{"class":385},[371,2232,2233],{"class":373,"line":898},[371,2234,424],{"emptyLinePlaceholder":423},[371,2236,2237,2239],{"class":373,"line":912},[371,2238,1693],{"class":430},[371,2240,468],{"class":385},[371,2242,2243,2246,2248,2250,2252],{"class":373,"line":924},[371,2244,2245],{"class":381},"        timer",[371,2247,2199],{"class":385},[371,2249,2202],{"class":381},[371,2251,2102],{"class":434},[371,2253,882],{"class":385},[371,2255,2256],{"class":373,"line":935},[371,2257,860],{"class":385},[371,2259,2261],{"class":373,"line":2260},15,[371,2262,487],{"class":385},[179,2264,2265,2267,2268,2270,2271,2273,2274,2276,2277,2279,2280,2282,2283,189],{},[186,2266,259],{}," 让 ",[186,2269,1590],{}," 走得到，",[186,2272,2102],{}," 再让 RunLoop 放掉 Timer。只有 ",[186,2275,278],{},"、从不 ",[186,2278,2102],{}," 的话，对象能释放，Timer 仍会一直触发，只是 ",[186,2281,547],{}," 已是 ",[186,2284,1518],{},[179,2286,2287,2288,189],{},"通知的 block 观察者由通知中心持有。闭包强引用控制器时，控制器走不到 ",[186,2289,1590],{},[362,2291,2293],{"className":364,"code":2292,"language":366,"meta":367,"style":367},"final class Player {\n    var token: NSObjectProtocol?\n\n    func listen() {\n        token = NotificationCenter.default.addObserver(\n            forName: Notification.Name(\"playback\"),\n            object: nil,\n            queue: .main\n        ) { [weak self] note in\n            self?.handle(note)\n        }\n    }\n\n    func handle(_ note: Notification) {}\n\n    deinit {\n        if let token {\n            NotificationCenter.default.removeObserver(token)\n        }\n    }\n}\n",[186,2294,2295,2306,2315,2319,2330,2346,2372,2384,2394,2410,2427,2431,2435,2439,2460,2464,2471,2485,2501,2506,2511],{"__ignoreMap":367},[371,2296,2297,2299,2301,2304],{"class":373,"line":374},[371,2298,1235],{"class":430},[371,2300,1238],{"class":430},[371,2302,2303],{"class":389}," Player",[371,2305,468],{"class":385},[371,2307,2308,2310,2313],{"class":373,"line":420},[371,2309,820],{"class":377},[371,2311,2312],{"class":381}," token: NSObjectProtocol",[371,2314,2129],{"class":385},[371,2316,2317],{"class":373,"line":427},[371,2318,424],{"emptyLinePlaceholder":423},[371,2320,2321,2323,2326,2328],{"class":373,"line":471},[371,2322,1637],{"class":430},[371,2324,2325],{"class":434}," listen",[371,2327,804],{"class":385},[371,2329,468],{"class":385},[371,2331,2332,2335,2337,2340,2343],{"class":373,"line":484},[371,2333,2334],{"class":381},"        token ",[371,2336,826],{"class":385},[371,2338,2339],{"class":381}," NotificationCenter.default.",[371,2341,2342],{"class":434},"addObserver",[371,2344,2345],{"class":385},"(\n",[371,2347,2348,2351,2353,2356,2359,2361,2363,2366,2368,2370],{"class":373,"line":490},[371,2349,2350],{"class":434},"            forName",[371,2352,2165],{"class":385},[371,2354,2355],{"class":381}," Notification.",[371,2357,2358],{"class":434},"Name",[371,2360,386],{"class":385},[371,2362,1703],{"class":385},[371,2364,2365],{"class":1706},"playback",[371,2367,1703],{"class":385},[371,2369,393],{"class":385},[371,2371,1041],{"class":381},[371,2373,2374,2377,2379,2382],{"class":373,"line":495},[371,2375,2376],{"class":434},"            object",[371,2378,2165],{"class":385},[371,2380,2381],{"class":385}," nil",[371,2383,1041],{"class":381},[371,2385,2386,2389,2391],{"class":373,"line":510},[371,2387,2388],{"class":434},"            queue",[371,2390,2165],{"class":385},[371,2392,2393],{"class":381}," .main\n",[371,2395,2396,2399,2401,2403,2405,2408],{"class":373,"line":524},[371,2397,2398],{"class":385},"        )",[371,2400,405],{"class":385},[371,2402,1766],{"class":381},[371,2404,278],{"class":377},[371,2406,2407],{"class":381}," self] note ",[371,2409,507],{"class":506},[371,2411,2412,2414,2416,2418,2421,2423,2425],{"class":373,"line":885},[371,2413,2196],{"class":381},[371,2415,2199],{"class":385},[371,2417,2202],{"class":381},[371,2419,2420],{"class":434},"handle",[371,2422,386],{"class":385},[371,2424,581],{"class":381},[371,2426,521],{"class":385},[371,2428,2429],{"class":373,"line":898},[371,2430,1668],{"class":385},[371,2432,2433],{"class":373,"line":912},[371,2434,860],{"class":385},[371,2436,2437],{"class":373,"line":924},[371,2438,424],{"emptyLinePlaceholder":423},[371,2440,2441,2443,2446,2448,2450,2453,2456,2458],{"class":373,"line":935},[371,2442,1637],{"class":430},[371,2444,2445],{"class":434}," handle",[371,2447,386],{"class":385},[371,2449,1263],{"class":434},[371,2451,2452],{"class":1266}," note",[371,2454,2455],{"class":381},": Notification",[371,2457,393],{"class":385},[371,2459,1688],{"class":385},[371,2461,2462],{"class":373,"line":2260},[371,2463,424],{"emptyLinePlaceholder":423},[371,2465,2467,2469],{"class":373,"line":2466},16,[371,2468,1693],{"class":430},[371,2470,468],{"class":385},[371,2472,2474,2477,2479,2482],{"class":373,"line":2473},17,[371,2475,2476],{"class":506},"        if",[371,2478,1781],{"class":377},[371,2480,2481],{"class":381}," token ",[371,2483,2484],{"class":385},"{\n",[371,2486,2488,2491,2494,2496,2499],{"class":373,"line":2487},18,[371,2489,2490],{"class":381},"            NotificationCenter.default.",[371,2492,2493],{"class":434},"removeObserver",[371,2495,386],{"class":385},[371,2497,2498],{"class":381},"token",[371,2500,521],{"class":385},[371,2502,2504],{"class":373,"line":2503},19,[371,2505,1668],{"class":385},[371,2507,2509],{"class":373,"line":2508},20,[371,2510,860],{"class":385},[371,2512,2514],{"class":373,"line":2513},21,[371,2515,487],{"class":385},[179,2517,2518,2521,2522,189,2525,2527,2528,2530],{},[186,2519,2520],{},"queue: .main"," 是 ",[186,2523,2524],{},"OperationQueue",[186,2526,259],{}," 让对象能释放。观察者还在中心里，通知来了仍会进闭包。",[186,2529,2493],{}," 才把它卸掉。",[179,2532,2533],{},"OC 里用 selector 注册的观察者，从 iOS 9 起会在观察者释放时自动移除。block 版不会。",[581,2535,2536,2537,2539,2540,2542,2543,2545],{},"面试点：环是两边强引用。",[186,2538,278],{}," 断的是闭包对对象的持有；解包后的 ",[186,2541,547],{}," 只在本次调用里变强。全局队列的 ",[186,2544,263],{}," 是延寿。Timer 和通知的 block 要把「弱引用」和「注销」两件事都做。",[191,2547,2549],{"id":2548},"和-delegate通知怎么选","和 Delegate、通知怎么选",[179,2551,2552],{},"先看关系，再看谁持有谁。",[300,2554,2555,2573],{},[303,2556,2557],{},[306,2558,2559,2561,2564,2567,2570],{},[309,2560],{},[309,2562,2563],{},"闭包",[309,2565,2566],{},"Delegate",[309,2568,2569],{},"通知",[309,2571,2572],{},"Target-Action",[319,2574,2575,2592,2609,2625,2646],{},[306,2576,2577,2580,2583,2586,2589],{},[324,2578,2579],{},"关系",[324,2581,2582],{},"调用方手里的一段逻辑",[324,2584,2585],{},"一对一",[324,2587,2588],{},"一对多",[324,2590,2591],{},"控件对一个对象的一个方法",[306,2593,2594,2597,2600,2603,2606],{},[324,2595,2596],{},"签名",[324,2598,2599],{},"编译期检查",[324,2601,2602],{},"协议，编译期检查",[324,2604,2605],{},"通知名，发送方不知道听众",[324,2607,2608],{},"selector，运行时才知道方法在不在",[306,2610,2611,2614,2617,2619,2622],{},[324,2612,2613],{},"返回值",[324,2615,2616],{},"可以有",[324,2618,2616],{},[324,2620,2621],{},"没有",[324,2623,2624],{},"一般没有",[306,2626,2627,2630,2635,2640,2643],{},[324,2628,2629],{},"谁持有谁",[324,2631,2632,2633],{},"存成属性就强引用闭包，闭包默认强引用 ",[186,2634,547],{},[324,2636,2637,2638],{},"习惯 ",[186,2639,1594],{},[324,2641,2642],{},"通知中心持有 block",[324,2644,2645],{},"控件持有 target",[306,2647,2648,2651,2654,2657,2660],{},[324,2649,2650],{},"适合",[324,2652,2653],{},"一次完成，逻辑就在调用旁边",[324,2655,2656],{},"一组相关回调",[324,2658,2659],{},"跨模块广播",[324,2661,2662],{},"按钮点击",[179,2664,2665],{},"一次结果、只有一个处理方，用闭包。回调是好几件相关的事（列表点进详情、下拉、出错），用 Delegate，让编译器检查方法齐不齐。听众有哪些事先不知道、也不用回传结果，用通知。",[179,2667,2668],{},"闭包属性一旦多到要编号，调用顺序和错误处理会散开。这时收成一个协议，或者收成一个小类型。",[179,2670,2671],{},"函数指针没有上下文，捕获不了局部变量。Selector 靠对象和方法名找实现，写错了要运行才发现。闭包和它们的差别，是多了一份会被捕获的上下文，而且签名在编译期就定下来。",[581,2673,2674,2675,2677],{},"面试点：用三个词选。一次回调是闭包，一组回调是 Delegate，一对多是通知。再补一句持有关系：Delegate 靠约定写成 ",[186,2676,278],{},"，闭包要自己写捕获列表。",[191,2679,2680],{"id":2680},"底层",[179,2682,2683],{},"底层先讲这三句。对方没往下问就停。",[179,2685,2686,2687,2689],{},"OC 的 Block 是一个结构体：一个函数指针，后面跟着捕获来的变量。copy 到堆上时，copy\u002Fdispose 负责持有和释放这些对象。",[186,2688,188],{}," 变量带一个 forwarding 指针，从栈上访问和从堆上访问，走到的是同一份数据。",[179,2691,2692],{},"Swift 闭包在运行时是函数指针加一个上下文对象。不捕获任何东西时，可以只剩函数指针。被捕获后还要改的局部变量，放在堆上的 box 里。",[179,2694,2695],{},"字段、helper 和 ARC\u002FMRC 的差别折在下面，后续再补 SIL 和调试步骤。",[2697,2698,2699,2725,2738,2766,2808],"accordion",{},[2700,2701,2704,2719],"accordion-item",{"icon":2702,"label":2703},"i-lucide-box","Block 结构体里有什么",[179,2705,2706,2707,2710,2711,2714,2715,2718],{},"先对上这几项。flags 每一位以后再补，现在记住三个：",[186,2708,2709],{},"BLOCK_IS_GLOBAL","（全局）、",[186,2712,2713],{},"BLOCK_NEEDS_FREE","（在堆上）、",[186,2716,2717],{},"BLOCK_HAS_COPY_DISPOSE","（带 helper）。",[362,2720,2723],{"className":2721,"code":2722,"language":577,"meta":367},[575],"isa           指向 Global \u002F Stack \u002F Malloc 的类\nflags         在不在堆上、有没有 helper、是不是全局\ninvoke        真正执行的函数\ndescriptor    结构体大小、签名、copy\u002Fdispose\n捕获的变量     跟在结构体后面\n",[186,2724,2722],{"__ignoreMap":367},[2700,2726,2729,2732,2735],{"icon":2727,"label":2728},"i-lucide-copy","copy 和 dispose 做什么",[179,2730,2731],{},"Block 捕获了对象或另一个 Block 时，descriptor 里会带两个 helper。",[179,2733,2734],{},"copy helper 在搬到堆上时运行：捕获的对象 retain，捕获的 Block 再 copy。dispose helper 在 Block 销毁时运行，对应 release。",[179,2736,2737],{},"所以「copy 到堆上之后对象被持有」，是 helper 做的，不是把对象拷贝了一份。",[2700,2739,2742,2751,2760],{"icon":2740,"label":2741},"i-lucide-arrow-right-left","forwarding 指针",[179,2743,2744,2746,2747,2750],{},[186,2745,188],{}," 变量外面还有一层结构。里面有 ",[186,2748,2749],{},"forwarding","，访问变量都先走它。",[179,2752,2753,2754,2756,2757,2759],{},"刚创建时结构在栈上，",[186,2755,2749],{}," 指向自己。Block 被 copy 时，这份结构搬到堆上：堆上的 ",[186,2758,2749],{}," 指向自己，栈上那个改成指向堆。之后无论从栈上的旧地址还是堆上的新地址进去，改的是同一份值。",[179,2761,2762,2763,2765],{},"这就是 ",[186,2764,188],{}," 能让 Block 里面和外面互相看见修改的原因。",[2700,2767,2769,2787],{"icon":106,"label":2768},"ARC 和 MRC 差在哪",[179,2770,2771,2772,2774,2775,2777,2778,2780,2781,2783,2784,2786],{},"MRC 下，栈 Block 要自己 copy。属性写 ",[186,2773,727],{},"。写 ",[186,2776,765],{}," 不会把栈 Block 搬到堆上，函数返回后再调用就是野指针。",[186,2779,188],{}," 修饰的对象默认不 retain，老代码才用 ",[186,2782,188],{}," 保存 ",[186,2785,547],{}," 来拆环。",[179,2788,2789,2790,2792,2793,2795,2796,189,2798,2800,2801,2804,2805,189],{},"ARC 下，返回 Block、赋给 ",[186,2791,733],{},"，编译器都会 copy。属性写 ",[186,2794,205],{}," 也会 copy，面试仍习惯说属性用 ",[186,2797,727],{},[186,2799,188],{}," 对象会被 retain。再写 ",[186,2802,2803],{},"__block typeof(self) weakSelf = self"," 拆不掉环，要写 ",[186,2806,2807],{},"__weak",[2700,2809,2811,2818,2829],{"icon":66,"label":2810},"Swift 在运行时对应什么",[179,2812,2813,2814,2817],{},"不带上下文的函数是薄函数（thin）：只有函数指针，接近 Global Block，也接近 ",[186,2815,2816],{},"@convention(c)"," 的函数指针。",[179,2819,2820,2821,2824,2825,2828],{},"捕获了变量的闭包是厚函数（thick）：函数指针加一个上下文对象。编译器把闭包打包出来的动作叫 ",[186,2822,2823],{},"partial_apply","。被闭包和外面一起改的局部变量，用 ",[186,2826,2827],{},"alloc_box"," 放到堆上。",[179,2830,2831],{},"后续补的时候，对着一段 SIL 把这三个名字对上源码即可。",[191,2833,2834],{"id":2834},"场景题",[179,2836,2837],{},"先盖住答案。每题按三句讲：结论、谁持有谁、怎么改。",[585,2839,2841],{"id":2840},"页面为什么不走-deinit","页面为什么不走 deinit",[179,2843,2844,2845,2847],{},"完成回调存在控制器的属性里，闭包里又写了 ",[186,2846,547],{},"。控制器持有闭包，闭包持有控制器。",[179,2849,2850,2851,2853,2854,189],{},"改成 ",[186,2852,259],{},"，或者这个回调只会走一次，调用前把属性置 ",[186,2855,1518],{},[585,2857,2859],{"id":2858},"weak-和-unowned-怎么选","weak 和 unowned 怎么选",[179,2861,2862,2863,2865,2866,2868],{},"对象可能先释放，用 ",[186,2864,278],{},"，调用时是可选的。能保证调用发生时对象还在，用 ",[186,2867,1390],{},"，对象先没了再调用会崩溃。",[179,2870,2871,2872,2874,2875,2877,2878,189],{},"只挂在对象自己的 ",[186,2873,1837],{}," 属性上、不会被别人保存，可以用 ",[186,2876,1390],{},"。其余回调用 ",[186,2879,278],{},[585,2881,2883],{"id":2882},"主队列-async-里写了-self算环吗","主队列 async 里写了 self，算环吗",[179,2885,2886,2887,189,2889,2891],{},"主队列持有闭包，闭包持有 ",[186,2888,547],{},[186,2890,547],{}," 不持有主队列。任务结束就松开，对象多活到那一次执行完。",[179,2893,2894,2896],{},[186,2895,547],{}," 的属性里持有这条队列，并且任务一直不结束，才会一直连着。",[585,2898,2900],{"id":2899},"外层已经-weak里面又存了一个闭包","外层已经 weak，里面又存了一个闭包",[179,2902,2903],{},"捕获列表只管写它的那一层。",[362,2905,2907],{"className":364,"code":2906,"language":366,"meta":367,"style":367},"onDone = { [weak self] in\n    guard let self else { return }\n    self.later = {\n        self.refresh()\n    }\n}\n",[186,2908,2909,2926,2942,2951,2960,2964],{"__ignoreMap":367},[371,2910,2911,2914,2916,2918,2920,2922,2924],{"class":373,"line":374},[371,2912,2913],{"class":381},"onDone ",[371,2915,826],{"class":385},[371,2917,405],{"class":385},[371,2919,1766],{"class":381},[371,2921,278],{"class":377},[371,2923,1771],{"class":381},[371,2925,507],{"class":506},[371,2927,2928,2930,2932,2934,2936,2938,2940],{"class":373,"line":420},[371,2929,1778],{"class":506},[371,2931,1781],{"class":377},[371,2933,1784],{"class":381},[371,2935,1787],{"class":506},[371,2937,405],{"class":385},[371,2939,1792],{"class":506},[371,2941,417],{"class":385},[371,2943,2944,2947,2949],{"class":373,"line":427},[371,2945,2946],{"class":381},"    self.later ",[371,2948,826],{"class":385},[371,2950,468],{"class":385},[371,2952,2953,2956,2958],{"class":373,"line":471},[371,2954,2955],{"class":381},"        self.",[371,2957,1661],{"class":434},[371,2959,882],{"class":385},[371,2961,2962],{"class":373,"line":484},[371,2963,860],{"class":385},[371,2965,2966],{"class":373,"line":490},[371,2967,487],{"class":385},[179,2969,2970,2971,2974,2975,2977,2978,2981,2982,2985,2986,2988,2989,189],{},"内层没有自己的捕获列表，它强引用 ",[186,2972,2973],{},"guard"," 解出来的 ",[186,2976,547],{},"，又存在 ",[186,2979,2980],{},"later"," 上。环是 ",[186,2983,2984],{},"self → later → self","。内层也要写 ",[186,2987,259],{},"，或者不要把内层存进 ",[186,2990,547],{},[179,2992,2993,2994,2997],{},"内层如果只是 ",[186,2995,2996],{},"DispatchQueue.main.async","，主队列用完会放掉闭包，那是延寿。",[585,2999,3001],{"id":3000},"lazy-闭包为什么会环","lazy 闭包为什么会环",[179,3003,3004,3007],{},[186,3005,3006],{},"lazy var label: () -> String = { self.name }","：对象持有闭包，闭包持有对象。第一次访问这个属性时环就成立。",[179,3009,3010,3011,3013,3014,189],{},"闭包不会离开对象，用 ",[186,3012,561],{},"。会离开，用 ",[186,3015,259],{},[585,3017,3019],{"id":3018},"timer-停不掉","Timer 停不掉",[179,3021,3022,3023,3025,3026,3028,3029,3031,3032,3034],{},"重复 Timer 被 RunLoop 持有，Timer 持有闭包。闭包强引用 ",[186,3024,547],{},"，",[186,3027,547],{}," 又持有 Timer 时，",[186,3030,1590],{}," 不走，写在里面的 ",[186,3033,2102],{}," 没机会执行。",[179,3036,3037,3039,3040,3042,3043,189,3045,3047],{},[186,3038,259],{}," 加上 ",[186,3041,1590],{}," 里 ",[186,3044,2102],{},[186,3046,2102],{}," 要在安装 Timer 的那条线程上调用。主线程创建的，回到主线程再停。",[585,3049,3050],{"id":3050},"通知把控制器留住了",[179,3052,3053,3056,3057,3059,3060,3062],{},[186,3054,3055],{},"addObserver(forName:object:queue:using:)"," 的闭包由通知中心持有。闭包强引用 ",[186,3058,547],{}," 时，",[186,3061,547],{}," 释放不了。",[179,3064,3065,3067,3068,3042,3070,3072],{},[186,3066,259],{},"，并把返回的 token 留着，在 ",[186,3069,1590],{},[186,3071,2493],{},"。selector 版从 iOS 9 起会跟着观察者一起移除，block 版要自己移除。",[585,3074,3075],{"id":3075},"捕获列表什么时候取值",[179,3077,3078],{},"创建闭包时，不是调用时。",[362,3080,3082],{"className":364,"code":3081,"language":366,"meta":367,"style":367},"var n = 1\nlet f = { [n] in print(n) }\nlet g = { print(n) }\nn = 2\nf() \u002F\u002F 1\ng() \u002F\u002F 2\n",[186,3083,3084,3094,3120,3141,3150,3159],{"__ignoreMap":367},[371,3085,3086,3088,3090,3092],{"class":373,"line":374},[371,3087,1139],{"class":377},[371,3089,1142],{"class":381},[371,3091,826],{"class":385},[371,3093,847],{"class":377},[371,3095,3096,3098,3101,3103,3105,3107,3109,3111,3113,3116,3118],{"class":373,"line":420},[371,3097,378],{"class":377},[371,3099,3100],{"class":381}," f ",[371,3102,826],{"class":385},[371,3104,405],{"class":385},[371,3106,1160],{"class":381},[371,3108,1163],{"class":506},[371,3110,1698],{"class":434},[371,3112,386],{"class":385},[371,3114,3115],{"class":381},"n",[371,3117,393],{"class":385},[371,3119,417],{"class":385},[371,3121,3122,3124,3127,3129,3131,3133,3135,3137,3139],{"class":373,"line":427},[371,3123,378],{"class":377},[371,3125,3126],{"class":381}," g ",[371,3128,826],{"class":385},[371,3130,405],{"class":385},[371,3132,1698],{"class":434},[371,3134,386],{"class":385},[371,3136,3115],{"class":381},[371,3138,393],{"class":385},[371,3140,417],{"class":385},[371,3142,3143,3145,3147],{"class":373,"line":471},[371,3144,1187],{"class":381},[371,3146,826],{"class":385},[371,3148,3149],{"class":377}," 2\n",[371,3151,3152,3155,3157],{"class":373,"line":484},[371,3153,3154],{"class":434},"f",[371,3156,804],{"class":385},[371,3158,921],{"class":920},[371,3160,3161,3164,3166],{"class":373,"line":490},[371,3162,3163],{"class":434},"g",[371,3165,804],{"class":385},[371,3167,3168],{"class":920}," \u002F\u002F 2\n",[179,3170,3171,568,3173,3175,3176,189,3178,3180,3181,189],{},[186,3172,3154],{},[186,3174,3115],{}," 不能再 ",[186,3177,844],{},[186,3179,3163],{}," 和外面改的是同一个 ",[186,3182,3115],{},[585,3184,3186],{"id":3185},"属性为什么要-copy","属性为什么要 copy",[179,3188,3189,3190,3192],{},"OC 的 Block 属性写成 ",[186,3191,727],{},"，是为了存进去的时候已经在堆上：",[362,3194,3196],{"className":675,"code":3195,"language":677,"meta":367,"style":367},"@property (nonatomic, copy) void (^onFinish)(void);\n",[186,3197,3198],{"__ignoreMap":367},[371,3199,3200],{"class":373,"line":374},[371,3201,3195],{},[179,3203,3204,3207,3208,558,3210,3213],{},[186,3205,3206],{},"nonatomic"," 表示赋值不加锁，和 copy 是两件事。MRC 下写成 ",[186,3209,765],{},[186,3211,3212],{},"assign","，栈 Block 出了函数再调用，就是野指针。",[179,3215,3216],{},"Swift 的闭包属性是普通强引用，逃逸时上下文已经在堆上：",[362,3218,3220],{"className":364,"code":3219,"language":366,"meta":367,"style":367},"var onFinish: (() -> Void)?\n",[186,3221,3222],{"__ignoreMap":367},[371,3223,3224,3226,3228,3230,3232,3234],{"class":373,"line":374},[371,3225,1139],{"class":377},[371,3227,1618],{"class":381},[371,3229,1621],{"class":385},[371,3231,396],{"class":385},[371,3233,463],{"class":389},[371,3235,1628],{"class":385},[179,3237,3238,3239,3241,3242,3245],{},"Swift 里和野指针对上的坑是 ",[186,3240,1390],{},"：对象已经释放，闭包还被别人调用。",[186,3243,3244],{},"unowned(unsafe)"," 连运行时检查都没有，业务代码不用。",[585,3247,3249],{"id":3248},"结构体里的-self-为什么不成环","结构体里的 self 为什么不成环",[179,3251,3252,3253,3256,3257,3259],{},"结构体是值。在 ",[186,3254,3255],{},"mutating"," 方法里返回一个用到 ",[186,3258,547],{}," 的逃逸闭包，编译器会拒绝。捕获列表可以把当时的字段拷走，拷走的是值，和原来的结构体没有互相持有。",[362,3261,3263],{"className":364,"code":3262,"language":366,"meta":367,"style":367},"struct Counter {\n    var n = 0\n\n    mutating func schedule() -> () -> Void {\n        { [n] in print(n) }\n    }\n}\n",[186,3264,3265,3275,3285,3289,3312,3330,3334],{"__ignoreMap":367},[371,3266,3267,3270,3273],{"class":373,"line":374},[371,3268,3269],{"class":430},"struct",[371,3271,3272],{"class":389}," Counter",[371,3274,468],{"class":385},[371,3276,3277,3279,3281,3283],{"class":373,"line":420},[371,3278,820],{"class":377},[371,3280,1142],{"class":381},[371,3282,826],{"class":385},[371,3284,829],{"class":377},[371,3286,3287],{"class":373,"line":427},[371,3288,424],{"emptyLinePlaceholder":423},[371,3290,3291,3294,3297,3300,3302,3304,3306,3308,3310],{"class":373,"line":471},[371,3292,3293],{"class":430},"    mutating",[371,3295,3296],{"class":430}," func",[371,3298,3299],{"class":434}," schedule",[371,3301,804],{"class":385},[371,3303,396],{"class":385},[371,3305,809],{"class":385},[371,3307,396],{"class":385},[371,3309,463],{"class":389},[371,3311,468],{"class":385},[371,3313,3314,3316,3318,3320,3322,3324,3326,3328],{"class":373,"line":484},[371,3315,1020],{"class":385},[371,3317,1160],{"class":381},[371,3319,1163],{"class":506},[371,3321,1698],{"class":434},[371,3323,386],{"class":385},[371,3325,3115],{"class":381},[371,3327,393],{"class":385},[371,3329,417],{"class":385},[371,3331,3332],{"class":373,"line":490},[371,3333,860],{"class":385},[371,3335,3336],{"class":373,"line":495},[371,3337,487],{"class":385},[585,3339,3341],{"id":3340},"一次请求闭包delegate通知选哪个","一次请求，闭包、Delegate、通知选哪个",[179,3343,3344,3345,3348],{},"请求方要这一次的结果，用闭包，成功和失败放在同一个 ",[186,3346,3347],{},"Result"," 里。",[179,3350,3351],{},"这一屏有加载、点击、空态好几件相关的事，用 Delegate。",[179,3353,3354],{},"登录态变了，设置页、首页、播放器都要知道，用通知。发送方不用认识这些页面。",[585,3356,3357],{"id":3357},"追问",[179,3359,3360,3363],{},[205,3361,3362],{},"两个闭包改同一个局部变量，结果串了。"," 它们是同一次函数调用里捕获的同一个变量，box 只有一块。要各算各的，分成两次函数调用，或者在捕获列表里各自拷一份。",[179,3365,3366,3369,3370,3372],{},[205,3367,3368],{},"闭包赋值之后，捕获状态被共享。"," ",[186,3371,948],{}," 复制的是引用。要一份新的状态，再调用一次创建闭包的函数。",[179,3374,3375,3380,3381,3384],{},[205,3376,3377,189],{},[186,3378,3379],{},"@autoclosure"," 调用处写普通表达式，函数里把它当成闭包，执行到那一行才求值。",[186,3382,3383],{},"assert"," 的条件就是这样。业务接口少用，对方问再答。",[179,3386,3387,3390,3391,3393,3394,3397],{},[205,3388,3389],{},"非逃逸闭包交不出去。"," 非逃逸参数不能直接传给 ",[186,3392,540],{}," 参数。确实只在当前函数返回前使用，可以用 ",[186,3395,3396],{},"withoutActuallyEscaping"," 做一次限定范围内的转换，函数返回前必须用完。",[179,3399,3400,3403,3404,3407],{},[205,3401,3402],{},"跨线程、跨 actor。"," 闭包要离开当前隔离域时，它自己和捕获的值都要能安全传递（",[186,3405,3406],{},"Sendable","）。被闭包共享、还能改的可变状态，是这里会被追问的点。Swift 6 会在编译期查。细节以后补。",[3409,3410,3411],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sjYur, html code.shiki .sjYur{--shiki-light:#6182B8;--shiki-light-font-style:italic;--shiki-default:#82AAFF;--shiki-default-font-style:italic;--shiki-dark:#82AAFF;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":367,"searchDepth":420,"depth":420,"links":3413},[3414,3415,3419,3422,3427,3428,3429],{"id":193,"depth":420,"text":194},{"id":285,"depth":420,"text":285,"children":3416},[3417,3418],{"id":587,"depth":427,"text":588},{"id":712,"depth":427,"text":713},{"id":779,"depth":420,"text":779,"children":3420},[3421],{"id":1122,"depth":427,"text":1122},{"id":1563,"depth":420,"text":1563,"children":3423},[3424,3425,3426],{"id":1739,"depth":427,"text":1739},{"id":1911,"depth":427,"text":1911},{"id":1950,"depth":427,"text":1951},{"id":2548,"depth":420,"text":2549},{"id":2680,"depth":420,"text":2680},{"id":2834,"depth":420,"text":2834,"children":3430},[3431,3432,3433,3434,3435,3436,3437,3438,3439,3440,3441,3442],{"id":2840,"depth":427,"text":2841},{"id":2858,"depth":427,"text":2859},{"id":2882,"depth":427,"text":2883},{"id":2899,"depth":427,"text":2900},{"id":3000,"depth":427,"text":3001},{"id":3018,"depth":427,"text":3019},{"id":3050,"depth":427,"text":3050},{"id":3075,"depth":427,"text":3075},{"id":3185,"depth":427,"text":3186},{"id":3248,"depth":427,"text":3249},{"id":3340,"depth":427,"text":3341},{"id":3357,"depth":427,"text":3357},"先记 Swift 闭包：本质与 copy、变量捕获、循环引用，以及和 Delegate、通知怎么选。","md",[3446,3448],{"label":3447,"icon":109,"to":14},"UIKit 起步笔记",{"label":3449,"icon":3450,"to":3451,"target":3452},"Swift 闭包","i-lucide-book-open","https:\u002F\u002Fdocs.swift.org\u002Fswift-book\u002Fdocumentation\u002Fthe-swift-programming-language\u002Fclosures\u002F","_blank",{"date":3454,"categories":3455,"tags":3458},"2026-09-17",[3456,3457],"interview","ios",[108,146,3459,2563],"Swift",{"icon":119},{"title":3462,"description":3463},"高频 iOS 面试题 1","Swift 闭包面试题：类型与 copy、变量捕获、循环引用，以及和 Delegate、通知、Target-Action 怎么选。","58KrzDk1-PpYtuypxJnMvg2soC7p4UserfGH7yx0e5k",[3466,3468],{"title":108,"path":110,"stem":114,"description":3467,"icon":109,"children":-1},"iOS 开发实践与笔记。",{"title":121,"path":122,"stem":123,"description":3469,"icon":124,"children":-1},"在 Xcode 16 里给 UIKit 项目换启动图。Info 里的 UILaunchScreen 和 LaunchScreen.storyboard 都能做纯色背景、居中 Logo 或整张铺满。",1790767047955]