[{"data":1,"prerenderedAt":13610},["ShallowReactive",2],{"notes-mobile-engineer-interview":3},{"id":4,"title":5,"body":6,"date":13599,"description":13600,"extension":13601,"meta":13602,"navigation":356,"path":13603,"seo":13604,"seoDescription":13605,"seoTitle":13606,"slug":13607,"stem":13608,"__hash__":13609},"notes\u002Fnotes\u002F2026-05-03-mobile-engineer-interview.md","移动端全栈工程师面试题与详解",{"type":7,"value":8,"toc":13486},"minimark",[9,12,16,21,68,72,115,119,152,156,177,179,183,187,200,207,310,442,447,465,471,491,493,504,508,511,631,645,650,731,736,798,800,808,812,815,1121,1123,1130,1134,1137,1250,1255,1349,1351,1362,1366,1437,1513,1593,1595,1599,1603,1607,1618,1623,1713,1724,1775,1842,1847,1926,1928,1932,1936,1939,2009,2014,2025,2031,2033,2037,2041,2045,2050,2112,2117,2177,2182,2190,2195,2277,2279,2293,2297,2461,2466,2554,2559,2615,2617,2621,2625,2866,2871,2897,2899,2903,2907,2911,2916,2992,2997,3130,3135,3280,3285,3342,3344,3348,3352,3514,3519,3611,3613,3617,3621,3625,3630,3636,3639,3644,3801,3806,4010,4097,4099,4103,4107,4111,4193,4198,4398,4400,4404,4408,4412,4418,4423,4464,4554,4556,4560,4564,4567,4573,4578,4621,4664,4669,4683,4685,4689,4693,4699,4876,4881,4964,4966,4969,4973,4990,4994,5184,5186,5190,5194,5199,5265,5270,5374,5516,5518,5522,5526,5757,5759,5763,5767,5935,5937,5941,5945,5949,5955,5961,6106,6108,6112,6116,6122,6127,6323,6325,6329,6333,6338,6448,6453,6529,6534,6609,6611,6615,6619,6623,6684,6689,6921,6923,6927,6931,7193,7198,7272,7274,7278,7282,7559,7561,7565,7569,7573,7681,7686,7834,7836,7840,7844,7963,7968,8032,8037,8111,8113,8117,8121,8125,8131,8136,8320,8322,8326,8330,8527,8529,8533,8537,8541,8546,8658,8663,8752,8757,8828,8830,8834,8838,8907,9017,9019,9023,9027,9031,9037,9042,9096,9126,9128,9132,9136,9303,9305,9308,9312,9316,9320,9481,9483,9487,9491,9707,9709,9713,9717,9721,9727,9732,9779,9784,9790,9877,9879,9883,9887,9891,9896,10131,10136,10302,10384,10386,10390,10394,10398,10503,10561,10604,10609,10657,10662,10696,10740,10742,10746,10750,10754,11021,11023,11026,11030,11034,11038,11044,11049,11151,11189,11251,11256,11305,11362,11364,11368,11372,11449,11483,11512,11514,11518,11522,11526,11931,11936,11983,11985,11989,11993,11997,12003,12178,12183,12215,12217,12221,12225,12231,12392,12579,12658,12660,12664,12668,12674,12679,12725,12799,12804,12842,12844,12848,12852,12858,13250,13252,13256,13260,13319,13323,13382,13386,13436,13439,13482],[10,11],"hr",{},[13,14,15],"h2",{"id":15},"目录",[17,18,20],"h3",{"id":19},"第一部分ios-开发","第一部分：iOS 开发",[22,23,24,32,38,44,50,56,62],"ol",{},[25,26,27],"li",{},[28,29,31],"a",{"href":30},"#1-swift-%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80","Swift 语言基础",[25,33,34],{},[28,35,37],{"href":36},"#2-%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86","内存管理",[25,39,40],{},[28,41,43],{"href":42},"#3-uikit-%E4%B8%8E-swiftui","UIKit 与 SwiftUI",[25,45,46],{},[28,47,49],{"href":48},"#4-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B","并发编程",[25,51,52],{},[28,53,55],{"href":54},"#5-%E6%9E%B6%E6%9E%84%E4%B8%8E%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8Fios","架构与设计模式",[25,57,58],{},[28,59,61],{"href":60},"#6-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B8%8E%E5%B7%A5%E5%85%B7ios","性能优化与工具",[25,63,64],{},[28,65,67],{"href":66},"#7-%E7%B3%BB%E7%BB%9F%E6%9C%BA%E5%88%B6%E4%B8%8E%E6%A1%86%E6%9E%B6","系统机制与框架",[17,69,71],{"id":70},"第二部分android-开发","第二部分：Android 开发",[22,73,75,81,87,93,99,104,109],{"start":74},8,[25,76,77],{},[28,78,80],{"href":79},"#8-kotlin-%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80","Kotlin 语言基础",[25,82,83],{},[28,84,86],{"href":85},"#9-%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B8%8E%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F","四大组件与生命周期",[25,88,89],{},[28,90,92],{"href":91},"#10-jetpack-%E7%BB%84%E4%BB%B6","Jetpack 组件",[25,94,95],{},[28,96,98],{"href":97},"#11-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8Bcoroutines","并发编程（Coroutines）",[25,100,101],{},[28,102,55],{"href":103},"#12-%E6%9E%B6%E6%9E%84%E4%B8%8E%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8Fandroid",[25,105,106],{},[28,107,61],{"href":108},"#13-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B8%8E%E5%B7%A5%E5%85%B7android",[25,110,111],{},[28,112,114],{"href":113},"#14-%E7%B3%BB%E7%BB%9F%E6%9C%BA%E5%88%B6","系统机制",[17,116,118],{"id":117},"第三部分flutter-开发","第三部分：Flutter 开发",[22,120,122,128,134,140,146],{"start":121},15,[25,123,124],{},[28,125,127],{"href":126},"#15-dart-%E8%AF%AD%E8%A8%80%E4%B8%8E%E6%A0%B8%E5%BF%83%E6%9C%BA%E5%88%B6","Dart 语言与核心机制",[25,129,130],{},[28,131,133],{"href":132},"#16-%E6%B8%B2%E6%9F%93%E4%B8%8E%E4%B8%89%E6%A3%B5%E6%A0%91","渲染与三棵树",[25,135,136],{},[28,137,139],{"href":138},"#17-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86","状态管理",[25,141,142],{},[28,143,145],{"href":144},"#18-%E5%B9%B3%E5%8F%B0%E9%80%9A%E4%BF%A1%E4%B8%8E%E6%B7%B7%E5%90%88%E5%BC%80%E5%8F%91","平台通信与混合开发",[25,147,148],{},[28,149,151],{"href":150},"#19-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96flutter","性能优化",[17,153,155],{"id":154},"第四部分跨平台通用","第四部分：跨平台通用",[22,157,159,165,171],{"start":158},20,[25,160,161],{},[28,162,164],{"href":163},"#20-%E7%BD%91%E7%BB%9C%E4%B8%8E%E5%AE%89%E5%85%A8","网络与安全",[25,166,167],{},[28,168,170],{"href":169},"#21-cicd-%E4%B8%8E%E5%8F%91%E5%B8%83","CI\u002FCD 与发布",[25,172,173],{},[28,174,176],{"href":175},"#22-%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1","架构设计与系统设计",[10,178],{},[180,181,20],"h1",{"id":182},"第一部分ios-开发-1",[13,184,186],{"id":185},"_1-swift-语言基础","1. Swift 语言基础",[17,188,190,191,195,196,199],{"id":189},"q11-struct-和-class-的区别什么时候用哪个","Q1.1: ",[192,193,194],"code",{},"struct"," 和 ",[192,197,198],{},"class"," 的区别？什么时候用哪个？",[201,202,203],"p",{},[204,205,206],"strong",{},"答：",[208,209,210,226],"table",{},[211,212,213],"thead",{},[214,215,216,220,223],"tr",{},[217,218,219],"th",{},"特性",[217,221,222],{},"Struct（值类型）",[217,224,225],{},"Class（引用类型）",[227,228,229,241,252,263,274,285,298],"tbody",{},[214,230,231,235,238],{},[232,233,234],"td",{},"存储位置",[232,236,237],{},"通常在栈上（小对象）",[232,239,240],{},"堆上",[214,242,243,246,249],{},[232,244,245],{},"赋值行为",[232,247,248],{},"拷贝（Copy-on-Write）",[232,250,251],{},"共享引用",[214,253,254,257,260],{},[232,255,256],{},"继承",[232,258,259],{},"不支持",[232,261,262],{},"支持",[214,264,265,268,271],{},[232,266,267],{},"引用计数",[232,269,270],{},"无",[232,272,273],{},"ARC",[214,275,276,279,282],{},[232,277,278],{},"线程安全",[232,280,281],{},"天然安全（值拷贝）",[232,283,284],{},"需要同步",[214,286,287,292,295],{},[232,288,289],{},[192,290,291],{},"mutating",[232,293,294],{},"需要标记修改方法",[232,296,297],{},"不需要",[214,299,300,305,307],{},[232,301,302],{},[192,303,304],{},"deinit",[232,306,270],{},[232,308,309],{},"有析构器",[311,312,317],"pre",{"className":313,"code":314,"language":315,"meta":316,"style":316},"language-swift shiki shiki-themes github-light github-dark","\u002F\u002F Struct：值语义\nstruct Point {\n    var x: Double\n    var y: Double\n}\n\nvar a = Point(x: 1, y: 2)\nvar b = a       \u002F\u002F 拷贝\nb.x = 10\nprint(a.x)     \u002F\u002F 1，a 不受影响\n\n\u002F\u002F Class：引用语义\nclass Person {\n    var name: String\n    init(name: String) { self.name = name }\n}\n\nlet p1 = Person(name: \"Alice\")\nlet p2 = p1     \u002F\u002F 共享引用\np2.name = \"Bob\"\nprint(p1.name)  \u002F\u002F \"Bob\"，p1 也被修改了\n","swift","",[192,318,319,327,333,339,345,351,358,364,369,375,381,386,392,398,404,409,414,419,425,431,436],{"__ignoreMap":316},[320,321,324],"span",{"class":322,"line":323},"line",1,[320,325,326],{},"\u002F\u002F Struct：值语义\n",[320,328,330],{"class":322,"line":329},2,[320,331,332],{},"struct Point {\n",[320,334,336],{"class":322,"line":335},3,[320,337,338],{},"    var x: Double\n",[320,340,342],{"class":322,"line":341},4,[320,343,344],{},"    var y: Double\n",[320,346,348],{"class":322,"line":347},5,[320,349,350],{},"}\n",[320,352,354],{"class":322,"line":353},6,[320,355,357],{"emptyLinePlaceholder":356},true,"\n",[320,359,361],{"class":322,"line":360},7,[320,362,363],{},"var a = Point(x: 1, y: 2)\n",[320,365,366],{"class":322,"line":74},[320,367,368],{},"var b = a       \u002F\u002F 拷贝\n",[320,370,372],{"class":322,"line":371},9,[320,373,374],{},"b.x = 10\n",[320,376,378],{"class":322,"line":377},10,[320,379,380],{},"print(a.x)     \u002F\u002F 1，a 不受影响\n",[320,382,384],{"class":322,"line":383},11,[320,385,357],{"emptyLinePlaceholder":356},[320,387,389],{"class":322,"line":388},12,[320,390,391],{},"\u002F\u002F Class：引用语义\n",[320,393,395],{"class":322,"line":394},13,[320,396,397],{},"class Person {\n",[320,399,401],{"class":322,"line":400},14,[320,402,403],{},"    var name: String\n",[320,405,406],{"class":322,"line":121},[320,407,408],{},"    init(name: String) { self.name = name }\n",[320,410,412],{"class":322,"line":411},16,[320,413,350],{},[320,415,417],{"class":322,"line":416},17,[320,418,357],{"emptyLinePlaceholder":356},[320,420,422],{"class":322,"line":421},18,[320,423,424],{},"let p1 = Person(name: \"Alice\")\n",[320,426,428],{"class":322,"line":427},19,[320,429,430],{},"let p2 = p1     \u002F\u002F 共享引用\n",[320,432,433],{"class":322,"line":158},[320,434,435],{},"p2.name = \"Bob\"\n",[320,437,439],{"class":322,"line":438},21,[320,440,441],{},"print(p1.name)  \u002F\u002F \"Bob\"，p1 也被修改了\n",[201,443,444],{},[204,445,446],{},"选择指南（Apple 官方建议）：",[448,449,450,456],"ul",{},[25,451,452,455],{},[204,453,454],{},"默认用 Struct","：数据模型、坐标、配置等",[25,457,458,461,462,464],{},[204,459,460],{},"用 Class","：需要继承、需要引用语义（身份标识）、需要 ",[192,463,304],{},"、需要与 Objective-C 互操作",[201,466,467,470],{},[204,468,469],{},"Copy-on-Write (COW)："," Swift 标准库的集合类型（Array、Dictionary、Set）使用 COW 优化：只在真正修改且有多个引用时才执行拷贝。",[311,472,474],{"className":313,"code":473,"language":315,"meta":316,"style":316},"var arr1 = [1, 2, 3]\nvar arr2 = arr1      \u002F\u002F 此时共享底层存储，未拷贝\narr2.append(4)       \u002F\u002F 修改时才触发拷贝\n",[192,475,476,481,486],{"__ignoreMap":316},[320,477,478],{"class":322,"line":323},[320,479,480],{},"var arr1 = [1, 2, 3]\n",[320,482,483],{"class":322,"line":329},[320,484,485],{},"var arr2 = arr1      \u002F\u002F 此时共享底层存储，未拷贝\n",[320,487,488],{"class":322,"line":335},[320,489,490],{},"arr2.append(4)       \u002F\u002F 修改时才触发拷贝\n",[10,492],{},[17,494,496,497,195,500,503],{"id":495},"q12-swift-中的-protocol-和-protocol-oriented-programming-是什么","Q1.2: Swift 中的 ",[192,498,499],{},"Protocol",[192,501,502],{},"Protocol-Oriented Programming"," 是什么？",[201,505,506],{},[204,507,206],{},[201,509,510],{},"Protocol 定义行为契约，是 Swift 实现多态和代码复用的核心机制：",[311,512,514],{"className":313,"code":513,"language":315,"meta":316,"style":316},"protocol Drawable {\n    func draw()\n}\n\n\u002F\u002F Protocol Extension：提供默认实现\nextension Drawable {\n    func draw() {\n        print(\"Default drawing\")\n    }\n\n    \u002F\u002F 非协议要求的方法：静态派发\n    func description() -> String {\n        return \"A drawable object\"\n    }\n}\n\nstruct Circle: Drawable {\n    func draw() { print(\"Drawing circle\") }         \u002F\u002F 动态派发\n    func description() -> String { return \"Circle\" } \u002F\u002F ⚠️ 静态派发！\n}\n\nlet shape: Drawable = Circle()\nshape.draw()          \u002F\u002F \"Drawing circle\" ✅ 动态派发\nshape.description()   \u002F\u002F \"A drawable object\" ⚠️ 调用的是 extension 的默认实现！\n",[192,515,516,521,526,530,534,539,544,549,554,559,563,568,573,578,582,586,590,595,600,605,609,613,619,625],{"__ignoreMap":316},[320,517,518],{"class":322,"line":323},[320,519,520],{},"protocol Drawable {\n",[320,522,523],{"class":322,"line":329},[320,524,525],{},"    func draw()\n",[320,527,528],{"class":322,"line":335},[320,529,350],{},[320,531,532],{"class":322,"line":341},[320,533,357],{"emptyLinePlaceholder":356},[320,535,536],{"class":322,"line":347},[320,537,538],{},"\u002F\u002F Protocol Extension：提供默认实现\n",[320,540,541],{"class":322,"line":353},[320,542,543],{},"extension Drawable {\n",[320,545,546],{"class":322,"line":360},[320,547,548],{},"    func draw() {\n",[320,550,551],{"class":322,"line":74},[320,552,553],{},"        print(\"Default drawing\")\n",[320,555,556],{"class":322,"line":371},[320,557,558],{},"    }\n",[320,560,561],{"class":322,"line":377},[320,562,357],{"emptyLinePlaceholder":356},[320,564,565],{"class":322,"line":383},[320,566,567],{},"    \u002F\u002F 非协议要求的方法：静态派发\n",[320,569,570],{"class":322,"line":388},[320,571,572],{},"    func description() -> String {\n",[320,574,575],{"class":322,"line":394},[320,576,577],{},"        return \"A drawable object\"\n",[320,579,580],{"class":322,"line":400},[320,581,558],{},[320,583,584],{"class":322,"line":121},[320,585,350],{},[320,587,588],{"class":322,"line":411},[320,589,357],{"emptyLinePlaceholder":356},[320,591,592],{"class":322,"line":416},[320,593,594],{},"struct Circle: Drawable {\n",[320,596,597],{"class":322,"line":421},[320,598,599],{},"    func draw() { print(\"Drawing circle\") }         \u002F\u002F 动态派发\n",[320,601,602],{"class":322,"line":427},[320,603,604],{},"    func description() -> String { return \"Circle\" } \u002F\u002F ⚠️ 静态派发！\n",[320,606,607],{"class":322,"line":158},[320,608,350],{},[320,610,611],{"class":322,"line":438},[320,612,357],{"emptyLinePlaceholder":356},[320,614,616],{"class":322,"line":615},22,[320,617,618],{},"let shape: Drawable = Circle()\n",[320,620,622],{"class":322,"line":621},23,[320,623,624],{},"shape.draw()          \u002F\u002F \"Drawing circle\" ✅ 动态派发\n",[320,626,628],{"class":322,"line":627},24,[320,629,630],{},"shape.description()   \u002F\u002F \"A drawable object\" ⚠️ 调用的是 extension 的默认实现！\n",[201,632,633,636,637,640,641,644],{},[204,634,635],{},"关键陷阱："," Protocol Extension 中定义的方法如果",[204,638,639],{},"不在协议声明中","，则使用",[204,642,643],{},"静态派发","，根据编译时类型决定调用哪个实现。",[201,646,647],{},[204,648,649],{},"Protocol 与泛型约束：",[311,651,653],{"className":313,"code":652,"language":315,"meta":316,"style":316},"\u002F\u002F 关联类型（Associated Type）\nprotocol Container {\n    associatedtype Item\n    var count: Int { get }\n    mutating func append(_ item: Item)\n    subscript(i: Int) -> Item { get }\n}\n\n\u002F\u002F 泛型约束\nfunc findIndex\u003CT: Equatable>(of value: T, in array: [T]) -> Int? {\n    return array.firstIndex(of: value)\n}\n\n\u002F\u002F any vs some\nfunc draw(shape: any Drawable) { }  \u002F\u002F 存在类型（运行时多态，有开销）\nfunc draw(shape: some Drawable) { } \u002F\u002F 不透明类型（编译时确定，零开销）\n",[192,654,655,660,665,670,675,680,685,689,693,698,703,708,712,716,721,726],{"__ignoreMap":316},[320,656,657],{"class":322,"line":323},[320,658,659],{},"\u002F\u002F 关联类型（Associated Type）\n",[320,661,662],{"class":322,"line":329},[320,663,664],{},"protocol Container {\n",[320,666,667],{"class":322,"line":335},[320,668,669],{},"    associatedtype Item\n",[320,671,672],{"class":322,"line":341},[320,673,674],{},"    var count: Int { get }\n",[320,676,677],{"class":322,"line":347},[320,678,679],{},"    mutating func append(_ item: Item)\n",[320,681,682],{"class":322,"line":353},[320,683,684],{},"    subscript(i: Int) -> Item { get }\n",[320,686,687],{"class":322,"line":360},[320,688,350],{},[320,690,691],{"class":322,"line":74},[320,692,357],{"emptyLinePlaceholder":356},[320,694,695],{"class":322,"line":371},[320,696,697],{},"\u002F\u002F 泛型约束\n",[320,699,700],{"class":322,"line":377},[320,701,702],{},"func findIndex\u003CT: Equatable>(of value: T, in array: [T]) -> Int? {\n",[320,704,705],{"class":322,"line":383},[320,706,707],{},"    return array.firstIndex(of: value)\n",[320,709,710],{"class":322,"line":388},[320,711,350],{},[320,713,714],{"class":322,"line":394},[320,715,357],{"emptyLinePlaceholder":356},[320,717,718],{"class":322,"line":400},[320,719,720],{},"\u002F\u002F any vs some\n",[320,722,723],{"class":322,"line":121},[320,724,725],{},"func draw(shape: any Drawable) { }  \u002F\u002F 存在类型（运行时多态，有开销）\n",[320,727,728],{"class":322,"line":411},[320,729,730],{},"func draw(shape: some Drawable) { } \u002F\u002F 不透明类型（编译时确定，零开销）\n",[201,732,733],{},[204,734,735],{},"POP vs OOP：",[311,737,739],{"className":313,"code":738,"language":315,"meta":316,"style":316},"\u002F\u002F OOP：通过继承复用（单继承限制、紧耦合）\nclass Animal { func eat() { } }\nclass Dog: Animal { func bark() { } }\n\n\u002F\u002F POP：通过组合复用（灵活、可用于值类型）\nprotocol Eating { func eat() }\nprotocol Barking { func bark() }\n\nextension Eating { func eat() { print(\"Eating\") } }\nextension Barking { func bark() { print(\"Barking\") } }\n\nstruct Dog: Eating, Barking { }  \u002F\u002F 组合多个能力\n",[192,740,741,746,751,756,760,765,770,775,779,784,789,793],{"__ignoreMap":316},[320,742,743],{"class":322,"line":323},[320,744,745],{},"\u002F\u002F OOP：通过继承复用（单继承限制、紧耦合）\n",[320,747,748],{"class":322,"line":329},[320,749,750],{},"class Animal { func eat() { } }\n",[320,752,753],{"class":322,"line":335},[320,754,755],{},"class Dog: Animal { func bark() { } }\n",[320,757,758],{"class":322,"line":341},[320,759,357],{"emptyLinePlaceholder":356},[320,761,762],{"class":322,"line":347},[320,763,764],{},"\u002F\u002F POP：通过组合复用（灵活、可用于值类型）\n",[320,766,767],{"class":322,"line":353},[320,768,769],{},"protocol Eating { func eat() }\n",[320,771,772],{"class":322,"line":360},[320,773,774],{},"protocol Barking { func bark() }\n",[320,776,777],{"class":322,"line":74},[320,778,357],{"emptyLinePlaceholder":356},[320,780,781],{"class":322,"line":371},[320,782,783],{},"extension Eating { func eat() { print(\"Eating\") } }\n",[320,785,786],{"class":322,"line":377},[320,787,788],{},"extension Barking { func bark() { print(\"Barking\") } }\n",[320,790,791],{"class":322,"line":383},[320,792,357],{"emptyLinePlaceholder":356},[320,794,795],{"class":322,"line":388},[320,796,797],{},"struct Dog: Eating, Barking { }  \u002F\u002F 组合多个能力\n",[10,799],{},[17,801,803,804,807],{"id":802},"q13-swift-中的-enum-有哪些高级用法","Q1.3: Swift 中的 ",[192,805,806],{},"enum"," 有哪些高级用法？",[201,809,810],{},[204,811,206],{},[201,813,814],{},"Swift 的 enum 是一等类型，远比 C\u002FJava 的枚举强大：",[311,816,818],{"className":313,"code":817,"language":315,"meta":316,"style":316},"\u002F\u002F 1. 关联值（Associated Values）\nenum NetworkResult {\n    case success(data: Data, statusCode: Int)\n    case failure(error: Error)\n}\n\nlet result: NetworkResult = .success(data: data, statusCode: 200)\n\nswitch result {\ncase .success(let data, let statusCode) where statusCode == 200:\n    process(data)\ncase .success(_, let statusCode):\n    print(\"Unexpected status: \\(statusCode)\")\ncase .failure(let error):\n    print(\"Error: \\(error)\")\n}\n\n\u002F\u002F 2. 递归枚举\nindirect enum ArithExpr {\n    case number(Int)\n    case add(ArithExpr, ArithExpr)\n    case multiply(ArithExpr, ArithExpr)\n}\n\nfunc evaluate(_ expr: ArithExpr) -> Int {\n    switch expr {\n    case .number(let n): return n\n    case .add(let a, let b): return evaluate(a) + evaluate(b)\n    case .multiply(let a, let b): return evaluate(a) * evaluate(b)\n    }\n}\n\n\u002F\u002F 3. 遵循协议 + 计算属性\nenum Planet: CaseIterable, Comparable {\n    case mercury, venus, earth, mars\n\n    var distanceFromSun: Double {\n        switch self {\n        case .mercury: return 57.9\n        case .venus: return 108.2\n        case .earth: return 149.6\n        case .mars: return 227.9\n        }\n    }\n}\n\n\u002F\u002F 4. 用 enum 做命名空间（无 case 的 enum 不能被实例化）\nenum Constants {\n    static let apiBaseURL = \"https:\u002F\u002Fapi.example.com\"\n    static let maxRetries = 3\n}\n\n\u002F\u002F 5. Result 类型（标准库）\nenum Result\u003CSuccess, Failure: Error> {\n    case success(Success)\n    case failure(Failure)\n}\n",[192,819,820,825,830,835,840,844,848,853,857,862,867,872,877,882,887,892,896,900,905,910,915,920,925,929,933,939,945,951,957,963,968,973,978,984,990,996,1001,1007,1013,1019,1025,1031,1037,1043,1048,1053,1058,1064,1070,1076,1082,1087,1092,1098,1104,1110,1116],{"__ignoreMap":316},[320,821,822],{"class":322,"line":323},[320,823,824],{},"\u002F\u002F 1. 关联值（Associated Values）\n",[320,826,827],{"class":322,"line":329},[320,828,829],{},"enum NetworkResult {\n",[320,831,832],{"class":322,"line":335},[320,833,834],{},"    case success(data: Data, statusCode: Int)\n",[320,836,837],{"class":322,"line":341},[320,838,839],{},"    case failure(error: Error)\n",[320,841,842],{"class":322,"line":347},[320,843,350],{},[320,845,846],{"class":322,"line":353},[320,847,357],{"emptyLinePlaceholder":356},[320,849,850],{"class":322,"line":360},[320,851,852],{},"let result: NetworkResult = .success(data: data, statusCode: 200)\n",[320,854,855],{"class":322,"line":74},[320,856,357],{"emptyLinePlaceholder":356},[320,858,859],{"class":322,"line":371},[320,860,861],{},"switch result {\n",[320,863,864],{"class":322,"line":377},[320,865,866],{},"case .success(let data, let statusCode) where statusCode == 200:\n",[320,868,869],{"class":322,"line":383},[320,870,871],{},"    process(data)\n",[320,873,874],{"class":322,"line":388},[320,875,876],{},"case .success(_, let statusCode):\n",[320,878,879],{"class":322,"line":394},[320,880,881],{},"    print(\"Unexpected status: \\(statusCode)\")\n",[320,883,884],{"class":322,"line":400},[320,885,886],{},"case .failure(let error):\n",[320,888,889],{"class":322,"line":121},[320,890,891],{},"    print(\"Error: \\(error)\")\n",[320,893,894],{"class":322,"line":411},[320,895,350],{},[320,897,898],{"class":322,"line":416},[320,899,357],{"emptyLinePlaceholder":356},[320,901,902],{"class":322,"line":421},[320,903,904],{},"\u002F\u002F 2. 递归枚举\n",[320,906,907],{"class":322,"line":427},[320,908,909],{},"indirect enum ArithExpr {\n",[320,911,912],{"class":322,"line":158},[320,913,914],{},"    case number(Int)\n",[320,916,917],{"class":322,"line":438},[320,918,919],{},"    case add(ArithExpr, ArithExpr)\n",[320,921,922],{"class":322,"line":615},[320,923,924],{},"    case multiply(ArithExpr, ArithExpr)\n",[320,926,927],{"class":322,"line":621},[320,928,350],{},[320,930,931],{"class":322,"line":627},[320,932,357],{"emptyLinePlaceholder":356},[320,934,936],{"class":322,"line":935},25,[320,937,938],{},"func evaluate(_ expr: ArithExpr) -> Int {\n",[320,940,942],{"class":322,"line":941},26,[320,943,944],{},"    switch expr {\n",[320,946,948],{"class":322,"line":947},27,[320,949,950],{},"    case .number(let n): return n\n",[320,952,954],{"class":322,"line":953},28,[320,955,956],{},"    case .add(let a, let b): return evaluate(a) + evaluate(b)\n",[320,958,960],{"class":322,"line":959},29,[320,961,962],{},"    case .multiply(let a, let b): return evaluate(a) * evaluate(b)\n",[320,964,966],{"class":322,"line":965},30,[320,967,558],{},[320,969,971],{"class":322,"line":970},31,[320,972,350],{},[320,974,976],{"class":322,"line":975},32,[320,977,357],{"emptyLinePlaceholder":356},[320,979,981],{"class":322,"line":980},33,[320,982,983],{},"\u002F\u002F 3. 遵循协议 + 计算属性\n",[320,985,987],{"class":322,"line":986},34,[320,988,989],{},"enum Planet: CaseIterable, Comparable {\n",[320,991,993],{"class":322,"line":992},35,[320,994,995],{},"    case mercury, venus, earth, mars\n",[320,997,999],{"class":322,"line":998},36,[320,1000,357],{"emptyLinePlaceholder":356},[320,1002,1004],{"class":322,"line":1003},37,[320,1005,1006],{},"    var distanceFromSun: Double {\n",[320,1008,1010],{"class":322,"line":1009},38,[320,1011,1012],{},"        switch self {\n",[320,1014,1016],{"class":322,"line":1015},39,[320,1017,1018],{},"        case .mercury: return 57.9\n",[320,1020,1022],{"class":322,"line":1021},40,[320,1023,1024],{},"        case .venus: return 108.2\n",[320,1026,1028],{"class":322,"line":1027},41,[320,1029,1030],{},"        case .earth: return 149.6\n",[320,1032,1034],{"class":322,"line":1033},42,[320,1035,1036],{},"        case .mars: return 227.9\n",[320,1038,1040],{"class":322,"line":1039},43,[320,1041,1042],{},"        }\n",[320,1044,1046],{"class":322,"line":1045},44,[320,1047,558],{},[320,1049,1051],{"class":322,"line":1050},45,[320,1052,350],{},[320,1054,1056],{"class":322,"line":1055},46,[320,1057,357],{"emptyLinePlaceholder":356},[320,1059,1061],{"class":322,"line":1060},47,[320,1062,1063],{},"\u002F\u002F 4. 用 enum 做命名空间（无 case 的 enum 不能被实例化）\n",[320,1065,1067],{"class":322,"line":1066},48,[320,1068,1069],{},"enum Constants {\n",[320,1071,1073],{"class":322,"line":1072},49,[320,1074,1075],{},"    static let apiBaseURL = \"https:\u002F\u002Fapi.example.com\"\n",[320,1077,1079],{"class":322,"line":1078},50,[320,1080,1081],{},"    static let maxRetries = 3\n",[320,1083,1085],{"class":322,"line":1084},51,[320,1086,350],{},[320,1088,1090],{"class":322,"line":1089},52,[320,1091,357],{"emptyLinePlaceholder":356},[320,1093,1095],{"class":322,"line":1094},53,[320,1096,1097],{},"\u002F\u002F 5. Result 类型（标准库）\n",[320,1099,1101],{"class":322,"line":1100},54,[320,1102,1103],{},"enum Result\u003CSuccess, Failure: Error> {\n",[320,1105,1107],{"class":322,"line":1106},55,[320,1108,1109],{},"    case success(Success)\n",[320,1111,1113],{"class":322,"line":1112},56,[320,1114,1115],{},"    case failure(Failure)\n",[320,1117,1119],{"class":322,"line":1118},57,[320,1120,350],{},[10,1122],{},[17,1124,1126,1127],{"id":1125},"q14-解释-swift-的-propertywrapper","Q1.4: 解释 Swift 的 ",[192,1128,1129],{},"@propertyWrapper",[201,1131,1132],{},[204,1133,206],{},[201,1135,1136],{},"Property Wrapper 封装属性的存取逻辑，避免重复的样板代码：",[311,1138,1140],{"className":313,"code":1139,"language":315,"meta":316,"style":316},"\u002F\u002F 定义：限制值在指定范围内\n@propertyWrapper\nstruct Clamped\u003CValue: Comparable> {\n    var wrappedValue: Value {\n        didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }\n    }\n    let range: ClosedRange\u003CValue>\n\n    init(wrappedValue: Value, _ range: ClosedRange\u003CValue>) {\n        self.range = range\n        self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)\n    }\n}\n\n\u002F\u002F 使用\nstruct Player {\n    @Clamped(0...100) var health: Int = 100\n    @Clamped(0...999) var score: Int = 0\n}\n\nvar player = Player()\nplayer.health = 150  \u002F\u002F 被钳制为 100\nplayer.health = -10  \u002F\u002F 被钳制为 0\n",[192,1141,1142,1147,1152,1157,1162,1167,1171,1176,1180,1185,1190,1195,1199,1203,1207,1212,1217,1222,1227,1231,1235,1240,1245],{"__ignoreMap":316},[320,1143,1144],{"class":322,"line":323},[320,1145,1146],{},"\u002F\u002F 定义：限制值在指定范围内\n",[320,1148,1149],{"class":322,"line":329},[320,1150,1151],{},"@propertyWrapper\n",[320,1153,1154],{"class":322,"line":335},[320,1155,1156],{},"struct Clamped\u003CValue: Comparable> {\n",[320,1158,1159],{"class":322,"line":341},[320,1160,1161],{},"    var wrappedValue: Value {\n",[320,1163,1164],{"class":322,"line":347},[320,1165,1166],{},"        didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }\n",[320,1168,1169],{"class":322,"line":353},[320,1170,558],{},[320,1172,1173],{"class":322,"line":360},[320,1174,1175],{},"    let range: ClosedRange\u003CValue>\n",[320,1177,1178],{"class":322,"line":74},[320,1179,357],{"emptyLinePlaceholder":356},[320,1181,1182],{"class":322,"line":371},[320,1183,1184],{},"    init(wrappedValue: Value, _ range: ClosedRange\u003CValue>) {\n",[320,1186,1187],{"class":322,"line":377},[320,1188,1189],{},"        self.range = range\n",[320,1191,1192],{"class":322,"line":383},[320,1193,1194],{},"        self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)\n",[320,1196,1197],{"class":322,"line":388},[320,1198,558],{},[320,1200,1201],{"class":322,"line":394},[320,1202,350],{},[320,1204,1205],{"class":322,"line":400},[320,1206,357],{"emptyLinePlaceholder":356},[320,1208,1209],{"class":322,"line":121},[320,1210,1211],{},"\u002F\u002F 使用\n",[320,1213,1214],{"class":322,"line":411},[320,1215,1216],{},"struct Player {\n",[320,1218,1219],{"class":322,"line":416},[320,1220,1221],{},"    @Clamped(0...100) var health: Int = 100\n",[320,1223,1224],{"class":322,"line":421},[320,1225,1226],{},"    @Clamped(0...999) var score: Int = 0\n",[320,1228,1229],{"class":322,"line":427},[320,1230,350],{},[320,1232,1233],{"class":322,"line":158},[320,1234,357],{"emptyLinePlaceholder":356},[320,1236,1237],{"class":322,"line":438},[320,1238,1239],{},"var player = Player()\n",[320,1241,1242],{"class":322,"line":615},[320,1243,1244],{},"player.health = 150  \u002F\u002F 被钳制为 100\n",[320,1246,1247],{"class":322,"line":621},[320,1248,1249],{},"player.health = -10  \u002F\u002F 被钳制为 0\n",[201,1251,1252],{},[204,1253,1254],{},"SwiftUI 中的常见 Property Wrapper：",[208,1256,1257,1267],{},[211,1258,1259],{},[214,1260,1261,1264],{},[217,1262,1263],{},"Wrapper",[217,1265,1266],{},"用途",[227,1268,1269,1279,1289,1299,1309,1319,1329,1339],{},[214,1270,1271,1276],{},[232,1272,1273],{},[192,1274,1275],{},"@State",[232,1277,1278],{},"View 私有状态，值类型",[214,1280,1281,1286],{},[232,1282,1283],{},[192,1284,1285],{},"@Binding",[232,1287,1288],{},"父子 View 之间的双向绑定",[214,1290,1291,1296],{},[232,1292,1293],{},[192,1294,1295],{},"@ObservedObject",[232,1297,1298],{},"外部传入的可观察对象",[214,1300,1301,1306],{},[232,1302,1303],{},[192,1304,1305],{},"@StateObject",[232,1307,1308],{},"View 拥有的可观察对象（生命周期绑定）",[214,1310,1311,1316],{},[232,1312,1313],{},[192,1314,1315],{},"@EnvironmentObject",[232,1317,1318],{},"通过环境注入的共享对象",[214,1320,1321,1326],{},[232,1322,1323],{},[192,1324,1325],{},"@Environment",[232,1327,1328],{},"读取系统环境值（如 colorScheme）",[214,1330,1331,1336],{},[232,1332,1333],{},[192,1334,1335],{},"@Published",[232,1337,1338],{},"属性变更时自动通知订阅者",[214,1340,1341,1346],{},[232,1342,1343],{},[192,1344,1345],{},"@AppStorage",[232,1347,1348],{},"UserDefaults 的声明式封装",[10,1350],{},[17,1352,1354,1355,195,1358,1361],{"id":1353},"q15-some-和-any-关键字的区别是什么","Q1.5: ",[192,1356,1357],{},"some",[192,1359,1360],{},"any"," 关键字的区别是什么？",[201,1363,1364],{},[204,1365,206],{},[311,1367,1369],{"className":313,"code":1368,"language":315,"meta":316,"style":316},"\u002F\u002F some：不透明类型（Opaque Type）\n\u002F\u002F 编译时确定具体类型，但对调用者隐藏\nfunc makeShape() -> some Shape {\n    return Circle(radius: 10)\n    \u002F\u002F 每次调用返回同一具体类型（编译器保证）\n}\n\n\u002F\u002F any：存在类型（Existential Type）\n\u002F\u002F 运行时可以是任意遵循协议的类型\nfunc draw(shapes: [any Shape]) {\n    for shape in shapes {\n        shape.draw() \u002F\u002F 运行时动态派发\n    }\n}\n",[192,1370,1371,1376,1381,1386,1391,1396,1400,1404,1409,1414,1419,1424,1429,1433],{"__ignoreMap":316},[320,1372,1373],{"class":322,"line":323},[320,1374,1375],{},"\u002F\u002F some：不透明类型（Opaque Type）\n",[320,1377,1378],{"class":322,"line":329},[320,1379,1380],{},"\u002F\u002F 编译时确定具体类型，但对调用者隐藏\n",[320,1382,1383],{"class":322,"line":335},[320,1384,1385],{},"func makeShape() -> some Shape {\n",[320,1387,1388],{"class":322,"line":341},[320,1389,1390],{},"    return Circle(radius: 10)\n",[320,1392,1393],{"class":322,"line":347},[320,1394,1395],{},"    \u002F\u002F 每次调用返回同一具体类型（编译器保证）\n",[320,1397,1398],{"class":322,"line":353},[320,1399,350],{},[320,1401,1402],{"class":322,"line":360},[320,1403,357],{"emptyLinePlaceholder":356},[320,1405,1406],{"class":322,"line":74},[320,1407,1408],{},"\u002F\u002F any：存在类型（Existential Type）\n",[320,1410,1411],{"class":322,"line":371},[320,1412,1413],{},"\u002F\u002F 运行时可以是任意遵循协议的类型\n",[320,1415,1416],{"class":322,"line":377},[320,1417,1418],{},"func draw(shapes: [any Shape]) {\n",[320,1420,1421],{"class":322,"line":383},[320,1422,1423],{},"    for shape in shapes {\n",[320,1425,1426],{"class":322,"line":388},[320,1427,1428],{},"        shape.draw() \u002F\u002F 运行时动态派发\n",[320,1430,1431],{"class":322,"line":394},[320,1432,558],{},[320,1434,1435],{"class":322,"line":400},[320,1436,350],{},[208,1438,1439,1453],{},[211,1440,1441],{},[214,1442,1443,1445,1449],{},[217,1444],{},[217,1446,1447],{},[192,1448,1357],{},[217,1450,1451],{},[192,1452,1360],{},[227,1454,1455,1466,1477,1491,1502],{},[214,1456,1457,1460,1463],{},[232,1458,1459],{},"派发方式",[232,1461,1462],{},"静态派发（零开销）",[232,1464,1465],{},"动态派发（有开销）",[214,1467,1468,1471,1474],{},[232,1469,1470],{},"类型一致性",[232,1472,1473],{},"必须始终是同一具体类型",[232,1475,1476],{},"可以是不同类型",[214,1478,1479,1482,1485],{},[232,1480,1481],{},"放在集合中",[232,1483,1484],{},"不能（类型不统一）",[232,1486,1487,1488],{},"可以 ",[192,1489,1490],{},"[any Protocol]",[214,1492,1493,1496,1499],{},[232,1494,1495],{},"性能",[232,1497,1498],{},"更好",[232,1500,1501],{},"有装箱开销（Existential Container）",[214,1503,1504,1507,1510],{},[232,1505,1506],{},"使用场景",[232,1508,1509],{},"函数返回值、SwiftUI body",[232,1511,1512],{},"异构集合、需要运行时多态",[311,1514,1516],{"className":313,"code":1515,"language":315,"meta":316,"style":316},"\u002F\u002F SwiftUI 中 some 是必须的\nstruct ContentView: View {\n    var body: some View {  \u002F\u002F 编译器知道具体类型\n        VStack {\n            Text(\"Hello\")\n            Image(systemName: \"star\")\n        }\n    }\n}\n\n\u002F\u002F 需要 any 的场景\nprotocol Animal { func sound() -> String }\nstruct Dog: Animal { func sound() -> String { \"Woof\" } }\nstruct Cat: Animal { func sound() -> String { \"Meow\" } }\n\nlet pets: [any Animal] = [Dog(), Cat()] \u002F\u002F 异构集合必须用 any\n",[192,1517,1518,1523,1528,1533,1538,1543,1548,1552,1556,1560,1564,1569,1574,1579,1584,1588],{"__ignoreMap":316},[320,1519,1520],{"class":322,"line":323},[320,1521,1522],{},"\u002F\u002F SwiftUI 中 some 是必须的\n",[320,1524,1525],{"class":322,"line":329},[320,1526,1527],{},"struct ContentView: View {\n",[320,1529,1530],{"class":322,"line":335},[320,1531,1532],{},"    var body: some View {  \u002F\u002F 编译器知道具体类型\n",[320,1534,1535],{"class":322,"line":341},[320,1536,1537],{},"        VStack {\n",[320,1539,1540],{"class":322,"line":347},[320,1541,1542],{},"            Text(\"Hello\")\n",[320,1544,1545],{"class":322,"line":353},[320,1546,1547],{},"            Image(systemName: \"star\")\n",[320,1549,1550],{"class":322,"line":360},[320,1551,1042],{},[320,1553,1554],{"class":322,"line":74},[320,1555,558],{},[320,1557,1558],{"class":322,"line":371},[320,1559,350],{},[320,1561,1562],{"class":322,"line":377},[320,1563,357],{"emptyLinePlaceholder":356},[320,1565,1566],{"class":322,"line":383},[320,1567,1568],{},"\u002F\u002F 需要 any 的场景\n",[320,1570,1571],{"class":322,"line":388},[320,1572,1573],{},"protocol Animal { func sound() -> String }\n",[320,1575,1576],{"class":322,"line":394},[320,1577,1578],{},"struct Dog: Animal { func sound() -> String { \"Woof\" } }\n",[320,1580,1581],{"class":322,"line":400},[320,1582,1583],{},"struct Cat: Animal { func sound() -> String { \"Meow\" } }\n",[320,1585,1586],{"class":322,"line":121},[320,1587,357],{"emptyLinePlaceholder":356},[320,1589,1590],{"class":322,"line":411},[320,1591,1592],{},"let pets: [any Animal] = [Dog(), Cat()] \u002F\u002F 异构集合必须用 any\n",[10,1594],{},[13,1596,1598],{"id":1597},"_2-内存管理","2. 内存管理",[17,1600,1602],{"id":1601},"q21-arcautomatic-reference-counting的工作原理如何解决循环引用","Q2.1: ARC（Automatic Reference Counting）的工作原理？如何解决循环引用？",[201,1604,1605],{},[204,1606,206],{},[201,1608,1609,1610,1613,1614,1617],{},"ARC 在编译时自动插入 ",[192,1611,1612],{},"retain","（引用计数 +1）和 ",[192,1615,1616],{},"release","（引用计数 -1）调用。当引用计数归零时，对象被销毁。",[201,1619,1620],{},[204,1621,1622],{},"循环引用（Retain Cycle）：",[311,1624,1626],{"className":313,"code":1625,"language":315,"meta":316,"style":316},"\u002F\u002F ❌ 循环引用：两个对象互相强引用\nclass Person {\n    var apartment: Apartment?\n    deinit { print(\"Person deinit\") } \u002F\u002F 永远不会调用\n}\n\nclass Apartment {\n    var tenant: Person?   \u002F\u002F 强引用\n    deinit { print(\"Apartment deinit\") } \u002F\u002F 永远不会调用\n}\n\nvar john: Person? = Person()\nvar unit: Apartment? = Apartment()\njohn?.apartment = unit\nunit?.tenant = john\njohn = nil  \u002F\u002F 引用计数 1（Apartment 还持有）\nunit = nil  \u002F\u002F 引用计数 1（Person 还持有）\n\u002F\u002F 内存泄漏！\n",[192,1627,1628,1633,1637,1642,1647,1651,1655,1660,1665,1670,1674,1678,1683,1688,1693,1698,1703,1708],{"__ignoreMap":316},[320,1629,1630],{"class":322,"line":323},[320,1631,1632],{},"\u002F\u002F ❌ 循环引用：两个对象互相强引用\n",[320,1634,1635],{"class":322,"line":329},[320,1636,397],{},[320,1638,1639],{"class":322,"line":335},[320,1640,1641],{},"    var apartment: Apartment?\n",[320,1643,1644],{"class":322,"line":341},[320,1645,1646],{},"    deinit { print(\"Person deinit\") } \u002F\u002F 永远不会调用\n",[320,1648,1649],{"class":322,"line":347},[320,1650,350],{},[320,1652,1653],{"class":322,"line":353},[320,1654,357],{"emptyLinePlaceholder":356},[320,1656,1657],{"class":322,"line":360},[320,1658,1659],{},"class Apartment {\n",[320,1661,1662],{"class":322,"line":74},[320,1663,1664],{},"    var tenant: Person?   \u002F\u002F 强引用\n",[320,1666,1667],{"class":322,"line":371},[320,1668,1669],{},"    deinit { print(\"Apartment deinit\") } \u002F\u002F 永远不会调用\n",[320,1671,1672],{"class":322,"line":377},[320,1673,350],{},[320,1675,1676],{"class":322,"line":383},[320,1677,357],{"emptyLinePlaceholder":356},[320,1679,1680],{"class":322,"line":388},[320,1681,1682],{},"var john: Person? = Person()\n",[320,1684,1685],{"class":322,"line":394},[320,1686,1687],{},"var unit: Apartment? = Apartment()\n",[320,1689,1690],{"class":322,"line":400},[320,1691,1692],{},"john?.apartment = unit\n",[320,1694,1695],{"class":322,"line":121},[320,1696,1697],{},"unit?.tenant = john\n",[320,1699,1700],{"class":322,"line":411},[320,1701,1702],{},"john = nil  \u002F\u002F 引用计数 1（Apartment 还持有）\n",[320,1704,1705],{"class":322,"line":416},[320,1706,1707],{},"unit = nil  \u002F\u002F 引用计数 1（Person 还持有）\n",[320,1709,1710],{"class":322,"line":421},[320,1711,1712],{},"\u002F\u002F 内存泄漏！\n",[201,1714,1715],{},[204,1716,1717,1718,195,1721],{},"解决方案：",[192,1719,1720],{},"weak",[192,1722,1723],{},"unowned",[311,1725,1727],{"className":313,"code":1726,"language":315,"meta":316,"style":316},"class Apartment {\n    weak var tenant: Person?  \u002F\u002F 弱引用，不增加引用计数\n    \u002F\u002F tenant 被销毁后自动置为 nil\n}\n\nclass CreditCard {\n    unowned let owner: Person  \u002F\u002F 无主引用，不增加引用计数\n    \u002F\u002F 假设 owner 生命周期 >= CreditCard\n    \u002F\u002F owner 被销毁后访问会崩溃（类似野指针）\n}\n",[192,1728,1729,1733,1738,1743,1747,1751,1756,1761,1766,1771],{"__ignoreMap":316},[320,1730,1731],{"class":322,"line":323},[320,1732,1659],{},[320,1734,1735],{"class":322,"line":329},[320,1736,1737],{},"    weak var tenant: Person?  \u002F\u002F 弱引用，不增加引用计数\n",[320,1739,1740],{"class":322,"line":335},[320,1741,1742],{},"    \u002F\u002F tenant 被销毁后自动置为 nil\n",[320,1744,1745],{"class":322,"line":341},[320,1746,350],{},[320,1748,1749],{"class":322,"line":347},[320,1750,357],{"emptyLinePlaceholder":356},[320,1752,1753],{"class":322,"line":353},[320,1754,1755],{},"class CreditCard {\n",[320,1757,1758],{"class":322,"line":360},[320,1759,1760],{},"    unowned let owner: Person  \u002F\u002F 无主引用，不增加引用计数\n",[320,1762,1763],{"class":322,"line":74},[320,1764,1765],{},"    \u002F\u002F 假设 owner 生命周期 >= CreditCard\n",[320,1767,1768],{"class":322,"line":371},[320,1769,1770],{},"    \u002F\u002F owner 被销毁后访问会崩溃（类似野指针）\n",[320,1772,1773],{"class":322,"line":377},[320,1774,350],{},[208,1776,1777,1791],{},[211,1778,1779],{},[214,1780,1781,1783,1787],{},[217,1782],{},[217,1784,1785],{},[192,1786,1720],{},[217,1788,1789],{},[192,1790,1723],{},[227,1792,1793,1807,1821,1831],{},[214,1794,1795,1798,1804],{},[232,1796,1797],{},"类型",[232,1799,1800,1803],{},[192,1801,1802],{},"Optional","（可选）",[232,1805,1806],{},"非可选",[214,1808,1809,1812,1818],{},[232,1810,1811],{},"对象销毁后",[232,1813,1814,1815],{},"自动设为 ",[192,1816,1817],{},"nil",[232,1819,1820],{},"悬垂引用（访问崩溃）",[214,1822,1823,1825,1828],{},[232,1824,1495],{},[232,1826,1827],{},"略有开销（side table）",[232,1829,1830],{},"更轻量",[214,1832,1833,1836,1839],{},[232,1834,1835],{},"适用场景",[232,1837,1838],{},"引用对象可能先销毁",[232,1840,1841],{},"确信引用对象生命周期更长",[201,1843,1844],{},[204,1845,1846],{},"闭包中的循环引用：",[311,1848,1850],{"className":313,"code":1849,"language":315,"meta":316,"style":316},"class ViewController: UIViewController {\n    var name = \"VC\"\n\n    func setupTimer() {\n        \u002F\u002F ❌ 闭包隐式捕获 self（强引用）\n        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in\n            self.updateUI()  \u002F\u002F self 引用计数 +1\n        }\n\n        \u002F\u002F ✅ 使用 capture list 打破循环\n        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in\n            guard let self else { return }\n            self.updateUI()\n        }\n    }\n}\n",[192,1851,1852,1857,1862,1866,1871,1876,1881,1886,1890,1894,1899,1904,1909,1914,1918,1922],{"__ignoreMap":316},[320,1853,1854],{"class":322,"line":323},[320,1855,1856],{},"class ViewController: UIViewController {\n",[320,1858,1859],{"class":322,"line":329},[320,1860,1861],{},"    var name = \"VC\"\n",[320,1863,1864],{"class":322,"line":335},[320,1865,357],{"emptyLinePlaceholder":356},[320,1867,1868],{"class":322,"line":341},[320,1869,1870],{},"    func setupTimer() {\n",[320,1872,1873],{"class":322,"line":347},[320,1874,1875],{},"        \u002F\u002F ❌ 闭包隐式捕获 self（强引用）\n",[320,1877,1878],{"class":322,"line":353},[320,1879,1880],{},"        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in\n",[320,1882,1883],{"class":322,"line":360},[320,1884,1885],{},"            self.updateUI()  \u002F\u002F self 引用计数 +1\n",[320,1887,1888],{"class":322,"line":74},[320,1889,1042],{},[320,1891,1892],{"class":322,"line":371},[320,1893,357],{"emptyLinePlaceholder":356},[320,1895,1896],{"class":322,"line":377},[320,1897,1898],{},"        \u002F\u002F ✅ 使用 capture list 打破循环\n",[320,1900,1901],{"class":322,"line":383},[320,1902,1903],{},"        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in\n",[320,1905,1906],{"class":322,"line":388},[320,1907,1908],{},"            guard let self else { return }\n",[320,1910,1911],{"class":322,"line":394},[320,1912,1913],{},"            self.updateUI()\n",[320,1915,1916],{"class":322,"line":400},[320,1917,1042],{},[320,1919,1920],{"class":322,"line":121},[320,1921,558],{},[320,1923,1924],{"class":322,"line":411},[320,1925,350],{},[10,1927],{},[17,1929,1931],{"id":1930},"q22-什么是-autorelease-pool在-swift-中还需要关心它吗","Q2.2: 什么是 Autorelease Pool？在 Swift 中还需要关心它吗？",[201,1933,1934],{},[204,1935,206],{},[201,1937,1938],{},"Autorelease Pool 是 Objective-C MRC 时代的遗留机制，用于延迟释放对象：",[311,1940,1942],{"className":313,"code":1941,"language":315,"meta":316,"style":316},"\u002F\u002F Swift 中仍然需要关心的场景：\n\u002F\u002F 循环中大量创建临时对象时\nfor i in 0..\u003C1_000_000 {\n    \u002F\u002F ❌ 临时对象积压到 RunLoop 结束才释放\n    let image = processImage(data[i])\n}\n\n\u002F\u002F ✅ 用 autoreleasepool 及时释放\nfor i in 0..\u003C1_000_000 {\n    autoreleasepool {\n        let image = processImage(data[i])\n        \u002F\u002F 每次循环结束时释放 pool 中的对象\n    }\n}\n",[192,1943,1944,1949,1954,1959,1964,1969,1973,1977,1982,1986,1991,1996,2001,2005],{"__ignoreMap":316},[320,1945,1946],{"class":322,"line":323},[320,1947,1948],{},"\u002F\u002F Swift 中仍然需要关心的场景：\n",[320,1950,1951],{"class":322,"line":329},[320,1952,1953],{},"\u002F\u002F 循环中大量创建临时对象时\n",[320,1955,1956],{"class":322,"line":335},[320,1957,1958],{},"for i in 0..\u003C1_000_000 {\n",[320,1960,1961],{"class":322,"line":341},[320,1962,1963],{},"    \u002F\u002F ❌ 临时对象积压到 RunLoop 结束才释放\n",[320,1965,1966],{"class":322,"line":347},[320,1967,1968],{},"    let image = processImage(data[i])\n",[320,1970,1971],{"class":322,"line":353},[320,1972,350],{},[320,1974,1975],{"class":322,"line":360},[320,1976,357],{"emptyLinePlaceholder":356},[320,1978,1979],{"class":322,"line":74},[320,1980,1981],{},"\u002F\u002F ✅ 用 autoreleasepool 及时释放\n",[320,1983,1984],{"class":322,"line":371},[320,1985,1958],{},[320,1987,1988],{"class":322,"line":377},[320,1989,1990],{},"    autoreleasepool {\n",[320,1992,1993],{"class":322,"line":383},[320,1994,1995],{},"        let image = processImage(data[i])\n",[320,1997,1998],{"class":322,"line":388},[320,1999,2000],{},"        \u002F\u002F 每次循环结束时释放 pool 中的对象\n",[320,2002,2003],{"class":322,"line":394},[320,2004,558],{},[320,2006,2007],{"class":322,"line":400},[320,2008,350],{},[201,2010,2011],{},[204,2012,2013],{},"什么时候需要 autoreleasepool：",[448,2015,2016,2019,2022],{},[25,2017,2018],{},"循环中大量创建临时对象（特别是与 Objective-C 桥接的对象）",[25,2020,2021],{},"后台线程（没有自动的 RunLoop autorelease pool）",[25,2023,2024],{},"命令行工具（没有 UIApplication 管理的 pool）",[201,2026,2027,2030],{},[204,2028,2029],{},"主线程 RunLoop"," 每次迭代结束时会自动 drain autorelease pool，所以日常开发中大多不需要手动管理。",[10,2032],{},[13,2034,2036],{"id":2035},"_3-uikit-与-swiftui","3. UIKit 与 SwiftUI",[17,2038,2040],{"id":2039},"q31-swiftui-的视图更新机制是怎样的与-uikit-有什么本质区别","Q3.1: SwiftUI 的视图更新机制是怎样的？与 UIKit 有什么本质区别？",[201,2042,2043],{},[204,2044,206],{},[201,2046,2047],{},[204,2048,2049],{},"UIKit（命令式）：",[311,2051,2053],{"className":313,"code":2052,"language":315,"meta":316,"style":316},"\u002F\u002F 手动管理状态同步\nclass CounterVC: UIViewController {\n    var count = 0\n    let label = UILabel()\n\n    func increment() {\n        count += 1\n        label.text = \"\\(count)\"       \u002F\u002F 手动更新 UI\n        button.isEnabled = count \u003C 10  \u002F\u002F 手动更新 UI\n        \u002F\u002F 每个状态变化都要手动同步所有关联的 UI\n    }\n}\n",[192,2054,2055,2060,2065,2070,2075,2079,2084,2089,2094,2099,2104,2108],{"__ignoreMap":316},[320,2056,2057],{"class":322,"line":323},[320,2058,2059],{},"\u002F\u002F 手动管理状态同步\n",[320,2061,2062],{"class":322,"line":329},[320,2063,2064],{},"class CounterVC: UIViewController {\n",[320,2066,2067],{"class":322,"line":335},[320,2068,2069],{},"    var count = 0\n",[320,2071,2072],{"class":322,"line":341},[320,2073,2074],{},"    let label = UILabel()\n",[320,2076,2077],{"class":322,"line":347},[320,2078,357],{"emptyLinePlaceholder":356},[320,2080,2081],{"class":322,"line":353},[320,2082,2083],{},"    func increment() {\n",[320,2085,2086],{"class":322,"line":360},[320,2087,2088],{},"        count += 1\n",[320,2090,2091],{"class":322,"line":74},[320,2092,2093],{},"        label.text = \"\\(count)\"       \u002F\u002F 手动更新 UI\n",[320,2095,2096],{"class":322,"line":371},[320,2097,2098],{},"        button.isEnabled = count \u003C 10  \u002F\u002F 手动更新 UI\n",[320,2100,2101],{"class":322,"line":377},[320,2102,2103],{},"        \u002F\u002F 每个状态变化都要手动同步所有关联的 UI\n",[320,2105,2106],{"class":322,"line":383},[320,2107,558],{},[320,2109,2110],{"class":322,"line":388},[320,2111,350],{},[201,2113,2114],{},[204,2115,2116],{},"SwiftUI（声明式）：",[311,2118,2120],{"className":313,"code":2119,"language":315,"meta":316,"style":316},"struct CounterView: View {\n    @State private var count = 0\n\n    var body: some View {\n        VStack {\n            Text(\"\\(count)\")\n            Button(\"Increment\") { count += 1 }\n                .disabled(count >= 10)\n        }\n        \u002F\u002F 状态变化 → 自动重新计算 body → 高效 diff 更新\n    }\n}\n",[192,2121,2122,2127,2132,2136,2141,2145,2150,2155,2160,2164,2169,2173],{"__ignoreMap":316},[320,2123,2124],{"class":322,"line":323},[320,2125,2126],{},"struct CounterView: View {\n",[320,2128,2129],{"class":322,"line":329},[320,2130,2131],{},"    @State private var count = 0\n",[320,2133,2134],{"class":322,"line":335},[320,2135,357],{"emptyLinePlaceholder":356},[320,2137,2138],{"class":322,"line":341},[320,2139,2140],{},"    var body: some View {\n",[320,2142,2143],{"class":322,"line":347},[320,2144,1537],{},[320,2146,2147],{"class":322,"line":353},[320,2148,2149],{},"            Text(\"\\(count)\")\n",[320,2151,2152],{"class":322,"line":360},[320,2153,2154],{},"            Button(\"Increment\") { count += 1 }\n",[320,2156,2157],{"class":322,"line":74},[320,2158,2159],{},"                .disabled(count >= 10)\n",[320,2161,2162],{"class":322,"line":371},[320,2163,1042],{},[320,2165,2166],{"class":322,"line":377},[320,2167,2168],{},"        \u002F\u002F 状态变化 → 自动重新计算 body → 高效 diff 更新\n",[320,2170,2171],{"class":322,"line":383},[320,2172,558],{},[320,2174,2175],{"class":322,"line":388},[320,2176,350],{},[201,2178,2179],{},[204,2180,2181],{},"SwiftUI 更新机制：",[311,2183,2188],{"className":2184,"code":2186,"language":2187},[2185],"language-text","@State \u002F @ObservedObject 变化\n          ↓\n标记 View 为 invalid\n          ↓\n调用 body 属性（重新计算声明）\n          ↓\n与旧的 View 值进行结构对比（Diff）\n          ↓\n只更新变化的部分到底层 UIKit\u002FAppKit 视图\n","text",[192,2189,2186],{"__ignoreMap":316},[201,2191,2192],{},[204,2193,2194],{},"关键设计差异：",[208,2196,2197,2209],{},[211,2198,2199],{},[214,2200,2201,2203,2206],{},[217,2202],{},[217,2204,2205],{},"UIKit",[217,2207,2208],{},"SwiftUI",[227,2210,2211,2222,2233,2244,2255,2266],{},[214,2212,2213,2216,2219],{},[232,2214,2215],{},"View 本质",[232,2217,2218],{},"类（引用类型，长寿命对象）",[232,2220,2221],{},"结构体（值类型，轻量描述）",[214,2223,2224,2227,2230],{},[232,2225,2226],{},"更新方式",[232,2228,2229],{},"命令式（手动 set）",[232,2231,2232],{},"声明式（状态驱动）",[214,2234,2235,2238,2241],{},[232,2236,2237],{},"布局",[232,2239,2240],{},"Auto Layout（约束求解）",[232,2242,2243],{},"自带布局系统（Stack\u002FAlignment）",[214,2245,2246,2249,2252],{},[232,2247,2248],{},"数据流",[232,2250,2251],{},"Delegate\u002FKVO\u002FNotificationCenter",[232,2253,2254],{},"@State\u002F@Binding\u002FEnvironment",[214,2256,2257,2260,2263],{},[232,2258,2259],{},"动画",[232,2261,2262],{},"UIView.animate \u002F Core Animation",[232,2264,2265],{},"withAnimation \u002F .animation modifier",[214,2267,2268,2271,2274],{},[232,2269,2270],{},"生命周期",[232,2272,2273],{},"viewDidLoad\u002FviewWillAppear...",[232,2275,2276],{},"onAppear\u002FonDisappear\u002Ftask",[10,2278],{},[17,2280,2282,2283,2285,2286,2285,2288,2285,2290,2292],{"id":2281},"q32-swiftui-中-statestateobjectobservedobjectenvironmentobject-的区别","Q3.2: SwiftUI 中 ",[192,2284,1275],{},"、",[192,2287,1305],{},[192,2289,1295],{},[192,2291,1315],{}," 的区别？",[201,2294,2295],{},[204,2296,206],{},[311,2298,2300],{"className":313,"code":2299,"language":315,"meta":316,"style":316},"\u002F\u002F @State：View 私有的简单值类型状态\nstruct ToggleView: View {\n    @State private var isOn = false  \u002F\u002F View 拥有此状态\n    var body: some View {\n        Toggle(\"Switch\", isOn: $isOn)\n    }\n}\n\n\u002F\u002F @StateObject：View 拥有的引用类型状态（整个生命周期只创建一次）\nstruct ParentView: View {\n    @StateObject private var viewModel = MyViewModel()  \u002F\u002F 只创建一次\n    var body: some View {\n        ChildView(viewModel: viewModel)\n    }\n}\n\n\u002F\u002F @ObservedObject：外部传入的引用类型状态（不拥有，可能重建）\nstruct ChildView: View {\n    @ObservedObject var viewModel: MyViewModel  \u002F\u002F 由父传入\n    var body: some View {\n        Text(viewModel.title)\n    }\n}\n\n\u002F\u002F @EnvironmentObject：通过环境注入的共享状态\nstruct DeepChildView: View {\n    @EnvironmentObject var settings: AppSettings  \u002F\u002F 从祖先注入\n    var body: some View {\n        Text(settings.theme)\n    }\n}\n\n\u002F\u002F 注入方式\nContentView()\n    .environmentObject(AppSettings())\n",[192,2301,2302,2307,2312,2317,2321,2326,2330,2334,2338,2343,2348,2353,2357,2362,2366,2370,2374,2379,2384,2389,2393,2398,2402,2406,2410,2415,2420,2425,2429,2434,2438,2442,2446,2451,2456],{"__ignoreMap":316},[320,2303,2304],{"class":322,"line":323},[320,2305,2306],{},"\u002F\u002F @State：View 私有的简单值类型状态\n",[320,2308,2309],{"class":322,"line":329},[320,2310,2311],{},"struct ToggleView: View {\n",[320,2313,2314],{"class":322,"line":335},[320,2315,2316],{},"    @State private var isOn = false  \u002F\u002F View 拥有此状态\n",[320,2318,2319],{"class":322,"line":341},[320,2320,2140],{},[320,2322,2323],{"class":322,"line":347},[320,2324,2325],{},"        Toggle(\"Switch\", isOn: $isOn)\n",[320,2327,2328],{"class":322,"line":353},[320,2329,558],{},[320,2331,2332],{"class":322,"line":360},[320,2333,350],{},[320,2335,2336],{"class":322,"line":74},[320,2337,357],{"emptyLinePlaceholder":356},[320,2339,2340],{"class":322,"line":371},[320,2341,2342],{},"\u002F\u002F @StateObject：View 拥有的引用类型状态（整个生命周期只创建一次）\n",[320,2344,2345],{"class":322,"line":377},[320,2346,2347],{},"struct ParentView: View {\n",[320,2349,2350],{"class":322,"line":383},[320,2351,2352],{},"    @StateObject private var viewModel = MyViewModel()  \u002F\u002F 只创建一次\n",[320,2354,2355],{"class":322,"line":388},[320,2356,2140],{},[320,2358,2359],{"class":322,"line":394},[320,2360,2361],{},"        ChildView(viewModel: viewModel)\n",[320,2363,2364],{"class":322,"line":400},[320,2365,558],{},[320,2367,2368],{"class":322,"line":121},[320,2369,350],{},[320,2371,2372],{"class":322,"line":411},[320,2373,357],{"emptyLinePlaceholder":356},[320,2375,2376],{"class":322,"line":416},[320,2377,2378],{},"\u002F\u002F @ObservedObject：外部传入的引用类型状态（不拥有，可能重建）\n",[320,2380,2381],{"class":322,"line":421},[320,2382,2383],{},"struct ChildView: View {\n",[320,2385,2386],{"class":322,"line":427},[320,2387,2388],{},"    @ObservedObject var viewModel: MyViewModel  \u002F\u002F 由父传入\n",[320,2390,2391],{"class":322,"line":158},[320,2392,2140],{},[320,2394,2395],{"class":322,"line":438},[320,2396,2397],{},"        Text(viewModel.title)\n",[320,2399,2400],{"class":322,"line":615},[320,2401,558],{},[320,2403,2404],{"class":322,"line":621},[320,2405,350],{},[320,2407,2408],{"class":322,"line":627},[320,2409,357],{"emptyLinePlaceholder":356},[320,2411,2412],{"class":322,"line":935},[320,2413,2414],{},"\u002F\u002F @EnvironmentObject：通过环境注入的共享状态\n",[320,2416,2417],{"class":322,"line":941},[320,2418,2419],{},"struct DeepChildView: View {\n",[320,2421,2422],{"class":322,"line":947},[320,2423,2424],{},"    @EnvironmentObject var settings: AppSettings  \u002F\u002F 从祖先注入\n",[320,2426,2427],{"class":322,"line":953},[320,2428,2140],{},[320,2430,2431],{"class":322,"line":959},[320,2432,2433],{},"        Text(settings.theme)\n",[320,2435,2436],{"class":322,"line":965},[320,2437,558],{},[320,2439,2440],{"class":322,"line":970},[320,2441,350],{},[320,2443,2444],{"class":322,"line":975},[320,2445,357],{"emptyLinePlaceholder":356},[320,2447,2448],{"class":322,"line":980},[320,2449,2450],{},"\u002F\u002F 注入方式\n",[320,2452,2453],{"class":322,"line":986},[320,2454,2455],{},"ContentView()\n",[320,2457,2458],{"class":322,"line":992},[320,2459,2460],{},"    .environmentObject(AppSettings())\n",[201,2462,2463],{},[204,2464,2465],{},"核心区别总结：",[208,2467,2468,2484],{},[211,2469,2470],{},[214,2471,2472,2474,2477,2479,2482],{},[217,2473,1263],{},[217,2475,2476],{},"所有权",[217,2478,1797],{},[217,2480,2481],{},"创建时机",[217,2483,1835],{},[227,2485,2486,2504,2520,2537],{},[214,2487,2488,2492,2495,2498,2501],{},[232,2489,2490],{},[192,2491,1275],{},[232,2493,2494],{},"View 拥有",[232,2496,2497],{},"值类型",[232,2499,2500],{},"View 首次创建时",[232,2502,2503],{},"简单的局部状态",[214,2505,2506,2510,2512,2515,2517],{},[232,2507,2508],{},[192,2509,1305],{},[232,2511,2494],{},[232,2513,2514],{},"ObservableObject",[232,2516,2500],{},[232,2518,2519],{},"ViewModel、复杂对象",[214,2521,2522,2526,2529,2531,2534],{},[232,2523,2524],{},[192,2525,1295],{},[232,2527,2528],{},"外部传入",[232,2530,2514],{},[232,2532,2533],{},"外部创建",[232,2535,2536],{},"父传子的数据",[214,2538,2539,2543,2546,2548,2551],{},[232,2540,2541],{},[192,2542,1315],{},[232,2544,2545],{},"环境注入",[232,2547,2514],{},[232,2549,2550],{},"祖先提供",[232,2552,2553],{},"全局共享（主题\u002F认证）",[201,2555,2556],{},[204,2557,2558],{},"⚠️ 常见陷阱：",[311,2560,2562],{"className":313,"code":2561,"language":315,"meta":316,"style":316},"\u002F\u002F ❌ 用 @ObservedObject 创建对象 → 每次 body 重算都会重建\nstruct BuggyView: View {\n    @ObservedObject var vm = MyViewModel()  \u002F\u002F 每次父 View 重建都创建新实例！\n    var body: some View { Text(vm.data) }\n}\n\n\u002F\u002F ✅ 用 @StateObject → 只创建一次\nstruct CorrectView: View {\n    @StateObject var vm = MyViewModel()\n    var body: some View { Text(vm.data) }\n}\n",[192,2563,2564,2569,2574,2579,2584,2588,2592,2597,2602,2607,2611],{"__ignoreMap":316},[320,2565,2566],{"class":322,"line":323},[320,2567,2568],{},"\u002F\u002F ❌ 用 @ObservedObject 创建对象 → 每次 body 重算都会重建\n",[320,2570,2571],{"class":322,"line":329},[320,2572,2573],{},"struct BuggyView: View {\n",[320,2575,2576],{"class":322,"line":335},[320,2577,2578],{},"    @ObservedObject var vm = MyViewModel()  \u002F\u002F 每次父 View 重建都创建新实例！\n",[320,2580,2581],{"class":322,"line":341},[320,2582,2583],{},"    var body: some View { Text(vm.data) }\n",[320,2585,2586],{"class":322,"line":347},[320,2587,350],{},[320,2589,2590],{"class":322,"line":353},[320,2591,357],{"emptyLinePlaceholder":356},[320,2593,2594],{"class":322,"line":360},[320,2595,2596],{},"\u002F\u002F ✅ 用 @StateObject → 只创建一次\n",[320,2598,2599],{"class":322,"line":74},[320,2600,2601],{},"struct CorrectView: View {\n",[320,2603,2604],{"class":322,"line":371},[320,2605,2606],{},"    @StateObject var vm = MyViewModel()\n",[320,2608,2609],{"class":322,"line":377},[320,2610,2583],{},[320,2612,2613],{"class":322,"line":383},[320,2614,350],{},[10,2616],{},[17,2618,2620],{"id":2619},"q33-uikit-中-uitableview-uicollectionview-的性能优化要点","Q3.3: UIKit 中 UITableView \u002F UICollectionView 的性能优化要点？",[201,2622,2623],{},[204,2624,206],{},[311,2626,2628],{"className":313,"code":2627,"language":315,"meta":316,"style":316},"\u002F\u002F 1. Cell 复用（最基础）\nfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {\n    let cell = tableView.dequeueReusableCell(withIdentifier: \"Cell\", for: indexPath)\n    \u002F\u002F 配置 cell...\n    return cell\n}\n\n\u002F\u002F 2. 预估高度 + 自动计算\ntableView.estimatedRowHeight = 80\ntableView.rowHeight = UITableView.automaticDimension\n\n\u002F\u002F 3. 异步图片加载 + 取消\nfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {\n    let cell = tableView.dequeueReusableCell(withIdentifier: \"Cell\", for: indexPath) as! ImageCell\n    cell.task?.cancel()  \u002F\u002F 取消上一次请求\n    cell.task = Task {\n        let image = try await ImageLoader.load(url: items[indexPath.row].imageURL)\n        if !Task.isCancelled {\n            cell.photoView.image = image\n        }\n    }\n    return cell\n}\n\n\u002F\u002F 4. 预加载（Prefetching）\nextension VC: UITableViewDataSourcePrefetching {\n    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {\n        for indexPath in indexPaths {\n            ImageLoader.prefetch(url: items[indexPath.row].imageURL)\n        }\n    }\n\n    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {\n        for indexPath in indexPaths {\n            ImageLoader.cancelPrefetch(url: items[indexPath.row].imageURL)\n        }\n    }\n}\n\n\u002F\u002F 5. Diffable Data Source（iOS 13+，避免 reloadData）\nvar snapshot = NSDiffableDataSourceSnapshot\u003CSection, Item>()\nsnapshot.appendSections([.main])\nsnapshot.appendItems(items, toSection: .main)\ndataSource.apply(snapshot, animatingDifferences: true)\n\n\u002F\u002F 6. Compositional Layout（iOS 13+，高性能复杂布局）\nlet layout = UICollectionViewCompositionalLayout { sectionIndex, env in\n    let item = NSCollectionLayoutItem(layoutSize: ...)\n    let group = NSCollectionLayoutGroup.horizontal(layoutSize: ..., subitems: [item])\n    return NSCollectionLayoutSection(group: group)\n}\n",[192,2629,2630,2635,2640,2645,2650,2655,2659,2663,2668,2673,2678,2682,2687,2691,2696,2701,2706,2711,2716,2721,2725,2729,2733,2737,2741,2746,2751,2756,2761,2766,2770,2774,2778,2783,2787,2792,2796,2800,2804,2808,2813,2818,2823,2828,2833,2837,2842,2847,2852,2857,2862],{"__ignoreMap":316},[320,2631,2632],{"class":322,"line":323},[320,2633,2634],{},"\u002F\u002F 1. Cell 复用（最基础）\n",[320,2636,2637],{"class":322,"line":329},[320,2638,2639],{},"func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {\n",[320,2641,2642],{"class":322,"line":335},[320,2643,2644],{},"    let cell = tableView.dequeueReusableCell(withIdentifier: \"Cell\", for: indexPath)\n",[320,2646,2647],{"class":322,"line":341},[320,2648,2649],{},"    \u002F\u002F 配置 cell...\n",[320,2651,2652],{"class":322,"line":347},[320,2653,2654],{},"    return cell\n",[320,2656,2657],{"class":322,"line":353},[320,2658,350],{},[320,2660,2661],{"class":322,"line":360},[320,2662,357],{"emptyLinePlaceholder":356},[320,2664,2665],{"class":322,"line":74},[320,2666,2667],{},"\u002F\u002F 2. 预估高度 + 自动计算\n",[320,2669,2670],{"class":322,"line":371},[320,2671,2672],{},"tableView.estimatedRowHeight = 80\n",[320,2674,2675],{"class":322,"line":377},[320,2676,2677],{},"tableView.rowHeight = UITableView.automaticDimension\n",[320,2679,2680],{"class":322,"line":383},[320,2681,357],{"emptyLinePlaceholder":356},[320,2683,2684],{"class":322,"line":388},[320,2685,2686],{},"\u002F\u002F 3. 异步图片加载 + 取消\n",[320,2688,2689],{"class":322,"line":394},[320,2690,2639],{},[320,2692,2693],{"class":322,"line":400},[320,2694,2695],{},"    let cell = tableView.dequeueReusableCell(withIdentifier: \"Cell\", for: indexPath) as! ImageCell\n",[320,2697,2698],{"class":322,"line":121},[320,2699,2700],{},"    cell.task?.cancel()  \u002F\u002F 取消上一次请求\n",[320,2702,2703],{"class":322,"line":411},[320,2704,2705],{},"    cell.task = Task {\n",[320,2707,2708],{"class":322,"line":416},[320,2709,2710],{},"        let image = try await ImageLoader.load(url: items[indexPath.row].imageURL)\n",[320,2712,2713],{"class":322,"line":421},[320,2714,2715],{},"        if !Task.isCancelled {\n",[320,2717,2718],{"class":322,"line":427},[320,2719,2720],{},"            cell.photoView.image = image\n",[320,2722,2723],{"class":322,"line":158},[320,2724,1042],{},[320,2726,2727],{"class":322,"line":438},[320,2728,558],{},[320,2730,2731],{"class":322,"line":615},[320,2732,2654],{},[320,2734,2735],{"class":322,"line":621},[320,2736,350],{},[320,2738,2739],{"class":322,"line":627},[320,2740,357],{"emptyLinePlaceholder":356},[320,2742,2743],{"class":322,"line":935},[320,2744,2745],{},"\u002F\u002F 4. 预加载（Prefetching）\n",[320,2747,2748],{"class":322,"line":941},[320,2749,2750],{},"extension VC: UITableViewDataSourcePrefetching {\n",[320,2752,2753],{"class":322,"line":947},[320,2754,2755],{},"    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {\n",[320,2757,2758],{"class":322,"line":953},[320,2759,2760],{},"        for indexPath in indexPaths {\n",[320,2762,2763],{"class":322,"line":959},[320,2764,2765],{},"            ImageLoader.prefetch(url: items[indexPath.row].imageURL)\n",[320,2767,2768],{"class":322,"line":965},[320,2769,1042],{},[320,2771,2772],{"class":322,"line":970},[320,2773,558],{},[320,2775,2776],{"class":322,"line":975},[320,2777,357],{"emptyLinePlaceholder":356},[320,2779,2780],{"class":322,"line":980},[320,2781,2782],{},"    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {\n",[320,2784,2785],{"class":322,"line":986},[320,2786,2760],{},[320,2788,2789],{"class":322,"line":992},[320,2790,2791],{},"            ImageLoader.cancelPrefetch(url: items[indexPath.row].imageURL)\n",[320,2793,2794],{"class":322,"line":998},[320,2795,1042],{},[320,2797,2798],{"class":322,"line":1003},[320,2799,558],{},[320,2801,2802],{"class":322,"line":1009},[320,2803,350],{},[320,2805,2806],{"class":322,"line":1015},[320,2807,357],{"emptyLinePlaceholder":356},[320,2809,2810],{"class":322,"line":1021},[320,2811,2812],{},"\u002F\u002F 5. Diffable Data Source（iOS 13+，避免 reloadData）\n",[320,2814,2815],{"class":322,"line":1027},[320,2816,2817],{},"var snapshot = NSDiffableDataSourceSnapshot\u003CSection, Item>()\n",[320,2819,2820],{"class":322,"line":1033},[320,2821,2822],{},"snapshot.appendSections([.main])\n",[320,2824,2825],{"class":322,"line":1039},[320,2826,2827],{},"snapshot.appendItems(items, toSection: .main)\n",[320,2829,2830],{"class":322,"line":1045},[320,2831,2832],{},"dataSource.apply(snapshot, animatingDifferences: true)\n",[320,2834,2835],{"class":322,"line":1050},[320,2836,357],{"emptyLinePlaceholder":356},[320,2838,2839],{"class":322,"line":1055},[320,2840,2841],{},"\u002F\u002F 6. Compositional Layout（iOS 13+，高性能复杂布局）\n",[320,2843,2844],{"class":322,"line":1060},[320,2845,2846],{},"let layout = UICollectionViewCompositionalLayout { sectionIndex, env in\n",[320,2848,2849],{"class":322,"line":1066},[320,2850,2851],{},"    let item = NSCollectionLayoutItem(layoutSize: ...)\n",[320,2853,2854],{"class":322,"line":1072},[320,2855,2856],{},"    let group = NSCollectionLayoutGroup.horizontal(layoutSize: ..., subitems: [item])\n",[320,2858,2859],{"class":322,"line":1078},[320,2860,2861],{},"    return NSCollectionLayoutSection(group: group)\n",[320,2863,2864],{"class":322,"line":1084},[320,2865,350],{},[201,2867,2868],{},[204,2869,2870],{},"关键优化清单：",[448,2872,2873,2880,2891,2894],{},[25,2874,2875,2876,2879],{},"避免在 ",[192,2877,2878],{},"cellForRow"," 中做耗时操作",[25,2881,2882,2883,2886,2887,2890],{},"Cell 中避免离屏渲染（圆角用 ",[192,2884,2885],{},"cornerRadius"," + ",[192,2888,2889],{},"masksToBounds"," 会触发，可用贝塞尔路径裁剪）",[25,2892,2893],{},"图层扁平化（减少层级嵌套）",[25,2895,2896],{},"固定高度优于动态计算",[10,2898],{},[13,2900,2902],{"id":2901},"_4-并发编程","4. 并发编程",[17,2904,2906],{"id":2905},"q41-swift-concurrencyasyncawaitactor如何工作","Q4.1: Swift Concurrency（async\u002Fawait、Actor）如何工作？",[201,2908,2909],{},[204,2910,206],{},[201,2912,2913],{},[204,2914,2915],{},"async\u002Fawait（结构化并发）：",[311,2917,2919],{"className":313,"code":2918,"language":315,"meta":316,"style":316},"\u002F\u002F 定义异步函数\nfunc fetchUser(id: String) async throws -> User {\n    let (data, _) = try await URLSession.shared.data(from: url)\n    return try JSONDecoder().decode(User.self, from: data)\n}\n\n\u002F\u002F 调用\nTask {\n    do {\n        let user = try await fetchUser(id: \"123\")\n        \u002F\u002F 自动在合适的线程更新（如果是 @MainActor）\n    } catch {\n        print(error)\n    }\n}\n",[192,2920,2921,2926,2931,2936,2941,2945,2949,2954,2959,2964,2969,2974,2979,2984,2988],{"__ignoreMap":316},[320,2922,2923],{"class":322,"line":323},[320,2924,2925],{},"\u002F\u002F 定义异步函数\n",[320,2927,2928],{"class":322,"line":329},[320,2929,2930],{},"func fetchUser(id: String) async throws -> User {\n",[320,2932,2933],{"class":322,"line":335},[320,2934,2935],{},"    let (data, _) = try await URLSession.shared.data(from: url)\n",[320,2937,2938],{"class":322,"line":341},[320,2939,2940],{},"    return try JSONDecoder().decode(User.self, from: data)\n",[320,2942,2943],{"class":322,"line":347},[320,2944,350],{},[320,2946,2947],{"class":322,"line":353},[320,2948,357],{"emptyLinePlaceholder":356},[320,2950,2951],{"class":322,"line":360},[320,2952,2953],{},"\u002F\u002F 调用\n",[320,2955,2956],{"class":322,"line":74},[320,2957,2958],{},"Task {\n",[320,2960,2961],{"class":322,"line":371},[320,2962,2963],{},"    do {\n",[320,2965,2966],{"class":322,"line":377},[320,2967,2968],{},"        let user = try await fetchUser(id: \"123\")\n",[320,2970,2971],{"class":322,"line":383},[320,2972,2973],{},"        \u002F\u002F 自动在合适的线程更新（如果是 @MainActor）\n",[320,2975,2976],{"class":322,"line":388},[320,2977,2978],{},"    } catch {\n",[320,2980,2981],{"class":322,"line":394},[320,2982,2983],{},"        print(error)\n",[320,2985,2986],{"class":322,"line":400},[320,2987,558],{},[320,2989,2990],{"class":322,"line":121},[320,2991,350],{},[201,2993,2994],{},[204,2995,2996],{},"并行执行：",[311,2998,3000],{"className":313,"code":2999,"language":315,"meta":316,"style":316},"\u002F\u002F async let：并行启动多个任务\nfunc loadDashboard() async throws -> Dashboard {\n    async let user = fetchUser()\n    async let posts = fetchPosts()\n    async let notifications = fetchNotifications()\n\n    \u002F\u002F 三个请求并行执行，在这里等待全部完成\n    return try await Dashboard(\n        user: user,\n        posts: posts,\n        notifications: notifications\n    )\n}\n\n\u002F\u002F TaskGroup：动态数量的并行任务\nfunc fetchAllImages(urls: [URL]) async throws -> [UIImage] {\n    try await withThrowingTaskGroup(of: UIImage.self) { group in\n        for url in urls {\n            group.addTask { try await downloadImage(url) }\n        }\n        var images: [UIImage] = []\n        for try await image in group {\n            images.append(image)\n        }\n        return images\n    }\n}\n",[192,3001,3002,3007,3012,3017,3022,3027,3031,3036,3041,3046,3051,3056,3061,3065,3069,3074,3079,3084,3089,3094,3098,3103,3108,3113,3117,3122,3126],{"__ignoreMap":316},[320,3003,3004],{"class":322,"line":323},[320,3005,3006],{},"\u002F\u002F async let：并行启动多个任务\n",[320,3008,3009],{"class":322,"line":329},[320,3010,3011],{},"func loadDashboard() async throws -> Dashboard {\n",[320,3013,3014],{"class":322,"line":335},[320,3015,3016],{},"    async let user = fetchUser()\n",[320,3018,3019],{"class":322,"line":341},[320,3020,3021],{},"    async let posts = fetchPosts()\n",[320,3023,3024],{"class":322,"line":347},[320,3025,3026],{},"    async let notifications = fetchNotifications()\n",[320,3028,3029],{"class":322,"line":353},[320,3030,357],{"emptyLinePlaceholder":356},[320,3032,3033],{"class":322,"line":360},[320,3034,3035],{},"    \u002F\u002F 三个请求并行执行，在这里等待全部完成\n",[320,3037,3038],{"class":322,"line":74},[320,3039,3040],{},"    return try await Dashboard(\n",[320,3042,3043],{"class":322,"line":371},[320,3044,3045],{},"        user: user,\n",[320,3047,3048],{"class":322,"line":377},[320,3049,3050],{},"        posts: posts,\n",[320,3052,3053],{"class":322,"line":383},[320,3054,3055],{},"        notifications: notifications\n",[320,3057,3058],{"class":322,"line":388},[320,3059,3060],{},"    )\n",[320,3062,3063],{"class":322,"line":394},[320,3064,350],{},[320,3066,3067],{"class":322,"line":400},[320,3068,357],{"emptyLinePlaceholder":356},[320,3070,3071],{"class":322,"line":121},[320,3072,3073],{},"\u002F\u002F TaskGroup：动态数量的并行任务\n",[320,3075,3076],{"class":322,"line":411},[320,3077,3078],{},"func fetchAllImages(urls: [URL]) async throws -> [UIImage] {\n",[320,3080,3081],{"class":322,"line":416},[320,3082,3083],{},"    try await withThrowingTaskGroup(of: UIImage.self) { group in\n",[320,3085,3086],{"class":322,"line":421},[320,3087,3088],{},"        for url in urls {\n",[320,3090,3091],{"class":322,"line":427},[320,3092,3093],{},"            group.addTask { try await downloadImage(url) }\n",[320,3095,3096],{"class":322,"line":158},[320,3097,1042],{},[320,3099,3100],{"class":322,"line":438},[320,3101,3102],{},"        var images: [UIImage] = []\n",[320,3104,3105],{"class":322,"line":615},[320,3106,3107],{},"        for try await image in group {\n",[320,3109,3110],{"class":322,"line":621},[320,3111,3112],{},"            images.append(image)\n",[320,3114,3115],{"class":322,"line":627},[320,3116,1042],{},[320,3118,3119],{"class":322,"line":935},[320,3120,3121],{},"        return images\n",[320,3123,3124],{"class":322,"line":941},[320,3125,558],{},[320,3127,3128],{"class":322,"line":947},[320,3129,350],{},[201,3131,3132],{},[204,3133,3134],{},"Actor（数据竞争保护）：",[311,3136,3138],{"className":313,"code":3137,"language":315,"meta":316,"style":316},"\u002F\u002F Actor：保证内部状态串行访问，消除数据竞争\nactor BankAccount {\n    private var balance: Double = 0\n\n    func deposit(_ amount: Double) {\n        balance += amount\n    }\n\n    func withdraw(_ amount: Double) throws -> Double {\n        guard balance >= amount else { throw InsufficientFundsError() }\n        balance -= amount\n        return amount\n    }\n}\n\n\u002F\u002F 外部访问 Actor 属性\u002F方法必须 await\nlet account = BankAccount()\nawait account.deposit(100)\nlet money = try await account.withdraw(50)\n\n\u002F\u002F @MainActor：保证在主线程执行\n@MainActor\nclass ViewModel: ObservableObject {\n    @Published var items: [Item] = []\n\n    func load() async {\n        let data = await fetchFromNetwork()  \u002F\u002F 可能在其他线程\n        items = data  \u002F\u002F 自动回到主线程，因为类标记了 @MainActor\n    }\n}\n",[192,3139,3140,3145,3150,3155,3159,3164,3169,3173,3177,3182,3187,3192,3197,3201,3205,3209,3214,3219,3224,3229,3233,3238,3243,3248,3253,3257,3262,3267,3272,3276],{"__ignoreMap":316},[320,3141,3142],{"class":322,"line":323},[320,3143,3144],{},"\u002F\u002F Actor：保证内部状态串行访问，消除数据竞争\n",[320,3146,3147],{"class":322,"line":329},[320,3148,3149],{},"actor BankAccount {\n",[320,3151,3152],{"class":322,"line":335},[320,3153,3154],{},"    private var balance: Double = 0\n",[320,3156,3157],{"class":322,"line":341},[320,3158,357],{"emptyLinePlaceholder":356},[320,3160,3161],{"class":322,"line":347},[320,3162,3163],{},"    func deposit(_ amount: Double) {\n",[320,3165,3166],{"class":322,"line":353},[320,3167,3168],{},"        balance += amount\n",[320,3170,3171],{"class":322,"line":360},[320,3172,558],{},[320,3174,3175],{"class":322,"line":74},[320,3176,357],{"emptyLinePlaceholder":356},[320,3178,3179],{"class":322,"line":371},[320,3180,3181],{},"    func withdraw(_ amount: Double) throws -> Double {\n",[320,3183,3184],{"class":322,"line":377},[320,3185,3186],{},"        guard balance >= amount else { throw InsufficientFundsError() }\n",[320,3188,3189],{"class":322,"line":383},[320,3190,3191],{},"        balance -= amount\n",[320,3193,3194],{"class":322,"line":388},[320,3195,3196],{},"        return amount\n",[320,3198,3199],{"class":322,"line":394},[320,3200,558],{},[320,3202,3203],{"class":322,"line":400},[320,3204,350],{},[320,3206,3207],{"class":322,"line":121},[320,3208,357],{"emptyLinePlaceholder":356},[320,3210,3211],{"class":322,"line":411},[320,3212,3213],{},"\u002F\u002F 外部访问 Actor 属性\u002F方法必须 await\n",[320,3215,3216],{"class":322,"line":416},[320,3217,3218],{},"let account = BankAccount()\n",[320,3220,3221],{"class":322,"line":421},[320,3222,3223],{},"await account.deposit(100)\n",[320,3225,3226],{"class":322,"line":427},[320,3227,3228],{},"let money = try await account.withdraw(50)\n",[320,3230,3231],{"class":322,"line":158},[320,3232,357],{"emptyLinePlaceholder":356},[320,3234,3235],{"class":322,"line":438},[320,3236,3237],{},"\u002F\u002F @MainActor：保证在主线程执行\n",[320,3239,3240],{"class":322,"line":615},[320,3241,3242],{},"@MainActor\n",[320,3244,3245],{"class":322,"line":621},[320,3246,3247],{},"class ViewModel: ObservableObject {\n",[320,3249,3250],{"class":322,"line":627},[320,3251,3252],{},"    @Published var items: [Item] = []\n",[320,3254,3255],{"class":322,"line":935},[320,3256,357],{"emptyLinePlaceholder":356},[320,3258,3259],{"class":322,"line":941},[320,3260,3261],{},"    func load() async {\n",[320,3263,3264],{"class":322,"line":947},[320,3265,3266],{},"        let data = await fetchFromNetwork()  \u002F\u002F 可能在其他线程\n",[320,3268,3269],{"class":322,"line":953},[320,3270,3271],{},"        items = data  \u002F\u002F 自动回到主线程，因为类标记了 @MainActor\n",[320,3273,3274],{"class":322,"line":959},[320,3275,558],{},[320,3277,3278],{"class":322,"line":965},[320,3279,350],{},[201,3281,3282],{},[204,3283,3284],{},"Sendable：",[311,3286,3288],{"className":313,"code":3287,"language":315,"meta":316,"style":316},"\u002F\u002F Sendable 标记可以安全跨并发域传递的类型\nstruct UserData: Sendable {  \u002F\u002F 值类型通常自动 Sendable\n    let name: String\n    let age: Int\n}\n\n\u002F\u002F ⚠️ 类需要满足条件才能 Sendable\nfinal class Config: Sendable {  \u002F\u002F final + 所有属性不可变\n    let apiKey: String\n    init(apiKey: String) { self.apiKey = apiKey }\n}\n",[192,3289,3290,3295,3300,3305,3310,3314,3318,3323,3328,3333,3338],{"__ignoreMap":316},[320,3291,3292],{"class":322,"line":323},[320,3293,3294],{},"\u002F\u002F Sendable 标记可以安全跨并发域传递的类型\n",[320,3296,3297],{"class":322,"line":329},[320,3298,3299],{},"struct UserData: Sendable {  \u002F\u002F 值类型通常自动 Sendable\n",[320,3301,3302],{"class":322,"line":335},[320,3303,3304],{},"    let name: String\n",[320,3306,3307],{"class":322,"line":341},[320,3308,3309],{},"    let age: Int\n",[320,3311,3312],{"class":322,"line":347},[320,3313,350],{},[320,3315,3316],{"class":322,"line":353},[320,3317,357],{"emptyLinePlaceholder":356},[320,3319,3320],{"class":322,"line":360},[320,3321,3322],{},"\u002F\u002F ⚠️ 类需要满足条件才能 Sendable\n",[320,3324,3325],{"class":322,"line":74},[320,3326,3327],{},"final class Config: Sendable {  \u002F\u002F final + 所有属性不可变\n",[320,3329,3330],{"class":322,"line":371},[320,3331,3332],{},"    let apiKey: String\n",[320,3334,3335],{"class":322,"line":377},[320,3336,3337],{},"    init(apiKey: String) { self.apiKey = apiKey }\n",[320,3339,3340],{"class":322,"line":383},[320,3341,350],{},[10,3343],{},[17,3345,3347],{"id":3346},"q42-gcdgrand-central-dispatch的核心概念与-swift-concurrency-的对比","Q4.2: GCD（Grand Central Dispatch）的核心概念？与 Swift Concurrency 的对比？",[201,3349,3350],{},[204,3351,206],{},[311,3353,3355],{"className":313,"code":3354,"language":315,"meta":316,"style":316},"\u002F\u002F GCD：基于队列的并发模型\n\u002F\u002F 串行队列\nlet serial = DispatchQueue(label: \"com.app.serial\")\nserial.async { \u002F* 任务按顺序执行 *\u002F }\n\n\u002F\u002F 并发队列\nlet concurrent = DispatchQueue(label: \"com.app.concurrent\", attributes: .concurrent)\nconcurrent.async { \u002F* 任务并行执行 *\u002F }\n\n\u002F\u002F 主队列\nDispatchQueue.main.async { \u002F* UI 更新 *\u002F }\n\n\u002F\u002F 全局队列（按优先级）\nDispatchQueue.global(qos: .userInitiated).async { \u002F* 高优先级后台任务 *\u002F }\nDispatchQueue.global(qos: .utility).async { \u002F* 低优先级任务 *\u002F }\n\n\u002F\u002F DispatchGroup：等待一组任务完成\nlet group = DispatchGroup()\ngroup.enter()\nfetchA { group.leave() }\ngroup.enter()\nfetchB { group.leave() }\ngroup.notify(queue: .main) { \u002F* 全部完成 *\u002F }\n\n\u002F\u002F Semaphore：限制并发数\nlet semaphore = DispatchSemaphore(value: 3) \u002F\u002F 最多 3 个并发\nfor url in urls {\n    semaphore.wait()\n    queue.async {\n        download(url)\n        semaphore.signal()\n    }\n}\n",[192,3356,3357,3362,3367,3372,3377,3381,3386,3391,3396,3400,3405,3410,3414,3419,3424,3429,3433,3438,3443,3448,3453,3457,3462,3467,3471,3476,3481,3486,3491,3496,3501,3506,3510],{"__ignoreMap":316},[320,3358,3359],{"class":322,"line":323},[320,3360,3361],{},"\u002F\u002F GCD：基于队列的并发模型\n",[320,3363,3364],{"class":322,"line":329},[320,3365,3366],{},"\u002F\u002F 串行队列\n",[320,3368,3369],{"class":322,"line":335},[320,3370,3371],{},"let serial = DispatchQueue(label: \"com.app.serial\")\n",[320,3373,3374],{"class":322,"line":341},[320,3375,3376],{},"serial.async { \u002F* 任务按顺序执行 *\u002F }\n",[320,3378,3379],{"class":322,"line":347},[320,3380,357],{"emptyLinePlaceholder":356},[320,3382,3383],{"class":322,"line":353},[320,3384,3385],{},"\u002F\u002F 并发队列\n",[320,3387,3388],{"class":322,"line":360},[320,3389,3390],{},"let concurrent = DispatchQueue(label: \"com.app.concurrent\", attributes: .concurrent)\n",[320,3392,3393],{"class":322,"line":74},[320,3394,3395],{},"concurrent.async { \u002F* 任务并行执行 *\u002F }\n",[320,3397,3398],{"class":322,"line":371},[320,3399,357],{"emptyLinePlaceholder":356},[320,3401,3402],{"class":322,"line":377},[320,3403,3404],{},"\u002F\u002F 主队列\n",[320,3406,3407],{"class":322,"line":383},[320,3408,3409],{},"DispatchQueue.main.async { \u002F* UI 更新 *\u002F }\n",[320,3411,3412],{"class":322,"line":388},[320,3413,357],{"emptyLinePlaceholder":356},[320,3415,3416],{"class":322,"line":394},[320,3417,3418],{},"\u002F\u002F 全局队列（按优先级）\n",[320,3420,3421],{"class":322,"line":400},[320,3422,3423],{},"DispatchQueue.global(qos: .userInitiated).async { \u002F* 高优先级后台任务 *\u002F }\n",[320,3425,3426],{"class":322,"line":121},[320,3427,3428],{},"DispatchQueue.global(qos: .utility).async { \u002F* 低优先级任务 *\u002F }\n",[320,3430,3431],{"class":322,"line":411},[320,3432,357],{"emptyLinePlaceholder":356},[320,3434,3435],{"class":322,"line":416},[320,3436,3437],{},"\u002F\u002F DispatchGroup：等待一组任务完成\n",[320,3439,3440],{"class":322,"line":421},[320,3441,3442],{},"let group = DispatchGroup()\n",[320,3444,3445],{"class":322,"line":427},[320,3446,3447],{},"group.enter()\n",[320,3449,3450],{"class":322,"line":158},[320,3451,3452],{},"fetchA { group.leave() }\n",[320,3454,3455],{"class":322,"line":438},[320,3456,3447],{},[320,3458,3459],{"class":322,"line":615},[320,3460,3461],{},"fetchB { group.leave() }\n",[320,3463,3464],{"class":322,"line":621},[320,3465,3466],{},"group.notify(queue: .main) { \u002F* 全部完成 *\u002F }\n",[320,3468,3469],{"class":322,"line":627},[320,3470,357],{"emptyLinePlaceholder":356},[320,3472,3473],{"class":322,"line":935},[320,3474,3475],{},"\u002F\u002F Semaphore：限制并发数\n",[320,3477,3478],{"class":322,"line":941},[320,3479,3480],{},"let semaphore = DispatchSemaphore(value: 3) \u002F\u002F 最多 3 个并发\n",[320,3482,3483],{"class":322,"line":947},[320,3484,3485],{},"for url in urls {\n",[320,3487,3488],{"class":322,"line":953},[320,3489,3490],{},"    semaphore.wait()\n",[320,3492,3493],{"class":322,"line":959},[320,3494,3495],{},"    queue.async {\n",[320,3497,3498],{"class":322,"line":965},[320,3499,3500],{},"        download(url)\n",[320,3502,3503],{"class":322,"line":970},[320,3504,3505],{},"        semaphore.signal()\n",[320,3507,3508],{"class":322,"line":975},[320,3509,558],{},[320,3511,3512],{"class":322,"line":980},[320,3513,350],{},[201,3515,3516],{},[204,3517,3518],{},"GCD vs Swift Concurrency：",[208,3520,3521,3533],{},[211,3522,3523],{},[214,3524,3525,3527,3530],{},[217,3526],{},[217,3528,3529],{},"GCD",[217,3531,3532],{},"Swift Concurrency",[227,3534,3535,3546,3557,3568,3579,3589,3600],{},[214,3536,3537,3540,3543],{},[232,3538,3539],{},"语法",[232,3541,3542],{},"闭包回调",[232,3544,3545],{},"async\u002Fawait",[214,3547,3548,3551,3554],{},[232,3549,3550],{},"线程管理",[232,3552,3553],{},"开发者控制队列",[232,3555,3556],{},"运行时自动管理",[214,3558,3559,3562,3565],{},[232,3560,3561],{},"数据安全",[232,3563,3564],{},"需手动加锁\u002F串行队列",[232,3566,3567],{},"Actor 自动保护",[214,3569,3570,3573,3576],{},[232,3571,3572],{},"取消支持",[232,3574,3575],{},"需手动实现",[232,3577,3578],{},"Task.cancel() 内建",[214,3580,3581,3584,3586],{},[232,3582,3583],{},"编译检查",[232,3585,270],{},[232,3587,3588],{},"Sendable 编译检查",[214,3590,3591,3594,3597],{},[232,3592,3593],{},"回调地狱",[232,3595,3596],{},"容易出现",[232,3598,3599],{},"线性代码流",[214,3601,3602,3605,3608],{},[232,3603,3604],{},"优先级反转",[232,3606,3607],{},"可能发生",[232,3609,3610],{},"运行时自动处理",[10,3612],{},[13,3614,3616],{"id":3615},"_5-架构与设计模式ios","5. 架构与设计模式（iOS）",[17,3618,3620],{"id":3619},"q51-对比-mvcmvvmvipertca-架构","Q5.1: 对比 MVC、MVVM、VIPER、TCA 架构",[201,3622,3623],{},[204,3624,206],{},[201,3626,3627],{},[204,3628,3629],{},"MVC（Apple 传统）：",[311,3631,3634],{"className":3632,"code":3633,"language":2187},[2185],"View ← Controller → Model\n       (Massive)\n",[192,3635,3633],{"__ignoreMap":316},[201,3637,3638],{},"问题：Controller 承担过多职责（Massive View Controller）",[201,3640,3641],{},[204,3642,3643],{},"MVVM：",[311,3645,3647],{"className":313,"code":3646,"language":315,"meta":316,"style":316},"\u002F\u002F Model\nstruct User { let name: String; let email: String }\n\n\u002F\u002F ViewModel：不依赖 UIKit\nclass UserViewModel: ObservableObject {\n    @Published var displayName = \"\"\n    @Published var isLoading = false\n    private let repository: UserRepository\n\n    init(repository: UserRepository) {\n        self.repository = repository\n    }\n\n    func load() async {\n        isLoading = true\n        let user = try? await repository.getUser()\n        displayName = user?.name ?? \"Unknown\"\n        isLoading = false\n    }\n}\n\n\u002F\u002F View：只负责展示\nstruct UserView: View {\n    @StateObject var viewModel: UserViewModel\n\n    var body: some View {\n        if viewModel.isLoading {\n            ProgressView()\n        } else {\n            Text(viewModel.displayName)\n        }\n    }\n}\n",[192,3648,3649,3654,3659,3663,3668,3673,3678,3683,3688,3692,3697,3702,3706,3710,3714,3719,3724,3729,3734,3738,3742,3746,3751,3756,3761,3765,3769,3774,3779,3784,3789,3793,3797],{"__ignoreMap":316},[320,3650,3651],{"class":322,"line":323},[320,3652,3653],{},"\u002F\u002F Model\n",[320,3655,3656],{"class":322,"line":329},[320,3657,3658],{},"struct User { let name: String; let email: String }\n",[320,3660,3661],{"class":322,"line":335},[320,3662,357],{"emptyLinePlaceholder":356},[320,3664,3665],{"class":322,"line":341},[320,3666,3667],{},"\u002F\u002F ViewModel：不依赖 UIKit\n",[320,3669,3670],{"class":322,"line":347},[320,3671,3672],{},"class UserViewModel: ObservableObject {\n",[320,3674,3675],{"class":322,"line":353},[320,3676,3677],{},"    @Published var displayName = \"\"\n",[320,3679,3680],{"class":322,"line":360},[320,3681,3682],{},"    @Published var isLoading = false\n",[320,3684,3685],{"class":322,"line":74},[320,3686,3687],{},"    private let repository: UserRepository\n",[320,3689,3690],{"class":322,"line":371},[320,3691,357],{"emptyLinePlaceholder":356},[320,3693,3694],{"class":322,"line":377},[320,3695,3696],{},"    init(repository: UserRepository) {\n",[320,3698,3699],{"class":322,"line":383},[320,3700,3701],{},"        self.repository = repository\n",[320,3703,3704],{"class":322,"line":388},[320,3705,558],{},[320,3707,3708],{"class":322,"line":394},[320,3709,357],{"emptyLinePlaceholder":356},[320,3711,3712],{"class":322,"line":400},[320,3713,3261],{},[320,3715,3716],{"class":322,"line":121},[320,3717,3718],{},"        isLoading = true\n",[320,3720,3721],{"class":322,"line":411},[320,3722,3723],{},"        let user = try? await repository.getUser()\n",[320,3725,3726],{"class":322,"line":416},[320,3727,3728],{},"        displayName = user?.name ?? \"Unknown\"\n",[320,3730,3731],{"class":322,"line":421},[320,3732,3733],{},"        isLoading = false\n",[320,3735,3736],{"class":322,"line":427},[320,3737,558],{},[320,3739,3740],{"class":322,"line":158},[320,3741,350],{},[320,3743,3744],{"class":322,"line":438},[320,3745,357],{"emptyLinePlaceholder":356},[320,3747,3748],{"class":322,"line":615},[320,3749,3750],{},"\u002F\u002F View：只负责展示\n",[320,3752,3753],{"class":322,"line":621},[320,3754,3755],{},"struct UserView: View {\n",[320,3757,3758],{"class":322,"line":627},[320,3759,3760],{},"    @StateObject var viewModel: UserViewModel\n",[320,3762,3763],{"class":322,"line":935},[320,3764,357],{"emptyLinePlaceholder":356},[320,3766,3767],{"class":322,"line":941},[320,3768,2140],{},[320,3770,3771],{"class":322,"line":947},[320,3772,3773],{},"        if viewModel.isLoading {\n",[320,3775,3776],{"class":322,"line":953},[320,3777,3778],{},"            ProgressView()\n",[320,3780,3781],{"class":322,"line":959},[320,3782,3783],{},"        } else {\n",[320,3785,3786],{"class":322,"line":965},[320,3787,3788],{},"            Text(viewModel.displayName)\n",[320,3790,3791],{"class":322,"line":970},[320,3792,1042],{},[320,3794,3795],{"class":322,"line":975},[320,3796,558],{},[320,3798,3799],{"class":322,"line":980},[320,3800,350],{},[201,3802,3803],{},[204,3804,3805],{},"TCA（The Composable Architecture）：",[311,3807,3809],{"className":313,"code":3808,"language":315,"meta":316,"style":316},"\u002F\u002F 单向数据流，强调可测试性和可组合性\n@Reducer\nstruct Counter {\n    @ObservableState\n    struct State: Equatable {\n        var count = 0\n    }\n\n    enum Action {\n        case increment\n        case decrement\n        case fetchCompleted(Int)\n    }\n\n    var body: some ReducerOf\u003CSelf> {\n        Reduce { state, action in\n            switch action {\n            case .increment:\n                state.count += 1\n                return .none\n            case .decrement:\n                state.count -= 1\n                return .none\n            case .fetchCompleted(let value):\n                state.count = value\n                return .none\n            }\n        }\n    }\n}\n\n\u002F\u002F View\nstruct CounterView: View {\n    let store: StoreOf\u003CCounter>\n\n    var body: some View {\n        HStack {\n            Button(\"-\") { store.send(.decrement) }\n            Text(\"\\(store.count)\")\n            Button(\"+\") { store.send(.increment) }\n        }\n    }\n}\n",[192,3810,3811,3816,3821,3826,3831,3836,3841,3845,3849,3854,3859,3864,3869,3873,3877,3882,3887,3892,3897,3902,3907,3912,3917,3921,3926,3931,3935,3940,3944,3948,3952,3956,3961,3965,3970,3974,3978,3983,3988,3993,3998,4002,4006],{"__ignoreMap":316},[320,3812,3813],{"class":322,"line":323},[320,3814,3815],{},"\u002F\u002F 单向数据流，强调可测试性和可组合性\n",[320,3817,3818],{"class":322,"line":329},[320,3819,3820],{},"@Reducer\n",[320,3822,3823],{"class":322,"line":335},[320,3824,3825],{},"struct Counter {\n",[320,3827,3828],{"class":322,"line":341},[320,3829,3830],{},"    @ObservableState\n",[320,3832,3833],{"class":322,"line":347},[320,3834,3835],{},"    struct State: Equatable {\n",[320,3837,3838],{"class":322,"line":353},[320,3839,3840],{},"        var count = 0\n",[320,3842,3843],{"class":322,"line":360},[320,3844,558],{},[320,3846,3847],{"class":322,"line":74},[320,3848,357],{"emptyLinePlaceholder":356},[320,3850,3851],{"class":322,"line":371},[320,3852,3853],{},"    enum Action {\n",[320,3855,3856],{"class":322,"line":377},[320,3857,3858],{},"        case increment\n",[320,3860,3861],{"class":322,"line":383},[320,3862,3863],{},"        case decrement\n",[320,3865,3866],{"class":322,"line":388},[320,3867,3868],{},"        case fetchCompleted(Int)\n",[320,3870,3871],{"class":322,"line":394},[320,3872,558],{},[320,3874,3875],{"class":322,"line":400},[320,3876,357],{"emptyLinePlaceholder":356},[320,3878,3879],{"class":322,"line":121},[320,3880,3881],{},"    var body: some ReducerOf\u003CSelf> {\n",[320,3883,3884],{"class":322,"line":411},[320,3885,3886],{},"        Reduce { state, action in\n",[320,3888,3889],{"class":322,"line":416},[320,3890,3891],{},"            switch action {\n",[320,3893,3894],{"class":322,"line":421},[320,3895,3896],{},"            case .increment:\n",[320,3898,3899],{"class":322,"line":427},[320,3900,3901],{},"                state.count += 1\n",[320,3903,3904],{"class":322,"line":158},[320,3905,3906],{},"                return .none\n",[320,3908,3909],{"class":322,"line":438},[320,3910,3911],{},"            case .decrement:\n",[320,3913,3914],{"class":322,"line":615},[320,3915,3916],{},"                state.count -= 1\n",[320,3918,3919],{"class":322,"line":621},[320,3920,3906],{},[320,3922,3923],{"class":322,"line":627},[320,3924,3925],{},"            case .fetchCompleted(let value):\n",[320,3927,3928],{"class":322,"line":935},[320,3929,3930],{},"                state.count = value\n",[320,3932,3933],{"class":322,"line":941},[320,3934,3906],{},[320,3936,3937],{"class":322,"line":947},[320,3938,3939],{},"            }\n",[320,3941,3942],{"class":322,"line":953},[320,3943,1042],{},[320,3945,3946],{"class":322,"line":959},[320,3947,558],{},[320,3949,3950],{"class":322,"line":965},[320,3951,350],{},[320,3953,3954],{"class":322,"line":970},[320,3955,357],{"emptyLinePlaceholder":356},[320,3957,3958],{"class":322,"line":975},[320,3959,3960],{},"\u002F\u002F View\n",[320,3962,3963],{"class":322,"line":980},[320,3964,2126],{},[320,3966,3967],{"class":322,"line":986},[320,3968,3969],{},"    let store: StoreOf\u003CCounter>\n",[320,3971,3972],{"class":322,"line":992},[320,3973,357],{"emptyLinePlaceholder":356},[320,3975,3976],{"class":322,"line":998},[320,3977,2140],{},[320,3979,3980],{"class":322,"line":1003},[320,3981,3982],{},"        HStack {\n",[320,3984,3985],{"class":322,"line":1009},[320,3986,3987],{},"            Button(\"-\") { store.send(.decrement) }\n",[320,3989,3990],{"class":322,"line":1015},[320,3991,3992],{},"            Text(\"\\(store.count)\")\n",[320,3994,3995],{"class":322,"line":1021},[320,3996,3997],{},"            Button(\"+\") { store.send(.increment) }\n",[320,3999,4000],{"class":322,"line":1027},[320,4001,1042],{},[320,4003,4004],{"class":322,"line":1033},[320,4005,558],{},[320,4007,4008],{"class":322,"line":1039},[320,4009,350],{},[208,4011,4012,4031],{},[211,4013,4014],{},[214,4015,4016,4019,4022,4025,4028],{},[217,4017,4018],{},"架构",[217,4020,4021],{},"复杂度",[217,4023,4024],{},"可测试性",[217,4026,4027],{},"适用规模",[217,4029,4030],{},"学习成本",[227,4032,4033,4049,4065,4081],{},[214,4034,4035,4038,4041,4044,4047],{},[232,4036,4037],{},"MVC",[232,4039,4040],{},"低",[232,4042,4043],{},"差",[232,4045,4046],{},"小型\u002F原型",[232,4048,4040],{},[214,4050,4051,4054,4057,4060,4063],{},[232,4052,4053],{},"MVVM",[232,4055,4056],{},"中",[232,4058,4059],{},"好",[232,4061,4062],{},"中型",[232,4064,4040],{},[214,4066,4067,4070,4073,4076,4079],{},[232,4068,4069],{},"VIPER",[232,4071,4072],{},"高",[232,4074,4075],{},"很好",[232,4077,4078],{},"大型团队",[232,4080,4072],{},[214,4082,4083,4086,4089,4092,4095],{},[232,4084,4085],{},"TCA",[232,4087,4088],{},"中-高",[232,4090,4091],{},"极好",[232,4093,4094],{},"中大型",[232,4096,4088],{},[10,4098],{},[13,4100,4102],{"id":4101},"_6-性能优化与工具ios","6. 性能优化与工具（iOS）",[17,4104,4106],{"id":4105},"q61-如何使用-instruments-诊断-ios-性能问题","Q6.1: 如何使用 Instruments 诊断 iOS 性能问题？",[201,4108,4109],{},[204,4110,206],{},[208,4112,4113,4125],{},[211,4114,4115],{},[214,4116,4117,4120,4122],{},[217,4118,4119],{},"Instrument",[217,4121,1266],{},[217,4123,4124],{},"关注指标",[227,4126,4127,4138,4149,4160,4171,4182],{},[214,4128,4129,4132,4135],{},[232,4130,4131],{},"Time Profiler",[232,4133,4134],{},"CPU 耗时分析",[232,4136,4137],{},"主线程占用、热点函数",[214,4139,4140,4143,4146],{},[232,4141,4142],{},"Allocations",[232,4144,4145],{},"内存分配跟踪",[232,4147,4148],{},"总内存、分配频率、泄漏",[214,4150,4151,4154,4157],{},[232,4152,4153],{},"Leaks",[232,4155,4156],{},"内存泄漏检测",[232,4158,4159],{},"泄漏对象、循环引用",[214,4161,4162,4165,4168],{},[232,4163,4164],{},"Core Animation",[232,4166,4167],{},"渲染性能",[232,4169,4170],{},"FPS、离屏渲染、混合图层",[214,4172,4173,4176,4179],{},[232,4174,4175],{},"Network",[232,4177,4178],{},"网络请求分析",[232,4180,4181],{},"请求数、响应时间、数据量",[214,4183,4184,4187,4190],{},[232,4185,4186],{},"Energy Log",[232,4188,4189],{},"电池消耗分析",[232,4191,4192],{},"CPU\u002FGPU\u002F网络唤醒、后台活动",[201,4194,4195],{},[204,4196,4197],{},"常见性能问题与解决方案：",[311,4199,4201],{"className":313,"code":4200,"language":315,"meta":316,"style":316},"\u002F\u002F 1. 主线程阻塞\n\u002F\u002F ❌ 主线程做 IO\u002F计算\nlet data = try! Data(contentsOf: largeFileURL) \u002F\u002F 主线程阻塞\n\n\u002F\u002F ✅ 移到后台\nTask.detached {\n    let data = try await loadData(from: largeFileURL)\n    await MainActor.run { self.process(data) }\n}\n\n\u002F\u002F 2. 离屏渲染\n\u002F\u002F ❌ 触发离屏渲染\nview.layer.cornerRadius = 10\nview.layer.masksToBounds = true  \u002F\u002F 配合圆角 → 离屏渲染\nview.layer.shadowOffset = CGSize(width: 0, height: 2)  \u002F\u002F 阴影 → 离屏渲染\n\n\u002F\u002F ✅ 优化\nview.layer.cornerRadius = 10\nview.layer.cornerCurve = .continuous\n\u002F\u002F 阴影加 path 避免离屏渲染\nview.layer.shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10).cgPath\n\n\u002F\u002F 3. 图片解码\n\u002F\u002F ❌ 加载原图（4000x3000 解码消耗大量内存）\nimageView.image = UIImage(named: \"huge_photo\")\n\n\u002F\u002F ✅ 降采样（Downsampling）\nfunc downsample(imageAt url: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {\n    let options = [kCGImageSourceShouldCache: false] as CFDictionary\n    guard let source = CGImageSourceCreateWithURL(url as CFURL, options) else { return nil }\n\n    let maxDimension = max(pointSize.width, pointSize.height) * scale\n    let downsampleOptions = [\n        kCGImageSourceCreateThumbnailFromImageAlways: true,\n        kCGImageSourceShouldCacheImmediately: true,\n        kCGImageSourceThumbnailMaxPixelSize: maxDimension\n    ] as CFDictionary\n\n    guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }\n    return UIImage(cgImage: cgImage)\n}\n",[192,4202,4203,4208,4213,4218,4222,4227,4232,4237,4242,4246,4250,4255,4260,4265,4270,4275,4279,4284,4288,4293,4298,4303,4307,4312,4317,4322,4326,4331,4336,4341,4346,4350,4355,4360,4365,4370,4375,4380,4384,4389,4394],{"__ignoreMap":316},[320,4204,4205],{"class":322,"line":323},[320,4206,4207],{},"\u002F\u002F 1. 主线程阻塞\n",[320,4209,4210],{"class":322,"line":329},[320,4211,4212],{},"\u002F\u002F ❌ 主线程做 IO\u002F计算\n",[320,4214,4215],{"class":322,"line":335},[320,4216,4217],{},"let data = try! Data(contentsOf: largeFileURL) \u002F\u002F 主线程阻塞\n",[320,4219,4220],{"class":322,"line":341},[320,4221,357],{"emptyLinePlaceholder":356},[320,4223,4224],{"class":322,"line":347},[320,4225,4226],{},"\u002F\u002F ✅ 移到后台\n",[320,4228,4229],{"class":322,"line":353},[320,4230,4231],{},"Task.detached {\n",[320,4233,4234],{"class":322,"line":360},[320,4235,4236],{},"    let data = try await loadData(from: largeFileURL)\n",[320,4238,4239],{"class":322,"line":74},[320,4240,4241],{},"    await MainActor.run { self.process(data) }\n",[320,4243,4244],{"class":322,"line":371},[320,4245,350],{},[320,4247,4248],{"class":322,"line":377},[320,4249,357],{"emptyLinePlaceholder":356},[320,4251,4252],{"class":322,"line":383},[320,4253,4254],{},"\u002F\u002F 2. 离屏渲染\n",[320,4256,4257],{"class":322,"line":388},[320,4258,4259],{},"\u002F\u002F ❌ 触发离屏渲染\n",[320,4261,4262],{"class":322,"line":394},[320,4263,4264],{},"view.layer.cornerRadius = 10\n",[320,4266,4267],{"class":322,"line":400},[320,4268,4269],{},"view.layer.masksToBounds = true  \u002F\u002F 配合圆角 → 离屏渲染\n",[320,4271,4272],{"class":322,"line":121},[320,4273,4274],{},"view.layer.shadowOffset = CGSize(width: 0, height: 2)  \u002F\u002F 阴影 → 离屏渲染\n",[320,4276,4277],{"class":322,"line":411},[320,4278,357],{"emptyLinePlaceholder":356},[320,4280,4281],{"class":322,"line":416},[320,4282,4283],{},"\u002F\u002F ✅ 优化\n",[320,4285,4286],{"class":322,"line":421},[320,4287,4264],{},[320,4289,4290],{"class":322,"line":427},[320,4291,4292],{},"view.layer.cornerCurve = .continuous\n",[320,4294,4295],{"class":322,"line":158},[320,4296,4297],{},"\u002F\u002F 阴影加 path 避免离屏渲染\n",[320,4299,4300],{"class":322,"line":438},[320,4301,4302],{},"view.layer.shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10).cgPath\n",[320,4304,4305],{"class":322,"line":615},[320,4306,357],{"emptyLinePlaceholder":356},[320,4308,4309],{"class":322,"line":621},[320,4310,4311],{},"\u002F\u002F 3. 图片解码\n",[320,4313,4314],{"class":322,"line":627},[320,4315,4316],{},"\u002F\u002F ❌ 加载原图（4000x3000 解码消耗大量内存）\n",[320,4318,4319],{"class":322,"line":935},[320,4320,4321],{},"imageView.image = UIImage(named: \"huge_photo\")\n",[320,4323,4324],{"class":322,"line":941},[320,4325,357],{"emptyLinePlaceholder":356},[320,4327,4328],{"class":322,"line":947},[320,4329,4330],{},"\u002F\u002F ✅ 降采样（Downsampling）\n",[320,4332,4333],{"class":322,"line":953},[320,4334,4335],{},"func downsample(imageAt url: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {\n",[320,4337,4338],{"class":322,"line":959},[320,4339,4340],{},"    let options = [kCGImageSourceShouldCache: false] as CFDictionary\n",[320,4342,4343],{"class":322,"line":965},[320,4344,4345],{},"    guard let source = CGImageSourceCreateWithURL(url as CFURL, options) else { return nil }\n",[320,4347,4348],{"class":322,"line":970},[320,4349,357],{"emptyLinePlaceholder":356},[320,4351,4352],{"class":322,"line":975},[320,4353,4354],{},"    let maxDimension = max(pointSize.width, pointSize.height) * scale\n",[320,4356,4357],{"class":322,"line":980},[320,4358,4359],{},"    let downsampleOptions = [\n",[320,4361,4362],{"class":322,"line":986},[320,4363,4364],{},"        kCGImageSourceCreateThumbnailFromImageAlways: true,\n",[320,4366,4367],{"class":322,"line":992},[320,4368,4369],{},"        kCGImageSourceShouldCacheImmediately: true,\n",[320,4371,4372],{"class":322,"line":998},[320,4373,4374],{},"        kCGImageSourceThumbnailMaxPixelSize: maxDimension\n",[320,4376,4377],{"class":322,"line":1003},[320,4378,4379],{},"    ] as CFDictionary\n",[320,4381,4382],{"class":322,"line":1009},[320,4383,357],{"emptyLinePlaceholder":356},[320,4385,4386],{"class":322,"line":1015},[320,4387,4388],{},"    guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }\n",[320,4390,4391],{"class":322,"line":1021},[320,4392,4393],{},"    return UIImage(cgImage: cgImage)\n",[320,4395,4396],{"class":322,"line":1027},[320,4397,350],{},[10,4399],{},[13,4401,4403],{"id":4402},"_7-系统机制与框架","7. 系统机制与框架",[17,4405,4407],{"id":4406},"q71-ios-app-的启动流程是怎样的如何优化启动时间","Q7.1: iOS App 的启动流程是怎样的？如何优化启动时间？",[201,4409,4410],{},[204,4411,206],{},[311,4413,4416],{"className":4414,"code":4415,"language":2187},[2185],"┌─────────────────────────────────────────────┐\n│  Pre-main（系统阶段）                        │\n│  1. 加载 dyld（动态链接器）                   │\n│  2. 加载动态库（系统 + 第三方）                │\n│  3. Rebase & Bind（指针修正）                 │\n│  4. ObjC Runtime Setup（类注册、category 加载）│\n│  5. +load 方法                               │\n│  6. C++ 静态初始化器                          │\n├─────────────────────────────────────────────┤\n│  Post-main（应用阶段）                       │\n│  7. main() → UIApplicationMain()            │\n│  8. application:didFinishLaunchingWithOptions│\n│  9. 首帧渲染完成                              │\n└─────────────────────────────────────────────┘\n",[192,4417,4415],{"__ignoreMap":316},[201,4419,4420],{},[204,4421,4422],{},"优化策略：",[208,4424,4425,4435],{},[211,4426,4427],{},[214,4428,4429,4432],{},[217,4430,4431],{},"阶段",[217,4433,4434],{},"优化方法",[227,4436,4437,4453],{},[214,4438,4439,4442],{},[232,4440,4441],{},"Pre-main",[232,4443,4444,4445,4448,4449,4452],{},"减少动态库数量（合并或改用静态库）、移除不用的 ObjC 类、避免 ",[192,4446,4447],{},"+load","（改用 ",[192,4450,4451],{},"+initialize","）、减少 C++ 静态初始化",[214,4454,4455,4458],{},[232,4456,4457],{},"Post-main",[232,4459,4460,4463],{},[192,4461,4462],{},"didFinishLaunching"," 中延迟非必要初始化、首屏只加载必要数据、懒加载 SDK、使用 Launch Storyboard 提升感知速度",[311,4465,4467],{"className":313,"code":4466,"language":315,"meta":316,"style":316},"\u002F\u002F 测量启动时间\n\u002F\u002F Scheme → Environment Variables → 添加 DYLD_PRINT_STATISTICS = 1\n\n\u002F\u002F 延迟初始化示例\nfunc application(_ application: UIApplication,\n                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n    \u002F\u002F 只做必要初始化\n    setupCrashReporting()\n    setupCoreData()\n\n    \u002F\u002F 延迟非关键初始化\n    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {\n        self.setupAnalytics()\n        self.setupPushNotifications()\n    }\n\n    return true\n}\n",[192,4468,4469,4474,4479,4483,4488,4493,4498,4503,4508,4513,4517,4522,4527,4532,4537,4541,4545,4550],{"__ignoreMap":316},[320,4470,4471],{"class":322,"line":323},[320,4472,4473],{},"\u002F\u002F 测量启动时间\n",[320,4475,4476],{"class":322,"line":329},[320,4477,4478],{},"\u002F\u002F Scheme → Environment Variables → 添加 DYLD_PRINT_STATISTICS = 1\n",[320,4480,4481],{"class":322,"line":335},[320,4482,357],{"emptyLinePlaceholder":356},[320,4484,4485],{"class":322,"line":341},[320,4486,4487],{},"\u002F\u002F 延迟初始化示例\n",[320,4489,4490],{"class":322,"line":347},[320,4491,4492],{},"func application(_ application: UIApplication,\n",[320,4494,4495],{"class":322,"line":353},[320,4496,4497],{},"                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n",[320,4499,4500],{"class":322,"line":360},[320,4501,4502],{},"    \u002F\u002F 只做必要初始化\n",[320,4504,4505],{"class":322,"line":74},[320,4506,4507],{},"    setupCrashReporting()\n",[320,4509,4510],{"class":322,"line":371},[320,4511,4512],{},"    setupCoreData()\n",[320,4514,4515],{"class":322,"line":377},[320,4516,357],{"emptyLinePlaceholder":356},[320,4518,4519],{"class":322,"line":383},[320,4520,4521],{},"    \u002F\u002F 延迟非关键初始化\n",[320,4523,4524],{"class":322,"line":388},[320,4525,4526],{},"    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {\n",[320,4528,4529],{"class":322,"line":394},[320,4530,4531],{},"        self.setupAnalytics()\n",[320,4533,4534],{"class":322,"line":400},[320,4535,4536],{},"        self.setupPushNotifications()\n",[320,4538,4539],{"class":322,"line":121},[320,4540,558],{},[320,4542,4543],{"class":322,"line":411},[320,4544,357],{"emptyLinePlaceholder":356},[320,4546,4547],{"class":322,"line":416},[320,4548,4549],{},"    return true\n",[320,4551,4552],{"class":322,"line":421},[320,4553,350],{},[10,4555],{},[17,4557,4559],{"id":4558},"q72-什么是-runloop它和-ios-事件处理的关系","Q7.2: 什么是 RunLoop？它和 iOS 事件处理的关系？",[201,4561,4562],{},[204,4563,206],{},[201,4565,4566],{},"RunLoop 是线程上的事件处理循环，让线程在没有事件时休眠，有事件时唤醒处理：",[311,4568,4571],{"className":4569,"code":4570,"language":2187},[2185],"┌──────────────────────────────────────┐\n│              RunLoop 循环             │\n│                                      │\n│   1. 处理 Source0（非端口事件）         │\n│      ↓                               │\n│   2. 处理 Source1（端口\u002F系统事件）      │\n│      ↓                               │\n│   3. 处理 Timer                       │\n│      ↓                               │\n│   4. 处理 Observer 通知               │\n│      ↓                               │\n│   5. 处理 GCD dispatch 到主队列的 block│\n│      ↓                               │\n│   6. 没有事件 → 线程休眠              │\n│      ↓                               │\n│   7. 有事件 → 唤醒，回到 1            │\n└──────────────────────────────────────┘\n",[192,4572,4570],{"__ignoreMap":316},[201,4574,4575],{},[204,4576,4577],{},"RunLoop Mode：",[208,4579,4580,4589],{},[211,4581,4582],{},[214,4583,4584,4587],{},[217,4585,4586],{},"Mode",[217,4588,1266],{},[227,4590,4591,4601,4611],{},[214,4592,4593,4598],{},[232,4594,4595],{},[192,4596,4597],{},"default",[232,4599,4600],{},"默认模式，大部分事件",[214,4602,4603,4608],{},[232,4604,4605],{},[192,4606,4607],{},"tracking",[232,4609,4610],{},"ScrollView 滚动时的模式",[214,4612,4613,4618],{},[232,4614,4615],{},[192,4616,4617],{},"common",[232,4619,4620],{},"default + tracking 的集合",[311,4622,4624],{"className":313,"code":4623,"language":315,"meta":316,"style":316},"\u002F\u002F 常见问题：Timer 在滚动时停止\n\u002F\u002F ❌ 默认 mode，滚动时 RunLoop 切换到 tracking mode，Timer 不触发\nlet timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in\n    updateUI()\n}\n\n\u002F\u002F ✅ 添加到 common mode\nRunLoop.current.add(timer, forMode: .common)\n",[192,4625,4626,4631,4636,4641,4646,4650,4654,4659],{"__ignoreMap":316},[320,4627,4628],{"class":322,"line":323},[320,4629,4630],{},"\u002F\u002F 常见问题：Timer 在滚动时停止\n",[320,4632,4633],{"class":322,"line":329},[320,4634,4635],{},"\u002F\u002F ❌ 默认 mode，滚动时 RunLoop 切换到 tracking mode，Timer 不触发\n",[320,4637,4638],{"class":322,"line":335},[320,4639,4640],{},"let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in\n",[320,4642,4643],{"class":322,"line":341},[320,4644,4645],{},"    updateUI()\n",[320,4647,4648],{"class":322,"line":347},[320,4649,350],{},[320,4651,4652],{"class":322,"line":353},[320,4653,357],{"emptyLinePlaceholder":356},[320,4655,4656],{"class":322,"line":360},[320,4657,4658],{},"\u002F\u002F ✅ 添加到 common mode\n",[320,4660,4661],{"class":322,"line":74},[320,4662,4663],{},"RunLoop.current.add(timer, forMode: .common)\n",[201,4665,4666],{},[204,4667,4668],{},"RunLoop 的应用场景：",[448,4670,4671,4674,4677,4680],{},[25,4672,4673],{},"AutoreleasePool 的 drain 时机（每次 RunLoop 迭代结束）",[25,4675,4676],{},"手势识别",[25,4678,4679],{},"屏幕刷新（CADisplayLink）",[25,4681,4682],{},"延迟加载（在 RunLoop 空闲时加载图片）",[10,4684],{},[17,4686,4688],{"id":4687},"q73-解释-ios-中的-app-lifecycle前后台切换和-scene-生命周期","Q7.3: 解释 iOS 中的 App Lifecycle（前后台切换）和 Scene 生命周期",[201,4690,4691],{},[204,4692,206],{},[311,4694,4697],{"className":4695,"code":4696,"language":2187},[2185],"Not Running → Inactive → Active → (用户使用中)\n                 ↕\n              Background → Suspended → (系统可能终止)\n",[192,4698,4696],{"__ignoreMap":316},[311,4700,4702],{"className":313,"code":4701,"language":315,"meta":316,"style":316},"\u002F\u002F iOS 13+ Scene-based lifecycle\nclass SceneDelegate: UIResponder, UIWindowSceneDelegate {\n    func sceneDidBecomeActive(_ scene: UIScene) {\n        \u002F\u002F 前台活跃：恢复暂停的任务、刷新 UI\n    }\n\n    func sceneWillResignActive(_ scene: UIScene) {\n        \u002F\u002F 即将失去焦点：暂停游戏、保存草稿\n    }\n\n    func sceneDidEnterBackground(_ scene: UIScene) {\n        \u002F\u002F 进入后台：保存数据、释放共享资源\n        \u002F\u002F 约 5 秒执行时间\n    }\n\n    func sceneWillEnterForeground(_ scene: UIScene) {\n        \u002F\u002F 即将回到前台：撤销后台的更改\n    }\n}\n\n\u002F\u002F SwiftUI\n@main\nstruct MyApp: App {\n    @Environment(\\.scenePhase) var scenePhase\n\n    var body: some Scene {\n        WindowGroup { ContentView() }\n            .onChange(of: scenePhase) { _, phase in\n                switch phase {\n                case .active: break      \u002F\u002F 前台活跃\n                case .inactive: break    \u002F\u002F 临时不活跃\n                case .background: break  \u002F\u002F 后台\n                @unknown default: break\n                }\n            }\n    }\n}\n",[192,4703,4704,4709,4714,4719,4724,4728,4732,4737,4742,4746,4750,4755,4760,4765,4769,4773,4778,4783,4787,4791,4795,4800,4805,4810,4815,4819,4824,4829,4834,4839,4844,4849,4854,4859,4864,4868,4872],{"__ignoreMap":316},[320,4705,4706],{"class":322,"line":323},[320,4707,4708],{},"\u002F\u002F iOS 13+ Scene-based lifecycle\n",[320,4710,4711],{"class":322,"line":329},[320,4712,4713],{},"class SceneDelegate: UIResponder, UIWindowSceneDelegate {\n",[320,4715,4716],{"class":322,"line":335},[320,4717,4718],{},"    func sceneDidBecomeActive(_ scene: UIScene) {\n",[320,4720,4721],{"class":322,"line":341},[320,4722,4723],{},"        \u002F\u002F 前台活跃：恢复暂停的任务、刷新 UI\n",[320,4725,4726],{"class":322,"line":347},[320,4727,558],{},[320,4729,4730],{"class":322,"line":353},[320,4731,357],{"emptyLinePlaceholder":356},[320,4733,4734],{"class":322,"line":360},[320,4735,4736],{},"    func sceneWillResignActive(_ scene: UIScene) {\n",[320,4738,4739],{"class":322,"line":74},[320,4740,4741],{},"        \u002F\u002F 即将失去焦点：暂停游戏、保存草稿\n",[320,4743,4744],{"class":322,"line":371},[320,4745,558],{},[320,4747,4748],{"class":322,"line":377},[320,4749,357],{"emptyLinePlaceholder":356},[320,4751,4752],{"class":322,"line":383},[320,4753,4754],{},"    func sceneDidEnterBackground(_ scene: UIScene) {\n",[320,4756,4757],{"class":322,"line":388},[320,4758,4759],{},"        \u002F\u002F 进入后台：保存数据、释放共享资源\n",[320,4761,4762],{"class":322,"line":394},[320,4763,4764],{},"        \u002F\u002F 约 5 秒执行时间\n",[320,4766,4767],{"class":322,"line":400},[320,4768,558],{},[320,4770,4771],{"class":322,"line":121},[320,4772,357],{"emptyLinePlaceholder":356},[320,4774,4775],{"class":322,"line":411},[320,4776,4777],{},"    func sceneWillEnterForeground(_ scene: UIScene) {\n",[320,4779,4780],{"class":322,"line":416},[320,4781,4782],{},"        \u002F\u002F 即将回到前台：撤销后台的更改\n",[320,4784,4785],{"class":322,"line":421},[320,4786,558],{},[320,4788,4789],{"class":322,"line":427},[320,4790,350],{},[320,4792,4793],{"class":322,"line":158},[320,4794,357],{"emptyLinePlaceholder":356},[320,4796,4797],{"class":322,"line":438},[320,4798,4799],{},"\u002F\u002F SwiftUI\n",[320,4801,4802],{"class":322,"line":615},[320,4803,4804],{},"@main\n",[320,4806,4807],{"class":322,"line":621},[320,4808,4809],{},"struct MyApp: App {\n",[320,4811,4812],{"class":322,"line":627},[320,4813,4814],{},"    @Environment(\\.scenePhase) var scenePhase\n",[320,4816,4817],{"class":322,"line":935},[320,4818,357],{"emptyLinePlaceholder":356},[320,4820,4821],{"class":322,"line":941},[320,4822,4823],{},"    var body: some Scene {\n",[320,4825,4826],{"class":322,"line":947},[320,4827,4828],{},"        WindowGroup { ContentView() }\n",[320,4830,4831],{"class":322,"line":953},[320,4832,4833],{},"            .onChange(of: scenePhase) { _, phase in\n",[320,4835,4836],{"class":322,"line":959},[320,4837,4838],{},"                switch phase {\n",[320,4840,4841],{"class":322,"line":965},[320,4842,4843],{},"                case .active: break      \u002F\u002F 前台活跃\n",[320,4845,4846],{"class":322,"line":970},[320,4847,4848],{},"                case .inactive: break    \u002F\u002F 临时不活跃\n",[320,4850,4851],{"class":322,"line":975},[320,4852,4853],{},"                case .background: break  \u002F\u002F 后台\n",[320,4855,4856],{"class":322,"line":980},[320,4857,4858],{},"                @unknown default: break\n",[320,4860,4861],{"class":322,"line":986},[320,4862,4863],{},"                }\n",[320,4865,4866],{"class":322,"line":992},[320,4867,3939],{},[320,4869,4870],{"class":322,"line":998},[320,4871,558],{},[320,4873,4874],{"class":322,"line":1003},[320,4875,350],{},[201,4877,4878],{},[204,4879,4880],{},"后台任务：",[311,4882,4884],{"className":313,"code":4883,"language":315,"meta":316,"style":316},"\u002F\u002F Background Task：申请额外执行时间\nfunc sceneDidEnterBackground(_ scene: UIScene) {\n    let taskID = UIApplication.shared.beginBackgroundTask {\n        \u002F\u002F 时间到了的清理回调\n        UIApplication.shared.endBackgroundTask(taskID)\n    }\n\n    Task {\n        await saveData()\n        UIApplication.shared.endBackgroundTask(taskID)\n    }\n}\n\n\u002F\u002F BGTaskScheduler（iOS 13+）：后台定时任务\nBGTaskScheduler.shared.register(forTaskWithIdentifier: \"com.app.refresh\", using: nil) { task in\n    handleBackgroundRefresh(task: task as! BGAppRefreshTask)\n}\n",[192,4885,4886,4891,4896,4901,4906,4911,4915,4919,4924,4929,4933,4937,4941,4945,4950,4955,4960],{"__ignoreMap":316},[320,4887,4888],{"class":322,"line":323},[320,4889,4890],{},"\u002F\u002F Background Task：申请额外执行时间\n",[320,4892,4893],{"class":322,"line":329},[320,4894,4895],{},"func sceneDidEnterBackground(_ scene: UIScene) {\n",[320,4897,4898],{"class":322,"line":335},[320,4899,4900],{},"    let taskID = UIApplication.shared.beginBackgroundTask {\n",[320,4902,4903],{"class":322,"line":341},[320,4904,4905],{},"        \u002F\u002F 时间到了的清理回调\n",[320,4907,4908],{"class":322,"line":347},[320,4909,4910],{},"        UIApplication.shared.endBackgroundTask(taskID)\n",[320,4912,4913],{"class":322,"line":353},[320,4914,558],{},[320,4916,4917],{"class":322,"line":360},[320,4918,357],{"emptyLinePlaceholder":356},[320,4920,4921],{"class":322,"line":74},[320,4922,4923],{},"    Task {\n",[320,4925,4926],{"class":322,"line":371},[320,4927,4928],{},"        await saveData()\n",[320,4930,4931],{"class":322,"line":377},[320,4932,4910],{},[320,4934,4935],{"class":322,"line":383},[320,4936,558],{},[320,4938,4939],{"class":322,"line":388},[320,4940,350],{},[320,4942,4943],{"class":322,"line":394},[320,4944,357],{"emptyLinePlaceholder":356},[320,4946,4947],{"class":322,"line":400},[320,4948,4949],{},"\u002F\u002F BGTaskScheduler（iOS 13+）：后台定时任务\n",[320,4951,4952],{"class":322,"line":121},[320,4953,4954],{},"BGTaskScheduler.shared.register(forTaskWithIdentifier: \"com.app.refresh\", using: nil) { task in\n",[320,4956,4957],{"class":322,"line":411},[320,4958,4959],{},"    handleBackgroundRefresh(task: task as! BGAppRefreshTask)\n",[320,4961,4962],{"class":322,"line":416},[320,4963,350],{},[10,4965],{},[180,4967,71],{"id":4968},"第二部分android-开发-1",[13,4970,4972],{"id":4971},"_8-kotlin-语言基础","8. Kotlin 语言基础",[17,4974,4976,4977,2285,4980,2285,4983,2285,4986,4989],{"id":4975},"q81-kotlin-中的-data-classsealed-classobjectcompanion-object-各有什么用","Q8.1: Kotlin 中的 ",[192,4978,4979],{},"data class",[192,4981,4982],{},"sealed class",[192,4984,4985],{},"object",[192,4987,4988],{},"companion object"," 各有什么用？",[201,4991,4992],{},[204,4993,206],{},[311,4995,4999],{"className":4996,"code":4997,"language":4998,"meta":316,"style":316},"language-kotlin shiki shiki-themes github-light github-dark","\u002F\u002F 1. data class：自动生成 equals\u002FhashCode\u002FtoString\u002Fcopy\u002FcomponentN\ndata class User(val name: String, val age: Int)\n\nval user1 = User(\"Alice\", 25)\nval user2 = user1.copy(age = 26)       \u002F\u002F 浅拷贝并修改\nval (name, age) = user1                 \u002F\u002F 解构\nprintln(user1)                          \u002F\u002F User(name=Alice, age=25)\nprintln(user1 == User(\"Alice\", 25))     \u002F\u002F true（结构相等）\n\n\u002F\u002F 2. sealed class：受限的继承层次，编译器可穷举\nsealed class Result\u003Cout T> {\n    data class Success\u003CT>(val data: T) : Result\u003CT>()\n    data class Error(val message: String) : Result\u003CNothing>()\n    data object Loading : Result\u003CNothing>()\n}\n\nfun handle(result: Result\u003CString>) = when (result) {\n    is Result.Success -> println(result.data)\n    is Result.Error -> println(result.message)\n    Result.Loading -> println(\"Loading...\")\n    \u002F\u002F 不需要 else，编译器知道已穷举\n}\n\n\u002F\u002F 3. object：单例\nobject Database {\n    fun connect() { \u002F* ... *\u002F }\n}\nDatabase.connect()\n\n\u002F\u002F 4. companion object：类的静态成员（类似 Java static）\nclass MyFragment : Fragment() {\n    companion object {\n        private const val ARG_ID = \"id\"\n\n        fun newInstance(id: String) = MyFragment().apply {\n            arguments = bundleOf(ARG_ID to id)\n        }\n    }\n}\n","kotlin",[192,5000,5001,5006,5011,5015,5020,5025,5030,5035,5040,5044,5049,5054,5059,5064,5069,5073,5077,5082,5087,5092,5097,5102,5106,5110,5115,5120,5125,5129,5134,5138,5143,5148,5153,5158,5162,5167,5172,5176,5180],{"__ignoreMap":316},[320,5002,5003],{"class":322,"line":323},[320,5004,5005],{},"\u002F\u002F 1. data class：自动生成 equals\u002FhashCode\u002FtoString\u002Fcopy\u002FcomponentN\n",[320,5007,5008],{"class":322,"line":329},[320,5009,5010],{},"data class User(val name: String, val age: Int)\n",[320,5012,5013],{"class":322,"line":335},[320,5014,357],{"emptyLinePlaceholder":356},[320,5016,5017],{"class":322,"line":341},[320,5018,5019],{},"val user1 = User(\"Alice\", 25)\n",[320,5021,5022],{"class":322,"line":347},[320,5023,5024],{},"val user2 = user1.copy(age = 26)       \u002F\u002F 浅拷贝并修改\n",[320,5026,5027],{"class":322,"line":353},[320,5028,5029],{},"val (name, age) = user1                 \u002F\u002F 解构\n",[320,5031,5032],{"class":322,"line":360},[320,5033,5034],{},"println(user1)                          \u002F\u002F User(name=Alice, age=25)\n",[320,5036,5037],{"class":322,"line":74},[320,5038,5039],{},"println(user1 == User(\"Alice\", 25))     \u002F\u002F true（结构相等）\n",[320,5041,5042],{"class":322,"line":371},[320,5043,357],{"emptyLinePlaceholder":356},[320,5045,5046],{"class":322,"line":377},[320,5047,5048],{},"\u002F\u002F 2. sealed class：受限的继承层次，编译器可穷举\n",[320,5050,5051],{"class":322,"line":383},[320,5052,5053],{},"sealed class Result\u003Cout T> {\n",[320,5055,5056],{"class":322,"line":388},[320,5057,5058],{},"    data class Success\u003CT>(val data: T) : Result\u003CT>()\n",[320,5060,5061],{"class":322,"line":394},[320,5062,5063],{},"    data class Error(val message: String) : Result\u003CNothing>()\n",[320,5065,5066],{"class":322,"line":400},[320,5067,5068],{},"    data object Loading : Result\u003CNothing>()\n",[320,5070,5071],{"class":322,"line":121},[320,5072,350],{},[320,5074,5075],{"class":322,"line":411},[320,5076,357],{"emptyLinePlaceholder":356},[320,5078,5079],{"class":322,"line":416},[320,5080,5081],{},"fun handle(result: Result\u003CString>) = when (result) {\n",[320,5083,5084],{"class":322,"line":421},[320,5085,5086],{},"    is Result.Success -> println(result.data)\n",[320,5088,5089],{"class":322,"line":427},[320,5090,5091],{},"    is Result.Error -> println(result.message)\n",[320,5093,5094],{"class":322,"line":158},[320,5095,5096],{},"    Result.Loading -> println(\"Loading...\")\n",[320,5098,5099],{"class":322,"line":438},[320,5100,5101],{},"    \u002F\u002F 不需要 else，编译器知道已穷举\n",[320,5103,5104],{"class":322,"line":615},[320,5105,350],{},[320,5107,5108],{"class":322,"line":621},[320,5109,357],{"emptyLinePlaceholder":356},[320,5111,5112],{"class":322,"line":627},[320,5113,5114],{},"\u002F\u002F 3. object：单例\n",[320,5116,5117],{"class":322,"line":935},[320,5118,5119],{},"object Database {\n",[320,5121,5122],{"class":322,"line":941},[320,5123,5124],{},"    fun connect() { \u002F* ... *\u002F }\n",[320,5126,5127],{"class":322,"line":947},[320,5128,350],{},[320,5130,5131],{"class":322,"line":953},[320,5132,5133],{},"Database.connect()\n",[320,5135,5136],{"class":322,"line":959},[320,5137,357],{"emptyLinePlaceholder":356},[320,5139,5140],{"class":322,"line":965},[320,5141,5142],{},"\u002F\u002F 4. companion object：类的静态成员（类似 Java static）\n",[320,5144,5145],{"class":322,"line":970},[320,5146,5147],{},"class MyFragment : Fragment() {\n",[320,5149,5150],{"class":322,"line":975},[320,5151,5152],{},"    companion object {\n",[320,5154,5155],{"class":322,"line":980},[320,5156,5157],{},"        private const val ARG_ID = \"id\"\n",[320,5159,5160],{"class":322,"line":986},[320,5161,357],{"emptyLinePlaceholder":356},[320,5163,5164],{"class":322,"line":992},[320,5165,5166],{},"        fun newInstance(id: String) = MyFragment().apply {\n",[320,5168,5169],{"class":322,"line":998},[320,5170,5171],{},"            arguments = bundleOf(ARG_ID to id)\n",[320,5173,5174],{"class":322,"line":1003},[320,5175,1042],{},[320,5177,5178],{"class":322,"line":1009},[320,5179,558],{},[320,5181,5182],{"class":322,"line":1015},[320,5183,350],{},[10,5185],{},[17,5187,5189],{"id":5188},"q82-kotlin-的空安全和作用域函数letrunwithapplyalso","Q8.2: Kotlin 的空安全和作用域函数（let\u002Frun\u002Fwith\u002Fapply\u002Falso）",[201,5191,5192],{},[204,5193,206],{},[201,5195,5196],{},[204,5197,5198],{},"空安全：",[311,5200,5202],{"className":4996,"code":5201,"language":4998,"meta":316,"style":316},"var name: String = \"Hello\"   \u002F\u002F 非空\nvar name: String? = null     \u002F\u002F 可空\n\nname?.length                 \u002F\u002F 安全调用\nname ?: \"default\"            \u002F\u002F Elvis 操作符\nname!!.length                \u002F\u002F 强制解包（null 时抛 NPE）\n\n\u002F\u002F 智能转换（Smart Cast）\nfun process(value: Any) {\n    if (value is String) {\n        println(value.length)  \u002F\u002F 自动转换为 String，无需强转\n    }\n}\n",[192,5203,5204,5209,5214,5218,5223,5228,5233,5237,5242,5247,5252,5257,5261],{"__ignoreMap":316},[320,5205,5206],{"class":322,"line":323},[320,5207,5208],{},"var name: String = \"Hello\"   \u002F\u002F 非空\n",[320,5210,5211],{"class":322,"line":329},[320,5212,5213],{},"var name: String? = null     \u002F\u002F 可空\n",[320,5215,5216],{"class":322,"line":335},[320,5217,357],{"emptyLinePlaceholder":356},[320,5219,5220],{"class":322,"line":341},[320,5221,5222],{},"name?.length                 \u002F\u002F 安全调用\n",[320,5224,5225],{"class":322,"line":347},[320,5226,5227],{},"name ?: \"default\"            \u002F\u002F Elvis 操作符\n",[320,5229,5230],{"class":322,"line":353},[320,5231,5232],{},"name!!.length                \u002F\u002F 强制解包（null 时抛 NPE）\n",[320,5234,5235],{"class":322,"line":360},[320,5236,357],{"emptyLinePlaceholder":356},[320,5238,5239],{"class":322,"line":74},[320,5240,5241],{},"\u002F\u002F 智能转换（Smart Cast）\n",[320,5243,5244],{"class":322,"line":371},[320,5245,5246],{},"fun process(value: Any) {\n",[320,5248,5249],{"class":322,"line":377},[320,5250,5251],{},"    if (value is String) {\n",[320,5253,5254],{"class":322,"line":383},[320,5255,5256],{},"        println(value.length)  \u002F\u002F 自动转换为 String，无需强转\n",[320,5258,5259],{"class":322,"line":388},[320,5260,558],{},[320,5262,5263],{"class":322,"line":394},[320,5264,350],{},[201,5266,5267],{},[204,5268,5269],{},"作用域函数：",[208,5271,5272,5288],{},[211,5273,5274],{},[214,5275,5276,5279,5282,5285],{},[217,5277,5278],{},"函数",[217,5280,5281],{},"对象引用",[217,5283,5284],{},"返回值",[217,5286,5287],{},"典型场景",[227,5289,5290,5308,5325,5341,5358],{},[214,5291,5292,5297,5302,5305],{},[232,5293,5294],{},[192,5295,5296],{},"let",[232,5298,5299],{},[192,5300,5301],{},"it",[232,5303,5304],{},"Lambda 结果",[232,5306,5307],{},"空安全调用、变量转换",[214,5309,5310,5315,5320,5322],{},[232,5311,5312],{},[192,5313,5314],{},"run",[232,5316,5317],{},[192,5318,5319],{},"this",[232,5321,5304],{},[232,5323,5324],{},"对象配置 + 计算结果",[214,5326,5327,5332,5336,5338],{},[232,5328,5329],{},[192,5330,5331],{},"with",[232,5333,5334],{},[192,5335,5319],{},[232,5337,5304],{},[232,5339,5340],{},"对已有对象调用多个方法",[214,5342,5343,5348,5352,5355],{},[232,5344,5345],{},[192,5346,5347],{},"apply",[232,5349,5350],{},[192,5351,5319],{},[232,5353,5354],{},"对象本身",[232,5356,5357],{},"对象初始化\u002F配置",[214,5359,5360,5365,5369,5371],{},[232,5361,5362],{},[192,5363,5364],{},"also",[232,5366,5367],{},[192,5368,5301],{},[232,5370,5354],{},[232,5372,5373],{},"额外的副作用（日志等）",[311,5375,5377],{"className":4996,"code":5376,"language":4998,"meta":316,"style":316},"\u002F\u002F let：空安全 + 转换\nval length = name?.let {\n    println(\"Name is $it\")\n    it.length  \u002F\u002F 返回长度\n}\n\n\u002F\u002F apply：对象初始化\nval textView = TextView(context).apply {\n    text = \"Hello\"\n    textSize = 16f\n    setTextColor(Color.BLACK)\n}\n\n\u002F\u002F also：链式操作中的副作用\nfun getUser() = repository.findUser(id)\n    .also { log(\"Found user: $it\") }  \u002F\u002F 日志，不影响返回值\n\n\u002F\u002F run：配置 + 计算\nval result = service.run {\n    port = 8080\n    query(\"SELECT * FROM users\")  \u002F\u002F 返回查询结果\n}\n\n\u002F\u002F with：已有对象的多个操作\nwith(binding) {\n    titleText.text = item.title\n    subtitleText.text = item.subtitle\n    imageView.load(item.imageUrl)\n}\n",[192,5378,5379,5384,5389,5394,5399,5403,5407,5412,5417,5422,5427,5432,5436,5440,5445,5450,5455,5459,5464,5469,5474,5479,5483,5487,5492,5497,5502,5507,5512],{"__ignoreMap":316},[320,5380,5381],{"class":322,"line":323},[320,5382,5383],{},"\u002F\u002F let：空安全 + 转换\n",[320,5385,5386],{"class":322,"line":329},[320,5387,5388],{},"val length = name?.let {\n",[320,5390,5391],{"class":322,"line":335},[320,5392,5393],{},"    println(\"Name is $it\")\n",[320,5395,5396],{"class":322,"line":341},[320,5397,5398],{},"    it.length  \u002F\u002F 返回长度\n",[320,5400,5401],{"class":322,"line":347},[320,5402,350],{},[320,5404,5405],{"class":322,"line":353},[320,5406,357],{"emptyLinePlaceholder":356},[320,5408,5409],{"class":322,"line":360},[320,5410,5411],{},"\u002F\u002F apply：对象初始化\n",[320,5413,5414],{"class":322,"line":74},[320,5415,5416],{},"val textView = TextView(context).apply {\n",[320,5418,5419],{"class":322,"line":371},[320,5420,5421],{},"    text = \"Hello\"\n",[320,5423,5424],{"class":322,"line":377},[320,5425,5426],{},"    textSize = 16f\n",[320,5428,5429],{"class":322,"line":383},[320,5430,5431],{},"    setTextColor(Color.BLACK)\n",[320,5433,5434],{"class":322,"line":388},[320,5435,350],{},[320,5437,5438],{"class":322,"line":394},[320,5439,357],{"emptyLinePlaceholder":356},[320,5441,5442],{"class":322,"line":400},[320,5443,5444],{},"\u002F\u002F also：链式操作中的副作用\n",[320,5446,5447],{"class":322,"line":121},[320,5448,5449],{},"fun getUser() = repository.findUser(id)\n",[320,5451,5452],{"class":322,"line":411},[320,5453,5454],{},"    .also { log(\"Found user: $it\") }  \u002F\u002F 日志，不影响返回值\n",[320,5456,5457],{"class":322,"line":416},[320,5458,357],{"emptyLinePlaceholder":356},[320,5460,5461],{"class":322,"line":421},[320,5462,5463],{},"\u002F\u002F run：配置 + 计算\n",[320,5465,5466],{"class":322,"line":427},[320,5467,5468],{},"val result = service.run {\n",[320,5470,5471],{"class":322,"line":158},[320,5472,5473],{},"    port = 8080\n",[320,5475,5476],{"class":322,"line":438},[320,5477,5478],{},"    query(\"SELECT * FROM users\")  \u002F\u002F 返回查询结果\n",[320,5480,5481],{"class":322,"line":615},[320,5482,350],{},[320,5484,5485],{"class":322,"line":621},[320,5486,357],{"emptyLinePlaceholder":356},[320,5488,5489],{"class":322,"line":627},[320,5490,5491],{},"\u002F\u002F with：已有对象的多个操作\n",[320,5493,5494],{"class":322,"line":935},[320,5495,5496],{},"with(binding) {\n",[320,5498,5499],{"class":322,"line":941},[320,5500,5501],{},"    titleText.text = item.title\n",[320,5503,5504],{"class":322,"line":947},[320,5505,5506],{},"    subtitleText.text = item.subtitle\n",[320,5508,5509],{"class":322,"line":953},[320,5510,5511],{},"    imageView.load(item.imageUrl)\n",[320,5513,5514],{"class":322,"line":959},[320,5515,350],{},[10,5517],{},[17,5519,5521],{"id":5520},"q83-kotlin-中的委托delegation机制","Q8.3: Kotlin 中的委托（Delegation）机制",[201,5523,5524],{},[204,5525,206],{},[311,5527,5529],{"className":4996,"code":5528,"language":4998,"meta":316,"style":316},"\u002F\u002F 1. 类委托（by）：用组合替代继承\ninterface Printer {\n    fun print(message: String)\n}\n\nclass ConsolePrinter : Printer {\n    override fun print(message: String) = println(message)\n}\n\n\u002F\u002F LoggingPrinter 自动委托 Printer 接口的实现给 printer\nclass LoggingPrinter(private val printer: Printer) : Printer by printer {\n    override fun print(message: String) {\n        println(\"[LOG] About to print\")\n        printer.print(message)  \u002F\u002F 委托给真正的实现\n    }\n}\n\n\u002F\u002F 2. 属性委托\n\u002F\u002F lazy：首次访问时初始化\nval heavyObject: HeavyObject by lazy {\n    HeavyObject()  \u002F\u002F 线程安全的懒初始化\n}\n\n\u002F\u002F observable：属性变化监听\nvar name: String by Delegates.observable(\"initial\") { prop, old, new ->\n    println(\"$old → $new\")\n}\n\n\u002F\u002F 自定义委托\nclass SharedPreferencesDelegate(\n    private val prefs: SharedPreferences,\n    private val key: String,\n    private val default: String\n) : ReadWriteProperty\u003CAny, String> {\n\n    override fun getValue(thisRef: Any, property: KProperty\u003C*>): String {\n        return prefs.getString(key, default) ?: default\n    }\n\n    override fun setValue(thisRef: Any, property: KProperty\u003C*>, value: String) {\n        prefs.edit().putString(key, value).apply()\n    }\n}\n\n\u002F\u002F 使用\nclass Settings(prefs: SharedPreferences) {\n    var username: String by SharedPreferencesDelegate(prefs, \"username\", \"\")\n    var theme: String by SharedPreferencesDelegate(prefs, \"theme\", \"light\")\n}\n",[192,5530,5531,5536,5541,5546,5550,5554,5559,5564,5568,5572,5577,5582,5587,5592,5597,5601,5605,5609,5614,5619,5624,5629,5633,5637,5642,5647,5652,5656,5660,5665,5670,5675,5680,5685,5690,5694,5699,5704,5708,5712,5717,5722,5726,5730,5734,5738,5743,5748,5753],{"__ignoreMap":316},[320,5532,5533],{"class":322,"line":323},[320,5534,5535],{},"\u002F\u002F 1. 类委托（by）：用组合替代继承\n",[320,5537,5538],{"class":322,"line":329},[320,5539,5540],{},"interface Printer {\n",[320,5542,5543],{"class":322,"line":335},[320,5544,5545],{},"    fun print(message: String)\n",[320,5547,5548],{"class":322,"line":341},[320,5549,350],{},[320,5551,5552],{"class":322,"line":347},[320,5553,357],{"emptyLinePlaceholder":356},[320,5555,5556],{"class":322,"line":353},[320,5557,5558],{},"class ConsolePrinter : Printer {\n",[320,5560,5561],{"class":322,"line":360},[320,5562,5563],{},"    override fun print(message: String) = println(message)\n",[320,5565,5566],{"class":322,"line":74},[320,5567,350],{},[320,5569,5570],{"class":322,"line":371},[320,5571,357],{"emptyLinePlaceholder":356},[320,5573,5574],{"class":322,"line":377},[320,5575,5576],{},"\u002F\u002F LoggingPrinter 自动委托 Printer 接口的实现给 printer\n",[320,5578,5579],{"class":322,"line":383},[320,5580,5581],{},"class LoggingPrinter(private val printer: Printer) : Printer by printer {\n",[320,5583,5584],{"class":322,"line":388},[320,5585,5586],{},"    override fun print(message: String) {\n",[320,5588,5589],{"class":322,"line":394},[320,5590,5591],{},"        println(\"[LOG] About to print\")\n",[320,5593,5594],{"class":322,"line":400},[320,5595,5596],{},"        printer.print(message)  \u002F\u002F 委托给真正的实现\n",[320,5598,5599],{"class":322,"line":121},[320,5600,558],{},[320,5602,5603],{"class":322,"line":411},[320,5604,350],{},[320,5606,5607],{"class":322,"line":416},[320,5608,357],{"emptyLinePlaceholder":356},[320,5610,5611],{"class":322,"line":421},[320,5612,5613],{},"\u002F\u002F 2. 属性委托\n",[320,5615,5616],{"class":322,"line":427},[320,5617,5618],{},"\u002F\u002F lazy：首次访问时初始化\n",[320,5620,5621],{"class":322,"line":158},[320,5622,5623],{},"val heavyObject: HeavyObject by lazy {\n",[320,5625,5626],{"class":322,"line":438},[320,5627,5628],{},"    HeavyObject()  \u002F\u002F 线程安全的懒初始化\n",[320,5630,5631],{"class":322,"line":615},[320,5632,350],{},[320,5634,5635],{"class":322,"line":621},[320,5636,357],{"emptyLinePlaceholder":356},[320,5638,5639],{"class":322,"line":627},[320,5640,5641],{},"\u002F\u002F observable：属性变化监听\n",[320,5643,5644],{"class":322,"line":935},[320,5645,5646],{},"var name: String by Delegates.observable(\"initial\") { prop, old, new ->\n",[320,5648,5649],{"class":322,"line":941},[320,5650,5651],{},"    println(\"$old → $new\")\n",[320,5653,5654],{"class":322,"line":947},[320,5655,350],{},[320,5657,5658],{"class":322,"line":953},[320,5659,357],{"emptyLinePlaceholder":356},[320,5661,5662],{"class":322,"line":959},[320,5663,5664],{},"\u002F\u002F 自定义委托\n",[320,5666,5667],{"class":322,"line":965},[320,5668,5669],{},"class SharedPreferencesDelegate(\n",[320,5671,5672],{"class":322,"line":970},[320,5673,5674],{},"    private val prefs: SharedPreferences,\n",[320,5676,5677],{"class":322,"line":975},[320,5678,5679],{},"    private val key: String,\n",[320,5681,5682],{"class":322,"line":980},[320,5683,5684],{},"    private val default: String\n",[320,5686,5687],{"class":322,"line":986},[320,5688,5689],{},") : ReadWriteProperty\u003CAny, String> {\n",[320,5691,5692],{"class":322,"line":992},[320,5693,357],{"emptyLinePlaceholder":356},[320,5695,5696],{"class":322,"line":998},[320,5697,5698],{},"    override fun getValue(thisRef: Any, property: KProperty\u003C*>): String {\n",[320,5700,5701],{"class":322,"line":1003},[320,5702,5703],{},"        return prefs.getString(key, default) ?: default\n",[320,5705,5706],{"class":322,"line":1009},[320,5707,558],{},[320,5709,5710],{"class":322,"line":1015},[320,5711,357],{"emptyLinePlaceholder":356},[320,5713,5714],{"class":322,"line":1021},[320,5715,5716],{},"    override fun setValue(thisRef: Any, property: KProperty\u003C*>, value: String) {\n",[320,5718,5719],{"class":322,"line":1027},[320,5720,5721],{},"        prefs.edit().putString(key, value).apply()\n",[320,5723,5724],{"class":322,"line":1033},[320,5725,558],{},[320,5727,5728],{"class":322,"line":1039},[320,5729,350],{},[320,5731,5732],{"class":322,"line":1045},[320,5733,357],{"emptyLinePlaceholder":356},[320,5735,5736],{"class":322,"line":1050},[320,5737,1211],{},[320,5739,5740],{"class":322,"line":1055},[320,5741,5742],{},"class Settings(prefs: SharedPreferences) {\n",[320,5744,5745],{"class":322,"line":1060},[320,5746,5747],{},"    var username: String by SharedPreferencesDelegate(prefs, \"username\", \"\")\n",[320,5749,5750],{"class":322,"line":1066},[320,5751,5752],{},"    var theme: String by SharedPreferencesDelegate(prefs, \"theme\", \"light\")\n",[320,5754,5755],{"class":322,"line":1072},[320,5756,350],{},[10,5758],{},[17,5760,5762],{"id":5761},"q84-kotlin-的内联函数inlinereified-泛型和高阶函数","Q8.4: Kotlin 的内联函数（inline）、reified 泛型和高阶函数",[201,5764,5765],{},[204,5766,206],{},[311,5768,5770],{"className":4996,"code":5769,"language":4998,"meta":316,"style":316},"\u002F\u002F inline：编译时将函数体内联到调用处，消除 Lambda 对象开销\ninline fun measureTime(block: () -> Unit): Long {\n    val start = System.currentTimeMillis()\n    block()  \u002F\u002F 不会创建 Function 对象\n    return System.currentTimeMillis() - start\n}\n\n\u002F\u002F reified：在 inline 函数中获取泛型的实际类型（运行时可用）\n\u002F\u002F 普通泛型在运行时被擦除，reified 保留类型信息\ninline fun \u003Creified T> Gson.fromJson(json: String): T {\n    return fromJson(json, T::class.java)  \u002F\u002F 可以直接使用 T::class\n}\n\n\u002F\u002F 使用\nval user = gson.fromJson\u003CUser>(jsonString) \u002F\u002F 不需要传 User::class.java\n\n\u002F\u002F crossinline \u002F noinline\ninline fun execute(\n    crossinline setup: () -> Unit,   \u002F\u002F 不允许非局部返回\n    noinline callback: () -> Unit    \u002F\u002F 不内联此参数（可以存储引用）\n) {\n    setup()\n    post(callback)  \u002F\u002F noinline 的 lambda 可以作为对象传递\n}\n\n\u002F\u002F 非局部返回\ninline fun List\u003CInt>.forEachInlined(action: (Int) -> Unit) {\n    for (item in this) action(item)\n}\n\nfun test() {\n    listOf(1, 2, 3).forEachInlined {\n        if (it == 2) return  \u002F\u002F 直接从 test() 返回（非局部返回）\n    }\n}\n",[192,5771,5772,5777,5782,5787,5792,5797,5801,5805,5810,5815,5820,5825,5829,5833,5837,5842,5846,5851,5856,5861,5866,5871,5876,5881,5885,5889,5894,5899,5904,5908,5912,5917,5922,5927,5931],{"__ignoreMap":316},[320,5773,5774],{"class":322,"line":323},[320,5775,5776],{},"\u002F\u002F inline：编译时将函数体内联到调用处，消除 Lambda 对象开销\n",[320,5778,5779],{"class":322,"line":329},[320,5780,5781],{},"inline fun measureTime(block: () -> Unit): Long {\n",[320,5783,5784],{"class":322,"line":335},[320,5785,5786],{},"    val start = System.currentTimeMillis()\n",[320,5788,5789],{"class":322,"line":341},[320,5790,5791],{},"    block()  \u002F\u002F 不会创建 Function 对象\n",[320,5793,5794],{"class":322,"line":347},[320,5795,5796],{},"    return System.currentTimeMillis() - start\n",[320,5798,5799],{"class":322,"line":353},[320,5800,350],{},[320,5802,5803],{"class":322,"line":360},[320,5804,357],{"emptyLinePlaceholder":356},[320,5806,5807],{"class":322,"line":74},[320,5808,5809],{},"\u002F\u002F reified：在 inline 函数中获取泛型的实际类型（运行时可用）\n",[320,5811,5812],{"class":322,"line":371},[320,5813,5814],{},"\u002F\u002F 普通泛型在运行时被擦除，reified 保留类型信息\n",[320,5816,5817],{"class":322,"line":377},[320,5818,5819],{},"inline fun \u003Creified T> Gson.fromJson(json: String): T {\n",[320,5821,5822],{"class":322,"line":383},[320,5823,5824],{},"    return fromJson(json, T::class.java)  \u002F\u002F 可以直接使用 T::class\n",[320,5826,5827],{"class":322,"line":388},[320,5828,350],{},[320,5830,5831],{"class":322,"line":394},[320,5832,357],{"emptyLinePlaceholder":356},[320,5834,5835],{"class":322,"line":400},[320,5836,1211],{},[320,5838,5839],{"class":322,"line":121},[320,5840,5841],{},"val user = gson.fromJson\u003CUser>(jsonString) \u002F\u002F 不需要传 User::class.java\n",[320,5843,5844],{"class":322,"line":411},[320,5845,357],{"emptyLinePlaceholder":356},[320,5847,5848],{"class":322,"line":416},[320,5849,5850],{},"\u002F\u002F crossinline \u002F noinline\n",[320,5852,5853],{"class":322,"line":421},[320,5854,5855],{},"inline fun execute(\n",[320,5857,5858],{"class":322,"line":427},[320,5859,5860],{},"    crossinline setup: () -> Unit,   \u002F\u002F 不允许非局部返回\n",[320,5862,5863],{"class":322,"line":158},[320,5864,5865],{},"    noinline callback: () -> Unit    \u002F\u002F 不内联此参数（可以存储引用）\n",[320,5867,5868],{"class":322,"line":438},[320,5869,5870],{},") {\n",[320,5872,5873],{"class":322,"line":615},[320,5874,5875],{},"    setup()\n",[320,5877,5878],{"class":322,"line":621},[320,5879,5880],{},"    post(callback)  \u002F\u002F noinline 的 lambda 可以作为对象传递\n",[320,5882,5883],{"class":322,"line":627},[320,5884,350],{},[320,5886,5887],{"class":322,"line":935},[320,5888,357],{"emptyLinePlaceholder":356},[320,5890,5891],{"class":322,"line":941},[320,5892,5893],{},"\u002F\u002F 非局部返回\n",[320,5895,5896],{"class":322,"line":947},[320,5897,5898],{},"inline fun List\u003CInt>.forEachInlined(action: (Int) -> Unit) {\n",[320,5900,5901],{"class":322,"line":953},[320,5902,5903],{},"    for (item in this) action(item)\n",[320,5905,5906],{"class":322,"line":959},[320,5907,350],{},[320,5909,5910],{"class":322,"line":965},[320,5911,357],{"emptyLinePlaceholder":356},[320,5913,5914],{"class":322,"line":970},[320,5915,5916],{},"fun test() {\n",[320,5918,5919],{"class":322,"line":975},[320,5920,5921],{},"    listOf(1, 2, 3).forEachInlined {\n",[320,5923,5924],{"class":322,"line":980},[320,5925,5926],{},"        if (it == 2) return  \u002F\u002F 直接从 test() 返回（非局部返回）\n",[320,5928,5929],{"class":322,"line":986},[320,5930,558],{},[320,5932,5933],{"class":322,"line":992},[320,5934,350],{},[10,5936],{},[13,5938,5940],{"id":5939},"_9-四大组件与生命周期","9. 四大组件与生命周期",[17,5942,5944],{"id":5943},"q91-activity-的生命周期和配置变更处理","Q9.1: Activity 的生命周期和配置变更处理",[201,5946,5947],{},[204,5948,206],{},[311,5950,5953],{"className":5951,"code":5952,"language":2187},[2185],"                  onCreate()\n                      ↓\n                  onStart()    ←── onRestart()\n                      ↓               ↑\n                  onResume()          │\n                      ↓               │\n               [Activity Running]     │\n                      ↓               │\n                  onPause()           │\n                      ↓               │\n                  onStop()  ──────────┘\n                      ↓\n                  onDestroy()\n",[192,5954,5952],{"__ignoreMap":316},[201,5956,5957,5960],{},[204,5958,5959],{},"配置变更（如旋转屏幕）默认行为："," 销毁 Activity → 重建 Activity",[311,5962,5964],{"className":4996,"code":5963,"language":4998,"meta":316,"style":316},"\u002F\u002F 方案 1：ViewModel（推荐，数据在配置变更中存活）\nclass MyViewModel : ViewModel() {\n    private val _data = MutableLiveData\u003CList\u003CItem>>()\n    val data: LiveData\u003CList\u003CItem>> = _data\n\n    fun loadData() {\n        viewModelScope.launch {\n            _data.value = repository.fetch()\n        }\n    }\n}\n\n\u002F\u002F 方案 2：onSaveInstanceState（少量简单数据）\noverride fun onSaveInstanceState(outState: Bundle) {\n    super.onSaveInstanceState(outState)\n    outState.putInt(\"scroll_position\", scrollPosition)\n}\n\noverride fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    savedInstanceState?.getInt(\"scroll_position\")?.let { scrollTo(it) }\n}\n\n\u002F\u002F 方案 3：自行处理配置变更（不推荐，除非有特殊需求）\n\u002F\u002F AndroidManifest.xml\n\u002F\u002F android:configChanges=\"orientation|screenSize|keyboardHidden\"\noverride fun onConfigurationChanged(newConfig: Configuration) {\n    super.onConfigurationChanged(newConfig)\n    \u002F\u002F 手动调整布局\n}\n",[192,5965,5966,5971,5976,5981,5986,5990,5995,6000,6005,6009,6013,6017,6021,6026,6031,6036,6041,6045,6049,6054,6059,6064,6068,6072,6077,6082,6087,6092,6097,6102],{"__ignoreMap":316},[320,5967,5968],{"class":322,"line":323},[320,5969,5970],{},"\u002F\u002F 方案 1：ViewModel（推荐，数据在配置变更中存活）\n",[320,5972,5973],{"class":322,"line":329},[320,5974,5975],{},"class MyViewModel : ViewModel() {\n",[320,5977,5978],{"class":322,"line":335},[320,5979,5980],{},"    private val _data = MutableLiveData\u003CList\u003CItem>>()\n",[320,5982,5983],{"class":322,"line":341},[320,5984,5985],{},"    val data: LiveData\u003CList\u003CItem>> = _data\n",[320,5987,5988],{"class":322,"line":347},[320,5989,357],{"emptyLinePlaceholder":356},[320,5991,5992],{"class":322,"line":353},[320,5993,5994],{},"    fun loadData() {\n",[320,5996,5997],{"class":322,"line":360},[320,5998,5999],{},"        viewModelScope.launch {\n",[320,6001,6002],{"class":322,"line":74},[320,6003,6004],{},"            _data.value = repository.fetch()\n",[320,6006,6007],{"class":322,"line":371},[320,6008,1042],{},[320,6010,6011],{"class":322,"line":377},[320,6012,558],{},[320,6014,6015],{"class":322,"line":383},[320,6016,350],{},[320,6018,6019],{"class":322,"line":388},[320,6020,357],{"emptyLinePlaceholder":356},[320,6022,6023],{"class":322,"line":394},[320,6024,6025],{},"\u002F\u002F 方案 2：onSaveInstanceState（少量简单数据）\n",[320,6027,6028],{"class":322,"line":400},[320,6029,6030],{},"override fun onSaveInstanceState(outState: Bundle) {\n",[320,6032,6033],{"class":322,"line":121},[320,6034,6035],{},"    super.onSaveInstanceState(outState)\n",[320,6037,6038],{"class":322,"line":411},[320,6039,6040],{},"    outState.putInt(\"scroll_position\", scrollPosition)\n",[320,6042,6043],{"class":322,"line":416},[320,6044,350],{},[320,6046,6047],{"class":322,"line":421},[320,6048,357],{"emptyLinePlaceholder":356},[320,6050,6051],{"class":322,"line":427},[320,6052,6053],{},"override fun onCreate(savedInstanceState: Bundle?) {\n",[320,6055,6056],{"class":322,"line":158},[320,6057,6058],{},"    super.onCreate(savedInstanceState)\n",[320,6060,6061],{"class":322,"line":438},[320,6062,6063],{},"    savedInstanceState?.getInt(\"scroll_position\")?.let { scrollTo(it) }\n",[320,6065,6066],{"class":322,"line":615},[320,6067,350],{},[320,6069,6070],{"class":322,"line":621},[320,6071,357],{"emptyLinePlaceholder":356},[320,6073,6074],{"class":322,"line":627},[320,6075,6076],{},"\u002F\u002F 方案 3：自行处理配置变更（不推荐，除非有特殊需求）\n",[320,6078,6079],{"class":322,"line":935},[320,6080,6081],{},"\u002F\u002F AndroidManifest.xml\n",[320,6083,6084],{"class":322,"line":941},[320,6085,6086],{},"\u002F\u002F android:configChanges=\"orientation|screenSize|keyboardHidden\"\n",[320,6088,6089],{"class":322,"line":947},[320,6090,6091],{},"override fun onConfigurationChanged(newConfig: Configuration) {\n",[320,6093,6094],{"class":322,"line":953},[320,6095,6096],{},"    super.onConfigurationChanged(newConfig)\n",[320,6098,6099],{"class":322,"line":959},[320,6100,6101],{},"    \u002F\u002F 手动调整布局\n",[320,6103,6104],{"class":322,"line":965},[320,6105,350],{},[10,6107],{},[17,6109,6111],{"id":6110},"q92-fragment-的生命周期和常见问题","Q9.2: Fragment 的生命周期和常见问题",[201,6113,6114],{},[204,6115,206],{},[311,6117,6120],{"className":6118,"code":6119,"language":2187},[2185],"onAttach() → onCreate() → onCreateView() → onViewCreated()\n    → onStart() → onResume()\n\nonPause() → onStop() → onDestroyView() → onDestroy() → onDetach()\n",[192,6121,6119],{"__ignoreMap":316},[201,6123,6124],{},[204,6125,6126],{},"常见问题与最佳实践：",[311,6128,6130],{"className":4996,"code":6129,"language":4998,"meta":316,"style":316},"class MyFragment : Fragment(R.layout.fragment_my) {\n\n    \u002F\u002F ✅ 使用 viewLifecycleOwner（而非 this）观察 LiveData\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        \u002F\u002F ❌ 使用 this 可能导致多次观察（Fragment 可能多次 onCreateView）\n        \u002F\u002F viewModel.data.observe(this) { ... }\n\n        \u002F\u002F ✅ viewLifecycleOwner 跟随 View 生命周期\n        viewModel.data.observe(viewLifecycleOwner) { data ->\n            adapter.submitList(data)\n        }\n    }\n\n    \u002F\u002F ViewBinding 的正确用法\n    private var _binding: FragmentMyBinding? = null\n    private val binding get() = _binding!!\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        _binding = FragmentMyBinding.inflate(inflater, container, false)\n        return binding.root\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding = null  \u002F\u002F 避免内存泄漏\n    }\n\n    \u002F\u002F Fragment 之间通信：使用 ViewModel 或 FragmentResult API\n    \u002F\u002F ❌ 直接引用其他 Fragment\n    \u002F\u002F ✅ 共享 ViewModel\n    private val sharedViewModel: SharedViewModel by activityViewModels()\n\n    \u002F\u002F ✅ FragmentResult API\n    setFragmentResult(\"requestKey\", bundleOf(\"key\" to value))\n    \u002F\u002F 接收方\n    setFragmentResultListener(\"requestKey\") { _, bundle ->\n        val value = bundle.getString(\"key\")\n    }\n}\n",[192,6131,6132,6137,6141,6146,6151,6156,6160,6165,6170,6174,6179,6184,6189,6193,6197,6201,6206,6211,6216,6220,6225,6230,6235,6239,6243,6248,6253,6258,6262,6266,6271,6276,6281,6286,6290,6295,6300,6305,6310,6315,6319],{"__ignoreMap":316},[320,6133,6134],{"class":322,"line":323},[320,6135,6136],{},"class MyFragment : Fragment(R.layout.fragment_my) {\n",[320,6138,6139],{"class":322,"line":329},[320,6140,357],{"emptyLinePlaceholder":356},[320,6142,6143],{"class":322,"line":335},[320,6144,6145],{},"    \u002F\u002F ✅ 使用 viewLifecycleOwner（而非 this）观察 LiveData\n",[320,6147,6148],{"class":322,"line":341},[320,6149,6150],{},"    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n",[320,6152,6153],{"class":322,"line":347},[320,6154,6155],{},"        super.onViewCreated(view, savedInstanceState)\n",[320,6157,6158],{"class":322,"line":353},[320,6159,357],{"emptyLinePlaceholder":356},[320,6161,6162],{"class":322,"line":360},[320,6163,6164],{},"        \u002F\u002F ❌ 使用 this 可能导致多次观察（Fragment 可能多次 onCreateView）\n",[320,6166,6167],{"class":322,"line":74},[320,6168,6169],{},"        \u002F\u002F viewModel.data.observe(this) { ... }\n",[320,6171,6172],{"class":322,"line":371},[320,6173,357],{"emptyLinePlaceholder":356},[320,6175,6176],{"class":322,"line":377},[320,6177,6178],{},"        \u002F\u002F ✅ viewLifecycleOwner 跟随 View 生命周期\n",[320,6180,6181],{"class":322,"line":383},[320,6182,6183],{},"        viewModel.data.observe(viewLifecycleOwner) { data ->\n",[320,6185,6186],{"class":322,"line":388},[320,6187,6188],{},"            adapter.submitList(data)\n",[320,6190,6191],{"class":322,"line":394},[320,6192,1042],{},[320,6194,6195],{"class":322,"line":400},[320,6196,558],{},[320,6198,6199],{"class":322,"line":121},[320,6200,357],{"emptyLinePlaceholder":356},[320,6202,6203],{"class":322,"line":411},[320,6204,6205],{},"    \u002F\u002F ViewBinding 的正确用法\n",[320,6207,6208],{"class":322,"line":416},[320,6209,6210],{},"    private var _binding: FragmentMyBinding? = null\n",[320,6212,6213],{"class":322,"line":421},[320,6214,6215],{},"    private val binding get() = _binding!!\n",[320,6217,6218],{"class":322,"line":427},[320,6219,357],{"emptyLinePlaceholder":356},[320,6221,6222],{"class":322,"line":158},[320,6223,6224],{},"    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n",[320,6226,6227],{"class":322,"line":438},[320,6228,6229],{},"        _binding = FragmentMyBinding.inflate(inflater, container, false)\n",[320,6231,6232],{"class":322,"line":615},[320,6233,6234],{},"        return binding.root\n",[320,6236,6237],{"class":322,"line":621},[320,6238,558],{},[320,6240,6241],{"class":322,"line":627},[320,6242,357],{"emptyLinePlaceholder":356},[320,6244,6245],{"class":322,"line":935},[320,6246,6247],{},"    override fun onDestroyView() {\n",[320,6249,6250],{"class":322,"line":941},[320,6251,6252],{},"        super.onDestroyView()\n",[320,6254,6255],{"class":322,"line":947},[320,6256,6257],{},"        _binding = null  \u002F\u002F 避免内存泄漏\n",[320,6259,6260],{"class":322,"line":953},[320,6261,558],{},[320,6263,6264],{"class":322,"line":959},[320,6265,357],{"emptyLinePlaceholder":356},[320,6267,6268],{"class":322,"line":965},[320,6269,6270],{},"    \u002F\u002F Fragment 之间通信：使用 ViewModel 或 FragmentResult API\n",[320,6272,6273],{"class":322,"line":970},[320,6274,6275],{},"    \u002F\u002F ❌ 直接引用其他 Fragment\n",[320,6277,6278],{"class":322,"line":975},[320,6279,6280],{},"    \u002F\u002F ✅ 共享 ViewModel\n",[320,6282,6283],{"class":322,"line":980},[320,6284,6285],{},"    private val sharedViewModel: SharedViewModel by activityViewModels()\n",[320,6287,6288],{"class":322,"line":986},[320,6289,357],{"emptyLinePlaceholder":356},[320,6291,6292],{"class":322,"line":992},[320,6293,6294],{},"    \u002F\u002F ✅ FragmentResult API\n",[320,6296,6297],{"class":322,"line":998},[320,6298,6299],{},"    setFragmentResult(\"requestKey\", bundleOf(\"key\" to value))\n",[320,6301,6302],{"class":322,"line":1003},[320,6303,6304],{},"    \u002F\u002F 接收方\n",[320,6306,6307],{"class":322,"line":1009},[320,6308,6309],{},"    setFragmentResultListener(\"requestKey\") { _, bundle ->\n",[320,6311,6312],{"class":322,"line":1015},[320,6313,6314],{},"        val value = bundle.getString(\"key\")\n",[320,6316,6317],{"class":322,"line":1021},[320,6318,558],{},[320,6320,6321],{"class":322,"line":1027},[320,6322,350],{},[10,6324],{},[17,6326,6328],{"id":6327},"q93-servicebroadcastreceivercontentprovider-的使用场景","Q9.3: Service、BroadcastReceiver、ContentProvider 的使用场景",[201,6330,6331],{},[204,6332,206],{},[201,6334,6335],{},[204,6336,6337],{},"Service：",[311,6339,6341],{"className":4996,"code":6340,"language":4998,"meta":316,"style":316},"\u002F\u002F Foreground Service（长时间后台任务，如音乐播放、GPS 导航）\nclass MusicService : Service() {\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        val notification = createNotification()\n        startForeground(NOTIFICATION_ID, notification) \u002F\u002F 必须显示通知\n        playMusic()\n        return START_STICKY \u002F\u002F 被杀后自动重启\n    }\n\n    override fun onBind(intent: Intent?): IBinder? = null\n}\n\n\u002F\u002F WorkManager（推荐替代后台 Service）\nval request = OneTimeWorkRequestBuilder\u003CUploadWorker>()\n    .setConstraints(Constraints.Builder()\n        .setRequiredNetworkType(NetworkType.CONNECTED)\n        .setRequiresBatteryNotLow(true)\n        .build())\n    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)\n    .build()\n\nWorkManager.getInstance(context).enqueue(request)\n",[192,6342,6343,6348,6353,6358,6363,6368,6373,6378,6382,6386,6391,6395,6399,6404,6409,6414,6419,6424,6429,6434,6439,6443],{"__ignoreMap":316},[320,6344,6345],{"class":322,"line":323},[320,6346,6347],{},"\u002F\u002F Foreground Service（长时间后台任务，如音乐播放、GPS 导航）\n",[320,6349,6350],{"class":322,"line":329},[320,6351,6352],{},"class MusicService : Service() {\n",[320,6354,6355],{"class":322,"line":335},[320,6356,6357],{},"    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n",[320,6359,6360],{"class":322,"line":341},[320,6361,6362],{},"        val notification = createNotification()\n",[320,6364,6365],{"class":322,"line":347},[320,6366,6367],{},"        startForeground(NOTIFICATION_ID, notification) \u002F\u002F 必须显示通知\n",[320,6369,6370],{"class":322,"line":353},[320,6371,6372],{},"        playMusic()\n",[320,6374,6375],{"class":322,"line":360},[320,6376,6377],{},"        return START_STICKY \u002F\u002F 被杀后自动重启\n",[320,6379,6380],{"class":322,"line":74},[320,6381,558],{},[320,6383,6384],{"class":322,"line":371},[320,6385,357],{"emptyLinePlaceholder":356},[320,6387,6388],{"class":322,"line":377},[320,6389,6390],{},"    override fun onBind(intent: Intent?): IBinder? = null\n",[320,6392,6393],{"class":322,"line":383},[320,6394,350],{},[320,6396,6397],{"class":322,"line":388},[320,6398,357],{"emptyLinePlaceholder":356},[320,6400,6401],{"class":322,"line":394},[320,6402,6403],{},"\u002F\u002F WorkManager（推荐替代后台 Service）\n",[320,6405,6406],{"class":322,"line":400},[320,6407,6408],{},"val request = OneTimeWorkRequestBuilder\u003CUploadWorker>()\n",[320,6410,6411],{"class":322,"line":121},[320,6412,6413],{},"    .setConstraints(Constraints.Builder()\n",[320,6415,6416],{"class":322,"line":411},[320,6417,6418],{},"        .setRequiredNetworkType(NetworkType.CONNECTED)\n",[320,6420,6421],{"class":322,"line":416},[320,6422,6423],{},"        .setRequiresBatteryNotLow(true)\n",[320,6425,6426],{"class":322,"line":421},[320,6427,6428],{},"        .build())\n",[320,6430,6431],{"class":322,"line":427},[320,6432,6433],{},"    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)\n",[320,6435,6436],{"class":322,"line":158},[320,6437,6438],{},"    .build()\n",[320,6440,6441],{"class":322,"line":438},[320,6442,357],{"emptyLinePlaceholder":356},[320,6444,6445],{"class":322,"line":615},[320,6446,6447],{},"WorkManager.getInstance(context).enqueue(request)\n",[201,6449,6450],{},[204,6451,6452],{},"BroadcastReceiver：",[311,6454,6456],{"className":4996,"code":6455,"language":4998,"meta":316,"style":316},"\u002F\u002F 静态注册（AndroidManifest.xml，受限）\n\u002F\u002F 动态注册（推荐）\nval receiver = object : BroadcastReceiver() {\n    override fun onReceive(context: Context, intent: Intent) {\n        when (intent.action) {\n            Intent.ACTION_BATTERY_LOW -> showWarning()\n            ConnectivityManager.CONNECTIVITY_ACTION -> checkNetwork()\n        }\n    }\n}\n\n\u002F\u002F 注册\nregisterReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_LOW))\n\u002F\u002F 注销（避免泄漏）\nunregisterReceiver(receiver)\n",[192,6457,6458,6463,6468,6473,6478,6483,6488,6493,6497,6501,6505,6509,6514,6519,6524],{"__ignoreMap":316},[320,6459,6460],{"class":322,"line":323},[320,6461,6462],{},"\u002F\u002F 静态注册（AndroidManifest.xml，受限）\n",[320,6464,6465],{"class":322,"line":329},[320,6466,6467],{},"\u002F\u002F 动态注册（推荐）\n",[320,6469,6470],{"class":322,"line":335},[320,6471,6472],{},"val receiver = object : BroadcastReceiver() {\n",[320,6474,6475],{"class":322,"line":341},[320,6476,6477],{},"    override fun onReceive(context: Context, intent: Intent) {\n",[320,6479,6480],{"class":322,"line":347},[320,6481,6482],{},"        when (intent.action) {\n",[320,6484,6485],{"class":322,"line":353},[320,6486,6487],{},"            Intent.ACTION_BATTERY_LOW -> showWarning()\n",[320,6489,6490],{"class":322,"line":360},[320,6491,6492],{},"            ConnectivityManager.CONNECTIVITY_ACTION -> checkNetwork()\n",[320,6494,6495],{"class":322,"line":74},[320,6496,1042],{},[320,6498,6499],{"class":322,"line":371},[320,6500,558],{},[320,6502,6503],{"class":322,"line":377},[320,6504,350],{},[320,6506,6507],{"class":322,"line":383},[320,6508,357],{"emptyLinePlaceholder":356},[320,6510,6511],{"class":322,"line":388},[320,6512,6513],{},"\u002F\u002F 注册\n",[320,6515,6516],{"class":322,"line":394},[320,6517,6518],{},"registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_LOW))\n",[320,6520,6521],{"class":322,"line":400},[320,6522,6523],{},"\u002F\u002F 注销（避免泄漏）\n",[320,6525,6526],{"class":322,"line":121},[320,6527,6528],{},"unregisterReceiver(receiver)\n",[201,6530,6531],{},[204,6532,6533],{},"ContentProvider：",[311,6535,6537],{"className":4996,"code":6536,"language":4998,"meta":316,"style":316},"\u002F\u002F 跨应用数据共享\nclass MyProvider : ContentProvider() {\n    override fun query(uri: Uri, ...): Cursor? {\n        return when (uriMatcher.match(uri)) {\n            ITEMS -> database.query(\"items\", ...)\n            ITEM_ID -> database.query(\"items\", ..., \"id=${uri.lastPathSegment}\")\n            else -> null\n        }\n    }\n}\n\n\u002F\u002F 使用\nval cursor = contentResolver.query(\n    ContactsContract.Contacts.CONTENT_URI, null, null, null, null\n)\n",[192,6538,6539,6544,6549,6554,6559,6564,6569,6574,6578,6582,6586,6590,6594,6599,6604],{"__ignoreMap":316},[320,6540,6541],{"class":322,"line":323},[320,6542,6543],{},"\u002F\u002F 跨应用数据共享\n",[320,6545,6546],{"class":322,"line":329},[320,6547,6548],{},"class MyProvider : ContentProvider() {\n",[320,6550,6551],{"class":322,"line":335},[320,6552,6553],{},"    override fun query(uri: Uri, ...): Cursor? {\n",[320,6555,6556],{"class":322,"line":341},[320,6557,6558],{},"        return when (uriMatcher.match(uri)) {\n",[320,6560,6561],{"class":322,"line":347},[320,6562,6563],{},"            ITEMS -> database.query(\"items\", ...)\n",[320,6565,6566],{"class":322,"line":353},[320,6567,6568],{},"            ITEM_ID -> database.query(\"items\", ..., \"id=${uri.lastPathSegment}\")\n",[320,6570,6571],{"class":322,"line":360},[320,6572,6573],{},"            else -> null\n",[320,6575,6576],{"class":322,"line":74},[320,6577,1042],{},[320,6579,6580],{"class":322,"line":371},[320,6581,558],{},[320,6583,6584],{"class":322,"line":377},[320,6585,350],{},[320,6587,6588],{"class":322,"line":383},[320,6589,357],{"emptyLinePlaceholder":356},[320,6591,6592],{"class":322,"line":388},[320,6593,1211],{},[320,6595,6596],{"class":322,"line":394},[320,6597,6598],{},"val cursor = contentResolver.query(\n",[320,6600,6601],{"class":322,"line":400},[320,6602,6603],{},"    ContactsContract.Contacts.CONTENT_URI, null, null, null, null\n",[320,6605,6606],{"class":322,"line":121},[320,6607,6608],{},")\n",[10,6610],{},[13,6612,6614],{"id":6613},"_10-jetpack-组件","10. Jetpack 组件",[17,6616,6618],{"id":6617},"q101-jetpack-compose-的重组recomposition机制","Q10.1: Jetpack Compose 的重组（Recomposition）机制",[201,6620,6621],{},[204,6622,206],{},[311,6624,6626],{"className":4996,"code":6625,"language":4998,"meta":316,"style":316},"\u002F\u002F Compose 核心：声明式 UI，状态变化触发重组\n@Composable\nfun Counter() {\n    var count by remember { mutableStateOf(0) }\n\n    Column {\n        Text(\"Count: $count\")  \u002F\u002F count 变化时重组\n        Button(onClick = { count++ }) {\n            Text(\"Increment\")\n        }\n    }\n}\n",[192,6627,6628,6633,6638,6643,6648,6652,6657,6662,6667,6672,6676,6680],{"__ignoreMap":316},[320,6629,6630],{"class":322,"line":323},[320,6631,6632],{},"\u002F\u002F Compose 核心：声明式 UI，状态变化触发重组\n",[320,6634,6635],{"class":322,"line":329},[320,6636,6637],{},"@Composable\n",[320,6639,6640],{"class":322,"line":335},[320,6641,6642],{},"fun Counter() {\n",[320,6644,6645],{"class":322,"line":341},[320,6646,6647],{},"    var count by remember { mutableStateOf(0) }\n",[320,6649,6650],{"class":322,"line":347},[320,6651,357],{"emptyLinePlaceholder":356},[320,6653,6654],{"class":322,"line":353},[320,6655,6656],{},"    Column {\n",[320,6658,6659],{"class":322,"line":360},[320,6660,6661],{},"        Text(\"Count: $count\")  \u002F\u002F count 变化时重组\n",[320,6663,6664],{"class":322,"line":74},[320,6665,6666],{},"        Button(onClick = { count++ }) {\n",[320,6668,6669],{"class":322,"line":371},[320,6670,6671],{},"            Text(\"Increment\")\n",[320,6673,6674],{"class":322,"line":377},[320,6675,1042],{},[320,6677,6678],{"class":322,"line":383},[320,6679,558],{},[320,6681,6682],{"class":322,"line":388},[320,6683,350],{},[201,6685,6686],{},[204,6687,6688],{},"重组优化原则：",[311,6690,6692],{"className":4996,"code":6691,"language":4998,"meta":316,"style":316},"\u002F\u002F 1. Compose 是智能的：只重组读取了变化状态的 Composable\n@Composable\nfun Parent() {\n    var name by remember { mutableStateOf(\"\") }\n    var age by remember { mutableStateOf(0) }\n\n    NameDisplay(name)  \u002F\u002F 只有 name 变化时重组\n    AgeDisplay(age)    \u002F\u002F 只有 age 变化时重组\n}\n\n\u002F\u002F 2. remember：在重组中保持值\n@Composable\nfun ExpensiveCalculation(input: List\u003CInt>) {\n    \u002F\u002F ❌ 每次重组都计算\n    val result = input.sorted().take(10)\n\n    \u002F\u002F ✅ 只有 input 变化时重新计算\n    val result = remember(input) { input.sorted().take(10) }\n}\n\n\u002F\u002F 3. derivedStateOf：减少不必要的重组\n@Composable\nfun ItemList(items: List\u003CItem>) {\n    val listState = rememberLazyListState()\n\n    \u002F\u002F ❌ 每次滚动都重组\n    val showButton = listState.firstVisibleItemIndex > 0\n\n    \u002F\u002F ✅ 只在条件变化时重组\n    val showButton by remember {\n        derivedStateOf { listState.firstVisibleItemIndex > 0 }\n    }\n\n    if (showButton) {\n        ScrollToTopButton()\n    }\n}\n\n\u002F\u002F 4. 稳定性（Stability）\n\u002F\u002F Compose 跳过重组的条件：参数是 Stable 且 equals 返回 true\n\u002F\u002F data class 默认 Stable（所有属性都是 val 且类型 Stable）\ndata class User(val name: String, val age: Int)  \u002F\u002F ✅ Stable\n\n\u002F\u002F List\u002FMap 不是 Stable（可能被外部修改）\ndata class State(val items: List\u003CItem>)  \u002F\u002F ⚠️ 不 Stable\n\n\u002F\u002F 解决方案：使用 @Immutable 或 kotlinx.collections.immutable\n@Immutable\ndata class State(val items: ImmutableList\u003CItem>)  \u002F\u002F ✅ Stable\n",[192,6693,6694,6699,6703,6708,6713,6718,6722,6727,6732,6736,6740,6745,6749,6754,6759,6764,6768,6773,6778,6782,6786,6791,6795,6800,6805,6809,6814,6819,6823,6828,6833,6838,6842,6846,6851,6856,6860,6864,6868,6873,6878,6883,6888,6892,6897,6902,6906,6911,6916],{"__ignoreMap":316},[320,6695,6696],{"class":322,"line":323},[320,6697,6698],{},"\u002F\u002F 1. Compose 是智能的：只重组读取了变化状态的 Composable\n",[320,6700,6701],{"class":322,"line":329},[320,6702,6637],{},[320,6704,6705],{"class":322,"line":335},[320,6706,6707],{},"fun Parent() {\n",[320,6709,6710],{"class":322,"line":341},[320,6711,6712],{},"    var name by remember { mutableStateOf(\"\") }\n",[320,6714,6715],{"class":322,"line":347},[320,6716,6717],{},"    var age by remember { mutableStateOf(0) }\n",[320,6719,6720],{"class":322,"line":353},[320,6721,357],{"emptyLinePlaceholder":356},[320,6723,6724],{"class":322,"line":360},[320,6725,6726],{},"    NameDisplay(name)  \u002F\u002F 只有 name 变化时重组\n",[320,6728,6729],{"class":322,"line":74},[320,6730,6731],{},"    AgeDisplay(age)    \u002F\u002F 只有 age 变化时重组\n",[320,6733,6734],{"class":322,"line":371},[320,6735,350],{},[320,6737,6738],{"class":322,"line":377},[320,6739,357],{"emptyLinePlaceholder":356},[320,6741,6742],{"class":322,"line":383},[320,6743,6744],{},"\u002F\u002F 2. remember：在重组中保持值\n",[320,6746,6747],{"class":322,"line":388},[320,6748,6637],{},[320,6750,6751],{"class":322,"line":394},[320,6752,6753],{},"fun ExpensiveCalculation(input: List\u003CInt>) {\n",[320,6755,6756],{"class":322,"line":400},[320,6757,6758],{},"    \u002F\u002F ❌ 每次重组都计算\n",[320,6760,6761],{"class":322,"line":121},[320,6762,6763],{},"    val result = input.sorted().take(10)\n",[320,6765,6766],{"class":322,"line":411},[320,6767,357],{"emptyLinePlaceholder":356},[320,6769,6770],{"class":322,"line":416},[320,6771,6772],{},"    \u002F\u002F ✅ 只有 input 变化时重新计算\n",[320,6774,6775],{"class":322,"line":421},[320,6776,6777],{},"    val result = remember(input) { input.sorted().take(10) }\n",[320,6779,6780],{"class":322,"line":427},[320,6781,350],{},[320,6783,6784],{"class":322,"line":158},[320,6785,357],{"emptyLinePlaceholder":356},[320,6787,6788],{"class":322,"line":438},[320,6789,6790],{},"\u002F\u002F 3. derivedStateOf：减少不必要的重组\n",[320,6792,6793],{"class":322,"line":615},[320,6794,6637],{},[320,6796,6797],{"class":322,"line":621},[320,6798,6799],{},"fun ItemList(items: List\u003CItem>) {\n",[320,6801,6802],{"class":322,"line":627},[320,6803,6804],{},"    val listState = rememberLazyListState()\n",[320,6806,6807],{"class":322,"line":935},[320,6808,357],{"emptyLinePlaceholder":356},[320,6810,6811],{"class":322,"line":941},[320,6812,6813],{},"    \u002F\u002F ❌ 每次滚动都重组\n",[320,6815,6816],{"class":322,"line":947},[320,6817,6818],{},"    val showButton = listState.firstVisibleItemIndex > 0\n",[320,6820,6821],{"class":322,"line":953},[320,6822,357],{"emptyLinePlaceholder":356},[320,6824,6825],{"class":322,"line":959},[320,6826,6827],{},"    \u002F\u002F ✅ 只在条件变化时重组\n",[320,6829,6830],{"class":322,"line":965},[320,6831,6832],{},"    val showButton by remember {\n",[320,6834,6835],{"class":322,"line":970},[320,6836,6837],{},"        derivedStateOf { listState.firstVisibleItemIndex > 0 }\n",[320,6839,6840],{"class":322,"line":975},[320,6841,558],{},[320,6843,6844],{"class":322,"line":980},[320,6845,357],{"emptyLinePlaceholder":356},[320,6847,6848],{"class":322,"line":986},[320,6849,6850],{},"    if (showButton) {\n",[320,6852,6853],{"class":322,"line":992},[320,6854,6855],{},"        ScrollToTopButton()\n",[320,6857,6858],{"class":322,"line":998},[320,6859,558],{},[320,6861,6862],{"class":322,"line":1003},[320,6863,350],{},[320,6865,6866],{"class":322,"line":1009},[320,6867,357],{"emptyLinePlaceholder":356},[320,6869,6870],{"class":322,"line":1015},[320,6871,6872],{},"\u002F\u002F 4. 稳定性（Stability）\n",[320,6874,6875],{"class":322,"line":1021},[320,6876,6877],{},"\u002F\u002F Compose 跳过重组的条件：参数是 Stable 且 equals 返回 true\n",[320,6879,6880],{"class":322,"line":1027},[320,6881,6882],{},"\u002F\u002F data class 默认 Stable（所有属性都是 val 且类型 Stable）\n",[320,6884,6885],{"class":322,"line":1033},[320,6886,6887],{},"data class User(val name: String, val age: Int)  \u002F\u002F ✅ Stable\n",[320,6889,6890],{"class":322,"line":1039},[320,6891,357],{"emptyLinePlaceholder":356},[320,6893,6894],{"class":322,"line":1045},[320,6895,6896],{},"\u002F\u002F List\u002FMap 不是 Stable（可能被外部修改）\n",[320,6898,6899],{"class":322,"line":1050},[320,6900,6901],{},"data class State(val items: List\u003CItem>)  \u002F\u002F ⚠️ 不 Stable\n",[320,6903,6904],{"class":322,"line":1055},[320,6905,357],{"emptyLinePlaceholder":356},[320,6907,6908],{"class":322,"line":1060},[320,6909,6910],{},"\u002F\u002F 解决方案：使用 @Immutable 或 kotlinx.collections.immutable\n",[320,6912,6913],{"class":322,"line":1066},[320,6914,6915],{},"@Immutable\n",[320,6917,6918],{"class":322,"line":1072},[320,6919,6920],{},"data class State(val items: ImmutableList\u003CItem>)  \u002F\u002F ✅ Stable\n",[10,6922],{},[17,6924,6926],{"id":6925},"q102-viewmodel-livedatastateflow-repository-模式","Q10.2: ViewModel + LiveData\u002FStateFlow + Repository 模式",[201,6928,6929],{},[204,6930,206],{},[311,6932,6934],{"className":4996,"code":6933,"language":4998,"meta":316,"style":316},"\u002F\u002F Repository\nclass UserRepository(\n    private val api: UserApi,\n    private val dao: UserDao\n) {\n    fun getUsers(): Flow\u003CList\u003CUser>> = dao.observeAll()  \u002F\u002F Room 返回 Flow\n\n    suspend fun refresh() {\n        val users = api.fetchUsers()\n        dao.insertAll(users)\n    }\n}\n\n\u002F\u002F ViewModel（使用 StateFlow）\nclass UserViewModel(private val repository: UserRepository) : ViewModel() {\n\n    private val _uiState = MutableStateFlow\u003CUiState>(UiState.Loading)\n    val uiState: StateFlow\u003CUiState> = _uiState.asStateFlow()\n\n    init {\n        viewModelScope.launch {\n            repository.getUsers()\n                .catch { _uiState.value = UiState.Error(it.message ?: \"Unknown\") }\n                .collect { users -> _uiState.value = UiState.Success(users) }\n        }\n    }\n\n    fun refresh() {\n        viewModelScope.launch {\n            _uiState.value = UiState.Loading\n            try {\n                repository.refresh()\n            } catch (e: Exception) {\n                _uiState.value = UiState.Error(e.message ?: \"Unknown\")\n            }\n        }\n    }\n}\n\nsealed class UiState {\n    data object Loading : UiState()\n    data class Success(val users: List\u003CUser>) : UiState()\n    data class Error(val message: String) : UiState()\n}\n\n\u002F\u002F Compose UI\n@Composable\nfun UserScreen(viewModel: UserViewModel = viewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n\n    when (val state = uiState) {\n        is UiState.Loading -> CircularProgressIndicator()\n        is UiState.Success -> UserList(state.users)\n        is UiState.Error -> ErrorView(state.message, onRetry = { viewModel.refresh() })\n    }\n}\n",[192,6935,6936,6941,6946,6951,6956,6960,6965,6969,6974,6979,6984,6988,6992,6996,7001,7006,7010,7015,7020,7024,7029,7033,7038,7043,7048,7052,7056,7060,7065,7069,7074,7079,7084,7089,7094,7098,7102,7106,7110,7114,7119,7124,7129,7134,7138,7142,7147,7151,7156,7161,7165,7170,7175,7180,7185,7189],{"__ignoreMap":316},[320,6937,6938],{"class":322,"line":323},[320,6939,6940],{},"\u002F\u002F Repository\n",[320,6942,6943],{"class":322,"line":329},[320,6944,6945],{},"class UserRepository(\n",[320,6947,6948],{"class":322,"line":335},[320,6949,6950],{},"    private val api: UserApi,\n",[320,6952,6953],{"class":322,"line":341},[320,6954,6955],{},"    private val dao: UserDao\n",[320,6957,6958],{"class":322,"line":347},[320,6959,5870],{},[320,6961,6962],{"class":322,"line":353},[320,6963,6964],{},"    fun getUsers(): Flow\u003CList\u003CUser>> = dao.observeAll()  \u002F\u002F Room 返回 Flow\n",[320,6966,6967],{"class":322,"line":360},[320,6968,357],{"emptyLinePlaceholder":356},[320,6970,6971],{"class":322,"line":74},[320,6972,6973],{},"    suspend fun refresh() {\n",[320,6975,6976],{"class":322,"line":371},[320,6977,6978],{},"        val users = api.fetchUsers()\n",[320,6980,6981],{"class":322,"line":377},[320,6982,6983],{},"        dao.insertAll(users)\n",[320,6985,6986],{"class":322,"line":383},[320,6987,558],{},[320,6989,6990],{"class":322,"line":388},[320,6991,350],{},[320,6993,6994],{"class":322,"line":394},[320,6995,357],{"emptyLinePlaceholder":356},[320,6997,6998],{"class":322,"line":400},[320,6999,7000],{},"\u002F\u002F ViewModel（使用 StateFlow）\n",[320,7002,7003],{"class":322,"line":121},[320,7004,7005],{},"class UserViewModel(private val repository: UserRepository) : ViewModel() {\n",[320,7007,7008],{"class":322,"line":411},[320,7009,357],{"emptyLinePlaceholder":356},[320,7011,7012],{"class":322,"line":416},[320,7013,7014],{},"    private val _uiState = MutableStateFlow\u003CUiState>(UiState.Loading)\n",[320,7016,7017],{"class":322,"line":421},[320,7018,7019],{},"    val uiState: StateFlow\u003CUiState> = _uiState.asStateFlow()\n",[320,7021,7022],{"class":322,"line":427},[320,7023,357],{"emptyLinePlaceholder":356},[320,7025,7026],{"class":322,"line":158},[320,7027,7028],{},"    init {\n",[320,7030,7031],{"class":322,"line":438},[320,7032,5999],{},[320,7034,7035],{"class":322,"line":615},[320,7036,7037],{},"            repository.getUsers()\n",[320,7039,7040],{"class":322,"line":621},[320,7041,7042],{},"                .catch { _uiState.value = UiState.Error(it.message ?: \"Unknown\") }\n",[320,7044,7045],{"class":322,"line":627},[320,7046,7047],{},"                .collect { users -> _uiState.value = UiState.Success(users) }\n",[320,7049,7050],{"class":322,"line":935},[320,7051,1042],{},[320,7053,7054],{"class":322,"line":941},[320,7055,558],{},[320,7057,7058],{"class":322,"line":947},[320,7059,357],{"emptyLinePlaceholder":356},[320,7061,7062],{"class":322,"line":953},[320,7063,7064],{},"    fun refresh() {\n",[320,7066,7067],{"class":322,"line":959},[320,7068,5999],{},[320,7070,7071],{"class":322,"line":965},[320,7072,7073],{},"            _uiState.value = UiState.Loading\n",[320,7075,7076],{"class":322,"line":970},[320,7077,7078],{},"            try {\n",[320,7080,7081],{"class":322,"line":975},[320,7082,7083],{},"                repository.refresh()\n",[320,7085,7086],{"class":322,"line":980},[320,7087,7088],{},"            } catch (e: Exception) {\n",[320,7090,7091],{"class":322,"line":986},[320,7092,7093],{},"                _uiState.value = UiState.Error(e.message ?: \"Unknown\")\n",[320,7095,7096],{"class":322,"line":992},[320,7097,3939],{},[320,7099,7100],{"class":322,"line":998},[320,7101,1042],{},[320,7103,7104],{"class":322,"line":1003},[320,7105,558],{},[320,7107,7108],{"class":322,"line":1009},[320,7109,350],{},[320,7111,7112],{"class":322,"line":1015},[320,7113,357],{"emptyLinePlaceholder":356},[320,7115,7116],{"class":322,"line":1021},[320,7117,7118],{},"sealed class UiState {\n",[320,7120,7121],{"class":322,"line":1027},[320,7122,7123],{},"    data object Loading : UiState()\n",[320,7125,7126],{"class":322,"line":1033},[320,7127,7128],{},"    data class Success(val users: List\u003CUser>) : UiState()\n",[320,7130,7131],{"class":322,"line":1039},[320,7132,7133],{},"    data class Error(val message: String) : UiState()\n",[320,7135,7136],{"class":322,"line":1045},[320,7137,350],{},[320,7139,7140],{"class":322,"line":1050},[320,7141,357],{"emptyLinePlaceholder":356},[320,7143,7144],{"class":322,"line":1055},[320,7145,7146],{},"\u002F\u002F Compose UI\n",[320,7148,7149],{"class":322,"line":1060},[320,7150,6637],{},[320,7152,7153],{"class":322,"line":1066},[320,7154,7155],{},"fun UserScreen(viewModel: UserViewModel = viewModel()) {\n",[320,7157,7158],{"class":322,"line":1072},[320,7159,7160],{},"    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n",[320,7162,7163],{"class":322,"line":1078},[320,7164,357],{"emptyLinePlaceholder":356},[320,7166,7167],{"class":322,"line":1084},[320,7168,7169],{},"    when (val state = uiState) {\n",[320,7171,7172],{"class":322,"line":1089},[320,7173,7174],{},"        is UiState.Loading -> CircularProgressIndicator()\n",[320,7176,7177],{"class":322,"line":1094},[320,7178,7179],{},"        is UiState.Success -> UserList(state.users)\n",[320,7181,7182],{"class":322,"line":1100},[320,7183,7184],{},"        is UiState.Error -> ErrorView(state.message, onRetry = { viewModel.refresh() })\n",[320,7186,7187],{"class":322,"line":1106},[320,7188,558],{},[320,7190,7191],{"class":322,"line":1112},[320,7192,350],{},[201,7194,7195],{},[204,7196,7197],{},"LiveData vs StateFlow：",[208,7199,7200,7212],{},[211,7201,7202],{},[214,7203,7204,7206,7209],{},[217,7205],{},[217,7207,7208],{},"LiveData",[217,7210,7211],{},"StateFlow",[227,7213,7214,7228,7239,7250,7261],{},[214,7215,7216,7219,7222],{},[232,7217,7218],{},"生命周期感知",[232,7220,7221],{},"自动",[232,7223,7224,7225],{},"需要 ",[192,7226,7227],{},"collectAsStateWithLifecycle()",[214,7229,7230,7233,7236],{},[232,7231,7232],{},"初始值",[232,7234,7235],{},"可选",[232,7237,7238],{},"必须有初始值",[214,7240,7241,7244,7247],{},[232,7242,7243],{},"纯 Kotlin",[232,7245,7246],{},"需要 Android 依赖",[232,7248,7249],{},"纯 Kotlin，跨平台",[214,7251,7252,7255,7258],{},[232,7253,7254],{},"操作符",[232,7256,7257],{},"有限（map\u002FswitchMap）",[232,7259,7260],{},"完整 Flow 操作符",[214,7262,7263,7266,7269],{},[232,7264,7265],{},"推荐",[232,7267,7268],{},"旧项目维护",[232,7270,7271],{},"新项目首选",[10,7273],{},[17,7275,7277],{"id":7276},"q103-room-数据库的使用和优化","Q10.3: Room 数据库的使用和优化",[201,7279,7280],{},[204,7281,206],{},[311,7283,7285],{"className":4996,"code":7284,"language":4998,"meta":316,"style":316},"\u002F\u002F Entity\n@Entity(tableName = \"users\")\ndata class UserEntity(\n    @PrimaryKey val id: String,\n    @ColumnInfo(name = \"display_name\") val name: String,\n    val email: String,\n    @ColumnInfo(index = true) val createdAt: Long  \u002F\u002F 添加索引\n)\n\n\u002F\u002F DAO\n@Dao\ninterface UserDao {\n    @Query(\"SELECT * FROM users ORDER BY created_at DESC\")\n    fun observeAll(): Flow\u003CList\u003CUserEntity>>  \u002F\u002F 响应式查询\n\n    @Query(\"SELECT * FROM users WHERE id = :userId\")\n    suspend fun getById(userId: String): UserEntity?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    suspend fun insertAll(users: List\u003CUserEntity>)\n\n    @Transaction  \u002F\u002F 事务\n    suspend fun replaceAll(users: List\u003CUserEntity>) {\n        deleteAll()\n        insertAll(users)\n    }\n\n    @Query(\"DELETE FROM users\")\n    suspend fun deleteAll()\n}\n\n\u002F\u002F Database\n@Database(entities = [UserEntity::class], version = 2)\n@TypeConverters(Converters::class)\nabstract class AppDatabase : RoomDatabase() {\n    abstract fun userDao(): UserDao\n}\n\n\u002F\u002F 类型转换器\nclass Converters {\n    @TypeConverter\n    fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) }\n\n    @TypeConverter\n    fun dateToTimestamp(date: Date?): Long? = date?.time\n}\n\n\u002F\u002F 数据库迁移\nval MIGRATION_1_2 = object : Migration(1, 2) {\n    override fun migrate(db: SupportSQLiteDatabase) {\n        db.execSQL(\"ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT ''\")\n    }\n}\n\n\u002F\u002F 构建\nRoom.databaseBuilder(context, AppDatabase::class.java, \"app.db\")\n    .addMigrations(MIGRATION_1_2)\n    .build()\n",[192,7286,7287,7292,7297,7302,7307,7312,7317,7322,7326,7330,7335,7340,7345,7350,7355,7359,7364,7369,7373,7378,7383,7387,7392,7397,7402,7407,7411,7415,7420,7425,7429,7433,7438,7443,7448,7453,7458,7462,7466,7471,7476,7481,7486,7490,7494,7499,7503,7507,7512,7517,7522,7527,7531,7535,7539,7544,7549,7554],{"__ignoreMap":316},[320,7288,7289],{"class":322,"line":323},[320,7290,7291],{},"\u002F\u002F Entity\n",[320,7293,7294],{"class":322,"line":329},[320,7295,7296],{},"@Entity(tableName = \"users\")\n",[320,7298,7299],{"class":322,"line":335},[320,7300,7301],{},"data class UserEntity(\n",[320,7303,7304],{"class":322,"line":341},[320,7305,7306],{},"    @PrimaryKey val id: String,\n",[320,7308,7309],{"class":322,"line":347},[320,7310,7311],{},"    @ColumnInfo(name = \"display_name\") val name: String,\n",[320,7313,7314],{"class":322,"line":353},[320,7315,7316],{},"    val email: String,\n",[320,7318,7319],{"class":322,"line":360},[320,7320,7321],{},"    @ColumnInfo(index = true) val createdAt: Long  \u002F\u002F 添加索引\n",[320,7323,7324],{"class":322,"line":74},[320,7325,6608],{},[320,7327,7328],{"class":322,"line":371},[320,7329,357],{"emptyLinePlaceholder":356},[320,7331,7332],{"class":322,"line":377},[320,7333,7334],{},"\u002F\u002F DAO\n",[320,7336,7337],{"class":322,"line":383},[320,7338,7339],{},"@Dao\n",[320,7341,7342],{"class":322,"line":388},[320,7343,7344],{},"interface UserDao {\n",[320,7346,7347],{"class":322,"line":394},[320,7348,7349],{},"    @Query(\"SELECT * FROM users ORDER BY created_at DESC\")\n",[320,7351,7352],{"class":322,"line":400},[320,7353,7354],{},"    fun observeAll(): Flow\u003CList\u003CUserEntity>>  \u002F\u002F 响应式查询\n",[320,7356,7357],{"class":322,"line":121},[320,7358,357],{"emptyLinePlaceholder":356},[320,7360,7361],{"class":322,"line":411},[320,7362,7363],{},"    @Query(\"SELECT * FROM users WHERE id = :userId\")\n",[320,7365,7366],{"class":322,"line":416},[320,7367,7368],{},"    suspend fun getById(userId: String): UserEntity?\n",[320,7370,7371],{"class":322,"line":421},[320,7372,357],{"emptyLinePlaceholder":356},[320,7374,7375],{"class":322,"line":427},[320,7376,7377],{},"    @Insert(onConflict = OnConflictStrategy.REPLACE)\n",[320,7379,7380],{"class":322,"line":158},[320,7381,7382],{},"    suspend fun insertAll(users: List\u003CUserEntity>)\n",[320,7384,7385],{"class":322,"line":438},[320,7386,357],{"emptyLinePlaceholder":356},[320,7388,7389],{"class":322,"line":615},[320,7390,7391],{},"    @Transaction  \u002F\u002F 事务\n",[320,7393,7394],{"class":322,"line":621},[320,7395,7396],{},"    suspend fun replaceAll(users: List\u003CUserEntity>) {\n",[320,7398,7399],{"class":322,"line":627},[320,7400,7401],{},"        deleteAll()\n",[320,7403,7404],{"class":322,"line":935},[320,7405,7406],{},"        insertAll(users)\n",[320,7408,7409],{"class":322,"line":941},[320,7410,558],{},[320,7412,7413],{"class":322,"line":947},[320,7414,357],{"emptyLinePlaceholder":356},[320,7416,7417],{"class":322,"line":953},[320,7418,7419],{},"    @Query(\"DELETE FROM users\")\n",[320,7421,7422],{"class":322,"line":959},[320,7423,7424],{},"    suspend fun deleteAll()\n",[320,7426,7427],{"class":322,"line":965},[320,7428,350],{},[320,7430,7431],{"class":322,"line":970},[320,7432,357],{"emptyLinePlaceholder":356},[320,7434,7435],{"class":322,"line":975},[320,7436,7437],{},"\u002F\u002F Database\n",[320,7439,7440],{"class":322,"line":980},[320,7441,7442],{},"@Database(entities = [UserEntity::class], version = 2)\n",[320,7444,7445],{"class":322,"line":986},[320,7446,7447],{},"@TypeConverters(Converters::class)\n",[320,7449,7450],{"class":322,"line":992},[320,7451,7452],{},"abstract class AppDatabase : RoomDatabase() {\n",[320,7454,7455],{"class":322,"line":998},[320,7456,7457],{},"    abstract fun userDao(): UserDao\n",[320,7459,7460],{"class":322,"line":1003},[320,7461,350],{},[320,7463,7464],{"class":322,"line":1009},[320,7465,357],{"emptyLinePlaceholder":356},[320,7467,7468],{"class":322,"line":1015},[320,7469,7470],{},"\u002F\u002F 类型转换器\n",[320,7472,7473],{"class":322,"line":1021},[320,7474,7475],{},"class Converters {\n",[320,7477,7478],{"class":322,"line":1027},[320,7479,7480],{},"    @TypeConverter\n",[320,7482,7483],{"class":322,"line":1033},[320,7484,7485],{},"    fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) }\n",[320,7487,7488],{"class":322,"line":1039},[320,7489,357],{"emptyLinePlaceholder":356},[320,7491,7492],{"class":322,"line":1045},[320,7493,7480],{},[320,7495,7496],{"class":322,"line":1050},[320,7497,7498],{},"    fun dateToTimestamp(date: Date?): Long? = date?.time\n",[320,7500,7501],{"class":322,"line":1055},[320,7502,350],{},[320,7504,7505],{"class":322,"line":1060},[320,7506,357],{"emptyLinePlaceholder":356},[320,7508,7509],{"class":322,"line":1066},[320,7510,7511],{},"\u002F\u002F 数据库迁移\n",[320,7513,7514],{"class":322,"line":1072},[320,7515,7516],{},"val MIGRATION_1_2 = object : Migration(1, 2) {\n",[320,7518,7519],{"class":322,"line":1078},[320,7520,7521],{},"    override fun migrate(db: SupportSQLiteDatabase) {\n",[320,7523,7524],{"class":322,"line":1084},[320,7525,7526],{},"        db.execSQL(\"ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT ''\")\n",[320,7528,7529],{"class":322,"line":1089},[320,7530,558],{},[320,7532,7533],{"class":322,"line":1094},[320,7534,350],{},[320,7536,7537],{"class":322,"line":1100},[320,7538,357],{"emptyLinePlaceholder":356},[320,7540,7541],{"class":322,"line":1106},[320,7542,7543],{},"\u002F\u002F 构建\n",[320,7545,7546],{"class":322,"line":1112},[320,7547,7548],{},"Room.databaseBuilder(context, AppDatabase::class.java, \"app.db\")\n",[320,7550,7551],{"class":322,"line":1118},[320,7552,7553],{},"    .addMigrations(MIGRATION_1_2)\n",[320,7555,7557],{"class":322,"line":7556},58,[320,7558,6438],{},[10,7560],{},[13,7562,7564],{"id":7563},"_11-并发编程coroutines","11. 并发编程（Coroutines）",[17,7566,7568],{"id":7567},"q111-kotlin-coroutines-的核心概念","Q11.1: Kotlin Coroutines 的核心概念",[201,7570,7571],{},[204,7572,206],{},[311,7574,7576],{"className":4996,"code":7575,"language":4998,"meta":316,"style":316},"\u002F\u002F CoroutineScope：定义协程的生命周期\nclass MyViewModel : ViewModel() {\n    \u002F\u002F viewModelScope：ViewModel 销毁时自动取消\n    fun load() = viewModelScope.launch {\n        val data = fetchData()  \u002F\u002F 挂起，不阻塞线程\n        _state.value = data\n    }\n}\n\n\u002F\u002F Dispatcher：决定协程在哪个线程执行\nviewModelScope.launch(Dispatchers.Main) {      \u002F\u002F 主线程\n    val data = withContext(Dispatchers.IO) {    \u002F\u002F 切到 IO 线程\n        api.fetchData()                        \u002F\u002F 网络请求\n    }\n    updateUI(data)                             \u002F\u002F 回到主线程\n}\n\n\u002F\u002F Dispatchers 对比\n\u002F\u002F Main：主线程，UI 操作\n\u002F\u002F IO：IO 密集型（网络、文件），线程池较大\n\u002F\u002F Default：CPU 密集型（排序、解析），线程数 = CPU 核数\n\u002F\u002F Unconfined：不切换线程（不推荐常规使用）\n",[192,7577,7578,7583,7587,7592,7597,7602,7607,7611,7615,7619,7624,7629,7634,7639,7643,7648,7652,7656,7661,7666,7671,7676],{"__ignoreMap":316},[320,7579,7580],{"class":322,"line":323},[320,7581,7582],{},"\u002F\u002F CoroutineScope：定义协程的生命周期\n",[320,7584,7585],{"class":322,"line":329},[320,7586,5975],{},[320,7588,7589],{"class":322,"line":335},[320,7590,7591],{},"    \u002F\u002F viewModelScope：ViewModel 销毁时自动取消\n",[320,7593,7594],{"class":322,"line":341},[320,7595,7596],{},"    fun load() = viewModelScope.launch {\n",[320,7598,7599],{"class":322,"line":347},[320,7600,7601],{},"        val data = fetchData()  \u002F\u002F 挂起，不阻塞线程\n",[320,7603,7604],{"class":322,"line":353},[320,7605,7606],{},"        _state.value = data\n",[320,7608,7609],{"class":322,"line":360},[320,7610,558],{},[320,7612,7613],{"class":322,"line":74},[320,7614,350],{},[320,7616,7617],{"class":322,"line":371},[320,7618,357],{"emptyLinePlaceholder":356},[320,7620,7621],{"class":322,"line":377},[320,7622,7623],{},"\u002F\u002F Dispatcher：决定协程在哪个线程执行\n",[320,7625,7626],{"class":322,"line":383},[320,7627,7628],{},"viewModelScope.launch(Dispatchers.Main) {      \u002F\u002F 主线程\n",[320,7630,7631],{"class":322,"line":388},[320,7632,7633],{},"    val data = withContext(Dispatchers.IO) {    \u002F\u002F 切到 IO 线程\n",[320,7635,7636],{"class":322,"line":394},[320,7637,7638],{},"        api.fetchData()                        \u002F\u002F 网络请求\n",[320,7640,7641],{"class":322,"line":400},[320,7642,558],{},[320,7644,7645],{"class":322,"line":121},[320,7646,7647],{},"    updateUI(data)                             \u002F\u002F 回到主线程\n",[320,7649,7650],{"class":322,"line":411},[320,7651,350],{},[320,7653,7654],{"class":322,"line":416},[320,7655,357],{"emptyLinePlaceholder":356},[320,7657,7658],{"class":322,"line":421},[320,7659,7660],{},"\u002F\u002F Dispatchers 对比\n",[320,7662,7663],{"class":322,"line":427},[320,7664,7665],{},"\u002F\u002F Main：主线程，UI 操作\n",[320,7667,7668],{"class":322,"line":158},[320,7669,7670],{},"\u002F\u002F IO：IO 密集型（网络、文件），线程池较大\n",[320,7672,7673],{"class":322,"line":438},[320,7674,7675],{},"\u002F\u002F Default：CPU 密集型（排序、解析），线程数 = CPU 核数\n",[320,7677,7678],{"class":322,"line":615},[320,7679,7680],{},"\u002F\u002F Unconfined：不切换线程（不推荐常规使用）\n",[201,7682,7683],{},[204,7684,7685],{},"结构化并发：",[311,7687,7689],{"className":4996,"code":7688,"language":4998,"meta":316,"style":316},"\u002F\u002F 并行请求\nsuspend fun loadDashboard(): Dashboard = coroutineScope {\n    val user = async { fetchUser() }\n    val posts = async { fetchPosts() }\n    val stats = async { fetchStats() }\n\n    Dashboard(\n        user = user.await(),\n        posts = posts.await(),\n        stats = stats.await()\n    )\n    \u002F\u002F 任何一个失败，其他自动取消\n}\n\n\u002F\u002F 异常处理\nviewModelScope.launch {\n    try {\n        val result = repository.fetchData()\n        _state.value = UiState.Success(result)\n    } catch (e: CancellationException) {\n        throw e  \u002F\u002F 不要吞掉 CancellationException！\n    } catch (e: Exception) {\n        _state.value = UiState.Error(e.message)\n    }\n}\n\n\u002F\u002F SupervisorJob：子协程失败不影响其他子协程\nval scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)\nscope.launch { task1() }  \u002F\u002F task1 失败不会取消 task2\nscope.launch { task2() }\n",[192,7690,7691,7696,7701,7706,7711,7716,7720,7725,7730,7735,7740,7744,7749,7753,7757,7762,7767,7772,7777,7782,7787,7792,7797,7802,7806,7810,7814,7819,7824,7829],{"__ignoreMap":316},[320,7692,7693],{"class":322,"line":323},[320,7694,7695],{},"\u002F\u002F 并行请求\n",[320,7697,7698],{"class":322,"line":329},[320,7699,7700],{},"suspend fun loadDashboard(): Dashboard = coroutineScope {\n",[320,7702,7703],{"class":322,"line":335},[320,7704,7705],{},"    val user = async { fetchUser() }\n",[320,7707,7708],{"class":322,"line":341},[320,7709,7710],{},"    val posts = async { fetchPosts() }\n",[320,7712,7713],{"class":322,"line":347},[320,7714,7715],{},"    val stats = async { fetchStats() }\n",[320,7717,7718],{"class":322,"line":353},[320,7719,357],{"emptyLinePlaceholder":356},[320,7721,7722],{"class":322,"line":360},[320,7723,7724],{},"    Dashboard(\n",[320,7726,7727],{"class":322,"line":74},[320,7728,7729],{},"        user = user.await(),\n",[320,7731,7732],{"class":322,"line":371},[320,7733,7734],{},"        posts = posts.await(),\n",[320,7736,7737],{"class":322,"line":377},[320,7738,7739],{},"        stats = stats.await()\n",[320,7741,7742],{"class":322,"line":383},[320,7743,3060],{},[320,7745,7746],{"class":322,"line":388},[320,7747,7748],{},"    \u002F\u002F 任何一个失败，其他自动取消\n",[320,7750,7751],{"class":322,"line":394},[320,7752,350],{},[320,7754,7755],{"class":322,"line":400},[320,7756,357],{"emptyLinePlaceholder":356},[320,7758,7759],{"class":322,"line":121},[320,7760,7761],{},"\u002F\u002F 异常处理\n",[320,7763,7764],{"class":322,"line":411},[320,7765,7766],{},"viewModelScope.launch {\n",[320,7768,7769],{"class":322,"line":416},[320,7770,7771],{},"    try {\n",[320,7773,7774],{"class":322,"line":421},[320,7775,7776],{},"        val result = repository.fetchData()\n",[320,7778,7779],{"class":322,"line":427},[320,7780,7781],{},"        _state.value = UiState.Success(result)\n",[320,7783,7784],{"class":322,"line":158},[320,7785,7786],{},"    } catch (e: CancellationException) {\n",[320,7788,7789],{"class":322,"line":438},[320,7790,7791],{},"        throw e  \u002F\u002F 不要吞掉 CancellationException！\n",[320,7793,7794],{"class":322,"line":615},[320,7795,7796],{},"    } catch (e: Exception) {\n",[320,7798,7799],{"class":322,"line":621},[320,7800,7801],{},"        _state.value = UiState.Error(e.message)\n",[320,7803,7804],{"class":322,"line":627},[320,7805,558],{},[320,7807,7808],{"class":322,"line":935},[320,7809,350],{},[320,7811,7812],{"class":322,"line":941},[320,7813,357],{"emptyLinePlaceholder":356},[320,7815,7816],{"class":322,"line":947},[320,7817,7818],{},"\u002F\u002F SupervisorJob：子协程失败不影响其他子协程\n",[320,7820,7821],{"class":322,"line":953},[320,7822,7823],{},"val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)\n",[320,7825,7826],{"class":322,"line":959},[320,7827,7828],{},"scope.launch { task1() }  \u002F\u002F task1 失败不会取消 task2\n",[320,7830,7831],{"class":322,"line":965},[320,7832,7833],{},"scope.launch { task2() }\n",[10,7835],{},[17,7837,7839],{"id":7838},"q112-flow-的冷流与热流以及-stateflow-sharedflow","Q11.2: Flow 的冷流与热流，以及 StateFlow \u002F SharedFlow",[201,7841,7842],{},[204,7843,206],{},[311,7845,7847],{"className":4996,"code":7846,"language":4998,"meta":316,"style":316},"\u002F\u002F Cold Flow（冷流）：每个收集者独立执行\nfun fetchItems(): Flow\u003CItem> = flow {\n    for (item in api.getItems()) {\n        emit(item)\n        delay(100)\n    }\n}\n\n\u002F\u002F 每次 collect 都会重新执行 flow { } 块\nfetchItems().collect { item -> println(item) } \u002F\u002F 执行一次\nfetchItems().collect { item -> println(item) } \u002F\u002F 再执行一次\n\n\u002F\u002F Hot Flow（热流）：多个收集者共享\n\u002F\u002F StateFlow：始终持有当前值，新收集者立即获得最新值\nprivate val _state = MutableStateFlow(UiState.Loading)\nval state: StateFlow\u003CUiState> = _state.asStateFlow()\n\n\u002F\u002F SharedFlow：不持有值，可配置重放和缓冲\nprivate val _events = MutableSharedFlow\u003CEvent>(\n    replay = 0,              \u002F\u002F 新订阅者不接收历史事件\n    extraBufferCapacity = 1, \u002F\u002F 缓冲区\n    onBufferOverflow = BufferOverflow.DROP_OLDEST\n)\nval events: SharedFlow\u003CEvent> = _events.asSharedFlow()\n",[192,7848,7849,7854,7859,7864,7869,7874,7878,7882,7886,7891,7896,7901,7905,7910,7915,7920,7925,7929,7934,7939,7944,7949,7954,7958],{"__ignoreMap":316},[320,7850,7851],{"class":322,"line":323},[320,7852,7853],{},"\u002F\u002F Cold Flow（冷流）：每个收集者独立执行\n",[320,7855,7856],{"class":322,"line":329},[320,7857,7858],{},"fun fetchItems(): Flow\u003CItem> = flow {\n",[320,7860,7861],{"class":322,"line":335},[320,7862,7863],{},"    for (item in api.getItems()) {\n",[320,7865,7866],{"class":322,"line":341},[320,7867,7868],{},"        emit(item)\n",[320,7870,7871],{"class":322,"line":347},[320,7872,7873],{},"        delay(100)\n",[320,7875,7876],{"class":322,"line":353},[320,7877,558],{},[320,7879,7880],{"class":322,"line":360},[320,7881,350],{},[320,7883,7884],{"class":322,"line":74},[320,7885,357],{"emptyLinePlaceholder":356},[320,7887,7888],{"class":322,"line":371},[320,7889,7890],{},"\u002F\u002F 每次 collect 都会重新执行 flow { } 块\n",[320,7892,7893],{"class":322,"line":377},[320,7894,7895],{},"fetchItems().collect { item -> println(item) } \u002F\u002F 执行一次\n",[320,7897,7898],{"class":322,"line":383},[320,7899,7900],{},"fetchItems().collect { item -> println(item) } \u002F\u002F 再执行一次\n",[320,7902,7903],{"class":322,"line":388},[320,7904,357],{"emptyLinePlaceholder":356},[320,7906,7907],{"class":322,"line":394},[320,7908,7909],{},"\u002F\u002F Hot Flow（热流）：多个收集者共享\n",[320,7911,7912],{"class":322,"line":400},[320,7913,7914],{},"\u002F\u002F StateFlow：始终持有当前值，新收集者立即获得最新值\n",[320,7916,7917],{"class":322,"line":121},[320,7918,7919],{},"private val _state = MutableStateFlow(UiState.Loading)\n",[320,7921,7922],{"class":322,"line":411},[320,7923,7924],{},"val state: StateFlow\u003CUiState> = _state.asStateFlow()\n",[320,7926,7927],{"class":322,"line":416},[320,7928,357],{"emptyLinePlaceholder":356},[320,7930,7931],{"class":322,"line":421},[320,7932,7933],{},"\u002F\u002F SharedFlow：不持有值，可配置重放和缓冲\n",[320,7935,7936],{"class":322,"line":427},[320,7937,7938],{},"private val _events = MutableSharedFlow\u003CEvent>(\n",[320,7940,7941],{"class":322,"line":158},[320,7942,7943],{},"    replay = 0,              \u002F\u002F 新订阅者不接收历史事件\n",[320,7945,7946],{"class":322,"line":438},[320,7947,7948],{},"    extraBufferCapacity = 1, \u002F\u002F 缓冲区\n",[320,7950,7951],{"class":322,"line":615},[320,7952,7953],{},"    onBufferOverflow = BufferOverflow.DROP_OLDEST\n",[320,7955,7956],{"class":322,"line":621},[320,7957,6608],{},[320,7959,7960],{"class":322,"line":627},[320,7961,7962],{},"val events: SharedFlow\u003CEvent> = _events.asSharedFlow()\n",[201,7964,7965],{},[204,7966,7967],{},"Flow 操作符：",[311,7969,7971],{"className":4996,"code":7970,"language":4998,"meta":316,"style":316},"repository.getUsers()\n    .map { users -> users.filter { it.isActive } }          \u002F\u002F 转换\n    .distinctUntilChanged()                                  \u002F\u002F 去重\n    .debounce(300)                                           \u002F\u002F 防抖\n    .catch { e -> emit(emptyList()) }                        \u002F\u002F 异常处理\n    .flowOn(Dispatchers.IO)                                  \u002F\u002F 上游在 IO 线程\n    .onEach { users -> analytics.log(\"users: ${users.size}\") }\n    .stateIn(                                                 \u002F\u002F 转换为 StateFlow\n        scope = viewModelScope,\n        started = SharingStarted.WhileSubscribed(5000),      \u002F\u002F 无订阅者 5s 后停止\n        initialValue = emptyList()\n    )\n",[192,7972,7973,7978,7983,7988,7993,7998,8003,8008,8013,8018,8023,8028],{"__ignoreMap":316},[320,7974,7975],{"class":322,"line":323},[320,7976,7977],{},"repository.getUsers()\n",[320,7979,7980],{"class":322,"line":329},[320,7981,7982],{},"    .map { users -> users.filter { it.isActive } }          \u002F\u002F 转换\n",[320,7984,7985],{"class":322,"line":335},[320,7986,7987],{},"    .distinctUntilChanged()                                  \u002F\u002F 去重\n",[320,7989,7990],{"class":322,"line":341},[320,7991,7992],{},"    .debounce(300)                                           \u002F\u002F 防抖\n",[320,7994,7995],{"class":322,"line":347},[320,7996,7997],{},"    .catch { e -> emit(emptyList()) }                        \u002F\u002F 异常处理\n",[320,7999,8000],{"class":322,"line":353},[320,8001,8002],{},"    .flowOn(Dispatchers.IO)                                  \u002F\u002F 上游在 IO 线程\n",[320,8004,8005],{"class":322,"line":360},[320,8006,8007],{},"    .onEach { users -> analytics.log(\"users: ${users.size}\") }\n",[320,8009,8010],{"class":322,"line":74},[320,8011,8012],{},"    .stateIn(                                                 \u002F\u002F 转换为 StateFlow\n",[320,8014,8015],{"class":322,"line":371},[320,8016,8017],{},"        scope = viewModelScope,\n",[320,8019,8020],{"class":322,"line":377},[320,8021,8022],{},"        started = SharingStarted.WhileSubscribed(5000),      \u002F\u002F 无订阅者 5s 后停止\n",[320,8024,8025],{"class":322,"line":383},[320,8026,8027],{},"        initialValue = emptyList()\n",[320,8029,8030],{"class":322,"line":388},[320,8031,3060],{},[201,8033,8034],{},[204,8035,8036],{},"在 UI 中安全收集：",[311,8038,8040],{"className":4996,"code":8039,"language":4998,"meta":316,"style":316},"\u002F\u002F Compose\n@Composable\nfun MyScreen(viewModel: MyViewModel) {\n    val state by viewModel.state.collectAsStateWithLifecycle()\n    \u002F\u002F 自动跟随 Lifecycle 暂停\u002F恢复\n}\n\n\u002F\u002F View 系统\nlifecycleScope.launch {\n    repeatOnLifecycle(Lifecycle.State.STARTED) {\n        viewModel.state.collect { state ->\n            updateUI(state)\n        }\n    }\n}\n",[192,8041,8042,8047,8051,8056,8061,8066,8070,8074,8079,8084,8089,8094,8099,8103,8107],{"__ignoreMap":316},[320,8043,8044],{"class":322,"line":323},[320,8045,8046],{},"\u002F\u002F Compose\n",[320,8048,8049],{"class":322,"line":329},[320,8050,6637],{},[320,8052,8053],{"class":322,"line":335},[320,8054,8055],{},"fun MyScreen(viewModel: MyViewModel) {\n",[320,8057,8058],{"class":322,"line":341},[320,8059,8060],{},"    val state by viewModel.state.collectAsStateWithLifecycle()\n",[320,8062,8063],{"class":322,"line":347},[320,8064,8065],{},"    \u002F\u002F 自动跟随 Lifecycle 暂停\u002F恢复\n",[320,8067,8068],{"class":322,"line":353},[320,8069,350],{},[320,8071,8072],{"class":322,"line":360},[320,8073,357],{"emptyLinePlaceholder":356},[320,8075,8076],{"class":322,"line":74},[320,8077,8078],{},"\u002F\u002F View 系统\n",[320,8080,8081],{"class":322,"line":371},[320,8082,8083],{},"lifecycleScope.launch {\n",[320,8085,8086],{"class":322,"line":377},[320,8087,8088],{},"    repeatOnLifecycle(Lifecycle.State.STARTED) {\n",[320,8090,8091],{"class":322,"line":383},[320,8092,8093],{},"        viewModel.state.collect { state ->\n",[320,8095,8096],{"class":322,"line":388},[320,8097,8098],{},"            updateUI(state)\n",[320,8100,8101],{"class":322,"line":394},[320,8102,1042],{},[320,8104,8105],{"class":322,"line":400},[320,8106,558],{},[320,8108,8109],{"class":322,"line":121},[320,8110,350],{},[10,8112],{},[13,8114,8116],{"id":8115},"_12-架构与设计模式android","12. 架构与设计模式（Android）",[17,8118,8120],{"id":8119},"q121-android-推荐架构google-官方","Q12.1: Android 推荐架构（Google 官方）",[201,8122,8123],{},[204,8124,206],{},[311,8126,8129],{"className":8127,"code":8128,"language":2187},[2185],"┌─────────────────────────────────────────┐\n│              UI Layer                    │\n│   Activity\u002FFragment\u002FCompose ← ViewModel │\n│         (观察 UiState)                   │\n├─────────────────────────────────────────┤\n│            Domain Layer (可选)           │\n│            UseCases                      │\n├─────────────────────────────────────────┤\n│             Data Layer                   │\n│   Repository → Remote DataSource (API)   │\n│              → Local DataSource (Room)   │\n└─────────────────────────────────────────┘\n",[192,8130,8128],{"__ignoreMap":316},[201,8132,8133],{},[204,8134,8135],{},"单向数据流（UDF）：",[311,8137,8139],{"className":4996,"code":8138,"language":4998,"meta":316,"style":316},"\u002F\u002F 事件从 UI → ViewModel → Repository（向下）\n\u002F\u002F 状态从 Repository → ViewModel → UI（向上）\n\n\u002F\u002F UiState\ndata class HomeUiState(\n    val items: List\u003CItem> = emptyList(),\n    val isLoading: Boolean = false,\n    val error: String? = null\n)\n\n\u002F\u002F ViewModel\nclass HomeViewModel(private val repository: ItemRepository) : ViewModel() {\n    private val _uiState = MutableStateFlow(HomeUiState())\n    val uiState: StateFlow\u003CHomeUiState> = _uiState.asStateFlow()\n\n    fun onEvent(event: HomeEvent) {\n        when (event) {\n            is HomeEvent.Refresh -> refresh()\n            is HomeEvent.Delete -> delete(event.itemId)\n        }\n    }\n\n    private fun refresh() {\n        viewModelScope.launch {\n            _uiState.update { it.copy(isLoading = true) }\n            try {\n                val items = repository.getItems()\n                _uiState.update { it.copy(items = items, isLoading = false) }\n            } catch (e: Exception) {\n                _uiState.update { it.copy(error = e.message, isLoading = false) }\n            }\n        }\n    }\n}\n\nsealed class HomeEvent {\n    data object Refresh : HomeEvent()\n    data class Delete(val itemId: String) : HomeEvent()\n}\n",[192,8140,8141,8146,8151,8155,8160,8165,8170,8175,8180,8184,8188,8193,8198,8203,8208,8212,8217,8222,8227,8232,8236,8240,8244,8249,8253,8258,8262,8267,8272,8276,8281,8285,8289,8293,8297,8301,8306,8311,8316],{"__ignoreMap":316},[320,8142,8143],{"class":322,"line":323},[320,8144,8145],{},"\u002F\u002F 事件从 UI → ViewModel → Repository（向下）\n",[320,8147,8148],{"class":322,"line":329},[320,8149,8150],{},"\u002F\u002F 状态从 Repository → ViewModel → UI（向上）\n",[320,8152,8153],{"class":322,"line":335},[320,8154,357],{"emptyLinePlaceholder":356},[320,8156,8157],{"class":322,"line":341},[320,8158,8159],{},"\u002F\u002F UiState\n",[320,8161,8162],{"class":322,"line":347},[320,8163,8164],{},"data class HomeUiState(\n",[320,8166,8167],{"class":322,"line":353},[320,8168,8169],{},"    val items: List\u003CItem> = emptyList(),\n",[320,8171,8172],{"class":322,"line":360},[320,8173,8174],{},"    val isLoading: Boolean = false,\n",[320,8176,8177],{"class":322,"line":74},[320,8178,8179],{},"    val error: String? = null\n",[320,8181,8182],{"class":322,"line":371},[320,8183,6608],{},[320,8185,8186],{"class":322,"line":377},[320,8187,357],{"emptyLinePlaceholder":356},[320,8189,8190],{"class":322,"line":383},[320,8191,8192],{},"\u002F\u002F ViewModel\n",[320,8194,8195],{"class":322,"line":388},[320,8196,8197],{},"class HomeViewModel(private val repository: ItemRepository) : ViewModel() {\n",[320,8199,8200],{"class":322,"line":394},[320,8201,8202],{},"    private val _uiState = MutableStateFlow(HomeUiState())\n",[320,8204,8205],{"class":322,"line":400},[320,8206,8207],{},"    val uiState: StateFlow\u003CHomeUiState> = _uiState.asStateFlow()\n",[320,8209,8210],{"class":322,"line":121},[320,8211,357],{"emptyLinePlaceholder":356},[320,8213,8214],{"class":322,"line":411},[320,8215,8216],{},"    fun onEvent(event: HomeEvent) {\n",[320,8218,8219],{"class":322,"line":416},[320,8220,8221],{},"        when (event) {\n",[320,8223,8224],{"class":322,"line":421},[320,8225,8226],{},"            is HomeEvent.Refresh -> refresh()\n",[320,8228,8229],{"class":322,"line":427},[320,8230,8231],{},"            is HomeEvent.Delete -> delete(event.itemId)\n",[320,8233,8234],{"class":322,"line":158},[320,8235,1042],{},[320,8237,8238],{"class":322,"line":438},[320,8239,558],{},[320,8241,8242],{"class":322,"line":615},[320,8243,357],{"emptyLinePlaceholder":356},[320,8245,8246],{"class":322,"line":621},[320,8247,8248],{},"    private fun refresh() {\n",[320,8250,8251],{"class":322,"line":627},[320,8252,5999],{},[320,8254,8255],{"class":322,"line":935},[320,8256,8257],{},"            _uiState.update { it.copy(isLoading = true) }\n",[320,8259,8260],{"class":322,"line":941},[320,8261,7078],{},[320,8263,8264],{"class":322,"line":947},[320,8265,8266],{},"                val items = repository.getItems()\n",[320,8268,8269],{"class":322,"line":953},[320,8270,8271],{},"                _uiState.update { it.copy(items = items, isLoading = false) }\n",[320,8273,8274],{"class":322,"line":959},[320,8275,7088],{},[320,8277,8278],{"class":322,"line":965},[320,8279,8280],{},"                _uiState.update { it.copy(error = e.message, isLoading = false) }\n",[320,8282,8283],{"class":322,"line":970},[320,8284,3939],{},[320,8286,8287],{"class":322,"line":975},[320,8288,1042],{},[320,8290,8291],{"class":322,"line":980},[320,8292,558],{},[320,8294,8295],{"class":322,"line":986},[320,8296,350],{},[320,8298,8299],{"class":322,"line":992},[320,8300,357],{"emptyLinePlaceholder":356},[320,8302,8303],{"class":322,"line":998},[320,8304,8305],{},"sealed class HomeEvent {\n",[320,8307,8308],{"class":322,"line":1003},[320,8309,8310],{},"    data object Refresh : HomeEvent()\n",[320,8312,8313],{"class":322,"line":1009},[320,8314,8315],{},"    data class Delete(val itemId: String) : HomeEvent()\n",[320,8317,8318],{"class":322,"line":1015},[320,8319,350],{},[10,8321],{},[17,8323,8325],{"id":8324},"q122-hilt-依赖注入","Q12.2: Hilt 依赖注入",[201,8327,8328],{},[204,8329,206],{},[311,8331,8333],{"className":4996,"code":8332,"language":4998,"meta":316,"style":316},"\u002F\u002F 1. Module 定义\n@Module\n@InstallIn(SingletonComponent::class)\nobject NetworkModule {\n    @Provides\n    @Singleton\n    fun provideRetrofit(): Retrofit = Retrofit.Builder()\n        .baseUrl(\"https:\u002F\u002Fapi.example.com\")\n        .addConverterFactory(GsonConverterFactory.create())\n        .build()\n\n    @Provides\n    @Singleton\n    fun provideUserApi(retrofit: Retrofit): UserApi =\n        retrofit.create(UserApi::class.java)\n}\n\n@Module\n@InstallIn(SingletonComponent::class)\nabstract class RepositoryModule {\n    @Binds\n    @Singleton\n    abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository\n}\n\n\u002F\u002F 2. 注入\n@HiltViewModel\nclass UserViewModel @Inject constructor(\n    private val repository: UserRepository\n) : ViewModel() { ... }\n\n@AndroidEntryPoint\nclass UserActivity : AppCompatActivity() {\n    private val viewModel: UserViewModel by viewModels()\n}\n\n\u002F\u002F 3. Scope 对比\n\u002F\u002F @Singleton → SingletonComponent（应用级）\n\u002F\u002F @ActivityScoped → ActivityComponent（Activity 级）\n\u002F\u002F @ViewModelScoped → ViewModelComponent（ViewModel 级）\n\u002F\u002F @FragmentScoped → FragmentComponent（Fragment 级）\n",[192,8334,8335,8340,8345,8350,8355,8360,8365,8370,8375,8380,8385,8389,8393,8397,8402,8407,8411,8415,8419,8423,8428,8433,8437,8442,8446,8450,8455,8460,8465,8470,8475,8479,8484,8489,8494,8498,8502,8507,8512,8517,8522],{"__ignoreMap":316},[320,8336,8337],{"class":322,"line":323},[320,8338,8339],{},"\u002F\u002F 1. Module 定义\n",[320,8341,8342],{"class":322,"line":329},[320,8343,8344],{},"@Module\n",[320,8346,8347],{"class":322,"line":335},[320,8348,8349],{},"@InstallIn(SingletonComponent::class)\n",[320,8351,8352],{"class":322,"line":341},[320,8353,8354],{},"object NetworkModule {\n",[320,8356,8357],{"class":322,"line":347},[320,8358,8359],{},"    @Provides\n",[320,8361,8362],{"class":322,"line":353},[320,8363,8364],{},"    @Singleton\n",[320,8366,8367],{"class":322,"line":360},[320,8368,8369],{},"    fun provideRetrofit(): Retrofit = Retrofit.Builder()\n",[320,8371,8372],{"class":322,"line":74},[320,8373,8374],{},"        .baseUrl(\"https:\u002F\u002Fapi.example.com\")\n",[320,8376,8377],{"class":322,"line":371},[320,8378,8379],{},"        .addConverterFactory(GsonConverterFactory.create())\n",[320,8381,8382],{"class":322,"line":377},[320,8383,8384],{},"        .build()\n",[320,8386,8387],{"class":322,"line":383},[320,8388,357],{"emptyLinePlaceholder":356},[320,8390,8391],{"class":322,"line":388},[320,8392,8359],{},[320,8394,8395],{"class":322,"line":394},[320,8396,8364],{},[320,8398,8399],{"class":322,"line":400},[320,8400,8401],{},"    fun provideUserApi(retrofit: Retrofit): UserApi =\n",[320,8403,8404],{"class":322,"line":121},[320,8405,8406],{},"        retrofit.create(UserApi::class.java)\n",[320,8408,8409],{"class":322,"line":411},[320,8410,350],{},[320,8412,8413],{"class":322,"line":416},[320,8414,357],{"emptyLinePlaceholder":356},[320,8416,8417],{"class":322,"line":421},[320,8418,8344],{},[320,8420,8421],{"class":322,"line":427},[320,8422,8349],{},[320,8424,8425],{"class":322,"line":158},[320,8426,8427],{},"abstract class RepositoryModule {\n",[320,8429,8430],{"class":322,"line":438},[320,8431,8432],{},"    @Binds\n",[320,8434,8435],{"class":322,"line":615},[320,8436,8364],{},[320,8438,8439],{"class":322,"line":621},[320,8440,8441],{},"    abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository\n",[320,8443,8444],{"class":322,"line":627},[320,8445,350],{},[320,8447,8448],{"class":322,"line":935},[320,8449,357],{"emptyLinePlaceholder":356},[320,8451,8452],{"class":322,"line":941},[320,8453,8454],{},"\u002F\u002F 2. 注入\n",[320,8456,8457],{"class":322,"line":947},[320,8458,8459],{},"@HiltViewModel\n",[320,8461,8462],{"class":322,"line":953},[320,8463,8464],{},"class UserViewModel @Inject constructor(\n",[320,8466,8467],{"class":322,"line":959},[320,8468,8469],{},"    private val repository: UserRepository\n",[320,8471,8472],{"class":322,"line":965},[320,8473,8474],{},") : ViewModel() { ... }\n",[320,8476,8477],{"class":322,"line":970},[320,8478,357],{"emptyLinePlaceholder":356},[320,8480,8481],{"class":322,"line":975},[320,8482,8483],{},"@AndroidEntryPoint\n",[320,8485,8486],{"class":322,"line":980},[320,8487,8488],{},"class UserActivity : AppCompatActivity() {\n",[320,8490,8491],{"class":322,"line":986},[320,8492,8493],{},"    private val viewModel: UserViewModel by viewModels()\n",[320,8495,8496],{"class":322,"line":992},[320,8497,350],{},[320,8499,8500],{"class":322,"line":998},[320,8501,357],{"emptyLinePlaceholder":356},[320,8503,8504],{"class":322,"line":1003},[320,8505,8506],{},"\u002F\u002F 3. Scope 对比\n",[320,8508,8509],{"class":322,"line":1009},[320,8510,8511],{},"\u002F\u002F @Singleton → SingletonComponent（应用级）\n",[320,8513,8514],{"class":322,"line":1015},[320,8515,8516],{},"\u002F\u002F @ActivityScoped → ActivityComponent（Activity 级）\n",[320,8518,8519],{"class":322,"line":1021},[320,8520,8521],{},"\u002F\u002F @ViewModelScoped → ViewModelComponent（ViewModel 级）\n",[320,8523,8524],{"class":322,"line":1027},[320,8525,8526],{},"\u002F\u002F @FragmentScoped → FragmentComponent（Fragment 级）\n",[10,8528],{},[13,8530,8532],{"id":8531},"_13-性能优化与工具android","13. 性能优化与工具（Android）",[17,8534,8536],{"id":8535},"q131-android-性能优化的关键领域","Q13.1: Android 性能优化的关键领域",[201,8538,8539],{},[204,8540,206],{},[201,8542,8543],{},[204,8544,8545],{},"1. 启动优化：",[311,8547,8549],{"className":4996,"code":8548,"language":4998,"meta":316,"style":316},"\u002F\u002F App Startup 库：控制初始化顺序和延迟\nclass AnalyticsInitializer : Initializer\u003CAnalytics> {\n    override fun create(context: Context): Analytics {\n        return Analytics.init(context)\n    }\n\n    override fun dependencies(): List\u003CClass\u003Cout Initializer\u003C*>>> {\n        return listOf(CrashReportingInitializer::class.java) \u002F\u002F 依赖关系\n    }\n}\n\n\u002F\u002F Baseline Profile（提升启动和运行时性能）\n\u002F\u002F 提前编译热路径代码为机器码\n@get:Rule\nval rule = BaselineProfileRule()\n\n@Test\nfun generateBaselineProfile() {\n    rule.collect(packageName = \"com.example.app\") {\n        startActivityAndWait()\n        \u002F\u002F 模拟用户操作...\n    }\n}\n",[192,8550,8551,8556,8561,8566,8571,8575,8579,8584,8589,8593,8597,8601,8606,8611,8616,8621,8625,8630,8635,8640,8645,8650,8654],{"__ignoreMap":316},[320,8552,8553],{"class":322,"line":323},[320,8554,8555],{},"\u002F\u002F App Startup 库：控制初始化顺序和延迟\n",[320,8557,8558],{"class":322,"line":329},[320,8559,8560],{},"class AnalyticsInitializer : Initializer\u003CAnalytics> {\n",[320,8562,8563],{"class":322,"line":335},[320,8564,8565],{},"    override fun create(context: Context): Analytics {\n",[320,8567,8568],{"class":322,"line":341},[320,8569,8570],{},"        return Analytics.init(context)\n",[320,8572,8573],{"class":322,"line":347},[320,8574,558],{},[320,8576,8577],{"class":322,"line":353},[320,8578,357],{"emptyLinePlaceholder":356},[320,8580,8581],{"class":322,"line":360},[320,8582,8583],{},"    override fun dependencies(): List\u003CClass\u003Cout Initializer\u003C*>>> {\n",[320,8585,8586],{"class":322,"line":74},[320,8587,8588],{},"        return listOf(CrashReportingInitializer::class.java) \u002F\u002F 依赖关系\n",[320,8590,8591],{"class":322,"line":371},[320,8592,558],{},[320,8594,8595],{"class":322,"line":377},[320,8596,350],{},[320,8598,8599],{"class":322,"line":383},[320,8600,357],{"emptyLinePlaceholder":356},[320,8602,8603],{"class":322,"line":388},[320,8604,8605],{},"\u002F\u002F Baseline Profile（提升启动和运行时性能）\n",[320,8607,8608],{"class":322,"line":394},[320,8609,8610],{},"\u002F\u002F 提前编译热路径代码为机器码\n",[320,8612,8613],{"class":322,"line":400},[320,8614,8615],{},"@get:Rule\n",[320,8617,8618],{"class":322,"line":121},[320,8619,8620],{},"val rule = BaselineProfileRule()\n",[320,8622,8623],{"class":322,"line":411},[320,8624,357],{"emptyLinePlaceholder":356},[320,8626,8627],{"class":322,"line":416},[320,8628,8629],{},"@Test\n",[320,8631,8632],{"class":322,"line":421},[320,8633,8634],{},"fun generateBaselineProfile() {\n",[320,8636,8637],{"class":322,"line":427},[320,8638,8639],{},"    rule.collect(packageName = \"com.example.app\") {\n",[320,8641,8642],{"class":322,"line":158},[320,8643,8644],{},"        startActivityAndWait()\n",[320,8646,8647],{"class":322,"line":438},[320,8648,8649],{},"        \u002F\u002F 模拟用户操作...\n",[320,8651,8652],{"class":322,"line":615},[320,8653,558],{},[320,8655,8656],{"class":322,"line":621},[320,8657,350],{},[201,8659,8660],{},[204,8661,8662],{},"2. 内存优化：",[311,8664,8666],{"className":4996,"code":8665,"language":4998,"meta":316,"style":316},"\u002F\u002F 避免内存泄漏\n\u002F\u002F ❌ 静态引用 Activity\ncompanion object {\n    var activity: Activity? = null  \u002F\u002F 永远不要这样做\n}\n\n\u002F\u002F ❌ 内部类持有外部引用\nclass MyActivity : AppCompatActivity() {\n    inner class MyHandler : Handler() { ... }  \u002F\u002F 隐式持有 Activity\n}\n\n\u002F\u002F ✅ 使用 WeakReference 或静态内部类\nclass MyHandler(activity: Activity) : Handler(Looper.getMainLooper()) {\n    private val activityRef = WeakReference(activity)\n    override fun handleMessage(msg: Message) {\n        activityRef.get()?.let { \u002F* ... *\u002F }\n    }\n}\n",[192,8667,8668,8673,8678,8683,8688,8692,8696,8701,8706,8711,8715,8719,8724,8729,8734,8739,8744,8748],{"__ignoreMap":316},[320,8669,8670],{"class":322,"line":323},[320,8671,8672],{},"\u002F\u002F 避免内存泄漏\n",[320,8674,8675],{"class":322,"line":329},[320,8676,8677],{},"\u002F\u002F ❌ 静态引用 Activity\n",[320,8679,8680],{"class":322,"line":335},[320,8681,8682],{},"companion object {\n",[320,8684,8685],{"class":322,"line":341},[320,8686,8687],{},"    var activity: Activity? = null  \u002F\u002F 永远不要这样做\n",[320,8689,8690],{"class":322,"line":347},[320,8691,350],{},[320,8693,8694],{"class":322,"line":353},[320,8695,357],{"emptyLinePlaceholder":356},[320,8697,8698],{"class":322,"line":360},[320,8699,8700],{},"\u002F\u002F ❌ 内部类持有外部引用\n",[320,8702,8703],{"class":322,"line":74},[320,8704,8705],{},"class MyActivity : AppCompatActivity() {\n",[320,8707,8708],{"class":322,"line":371},[320,8709,8710],{},"    inner class MyHandler : Handler() { ... }  \u002F\u002F 隐式持有 Activity\n",[320,8712,8713],{"class":322,"line":377},[320,8714,350],{},[320,8716,8717],{"class":322,"line":383},[320,8718,357],{"emptyLinePlaceholder":356},[320,8720,8721],{"class":322,"line":388},[320,8722,8723],{},"\u002F\u002F ✅ 使用 WeakReference 或静态内部类\n",[320,8725,8726],{"class":322,"line":394},[320,8727,8728],{},"class MyHandler(activity: Activity) : Handler(Looper.getMainLooper()) {\n",[320,8730,8731],{"class":322,"line":400},[320,8732,8733],{},"    private val activityRef = WeakReference(activity)\n",[320,8735,8736],{"class":322,"line":121},[320,8737,8738],{},"    override fun handleMessage(msg: Message) {\n",[320,8740,8741],{"class":322,"line":411},[320,8742,8743],{},"        activityRef.get()?.let { \u002F* ... *\u002F }\n",[320,8745,8746],{"class":322,"line":416},[320,8747,558],{},[320,8749,8750],{"class":322,"line":421},[320,8751,350],{},[201,8753,8754],{},[204,8755,8756],{},"3. 布局优化：",[311,8758,8760],{"className":4996,"code":8759,"language":4998,"meta":316,"style":316},"\u002F\u002F Compose：避免不必要的重组（前面已详述）\n\u002F\u002F View 系统：减少层级、使用 ConstraintLayout\n\u002F\u002F 工具：Layout Inspector\n\n\u002F\u002F R8\u002FProGuard：代码缩减 + 混淆\nandroid {\n    buildTypes {\n        release {\n            isMinifyEnabled = true\n            isShrinkResources = true\n            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n        }\n    }\n}\n",[192,8761,8762,8767,8772,8777,8781,8786,8791,8796,8801,8806,8811,8816,8820,8824],{"__ignoreMap":316},[320,8763,8764],{"class":322,"line":323},[320,8765,8766],{},"\u002F\u002F Compose：避免不必要的重组（前面已详述）\n",[320,8768,8769],{"class":322,"line":329},[320,8770,8771],{},"\u002F\u002F View 系统：减少层级、使用 ConstraintLayout\n",[320,8773,8774],{"class":322,"line":335},[320,8775,8776],{},"\u002F\u002F 工具：Layout Inspector\n",[320,8778,8779],{"class":322,"line":341},[320,8780,357],{"emptyLinePlaceholder":356},[320,8782,8783],{"class":322,"line":347},[320,8784,8785],{},"\u002F\u002F R8\u002FProGuard：代码缩减 + 混淆\n",[320,8787,8788],{"class":322,"line":353},[320,8789,8790],{},"android {\n",[320,8792,8793],{"class":322,"line":360},[320,8794,8795],{},"    buildTypes {\n",[320,8797,8798],{"class":322,"line":74},[320,8799,8800],{},"        release {\n",[320,8802,8803],{"class":322,"line":371},[320,8804,8805],{},"            isMinifyEnabled = true\n",[320,8807,8808],{"class":322,"line":377},[320,8809,8810],{},"            isShrinkResources = true\n",[320,8812,8813],{"class":322,"line":383},[320,8814,8815],{},"            proguardFiles(getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\")\n",[320,8817,8818],{"class":322,"line":388},[320,8819,1042],{},[320,8821,8822],{"class":322,"line":394},[320,8823,558],{},[320,8825,8826],{"class":322,"line":400},[320,8827,350],{},[10,8829],{},[17,8831,8833],{"id":8832},"q132-android-常用性能分析工具","Q13.2: Android 常用性能分析工具",[201,8835,8836],{},[204,8837,206],{},[208,8839,8840,8849],{},[211,8841,8842],{},[214,8843,8844,8847],{},[217,8845,8846],{},"工具",[217,8848,1266],{},[227,8850,8851,8859,8867,8875,8883,8891,8899],{},[214,8852,8853,8856],{},[232,8854,8855],{},"Android Studio Profiler",[232,8857,8858],{},"CPU、内存、网络、电量实时监控",[214,8860,8861,8864],{},[232,8862,8863],{},"Layout Inspector",[232,8865,8866],{},"Compose\u002FView 层级、重组次数",[214,8868,8869,8872],{},[232,8870,8871],{},"LeakCanary",[232,8873,8874],{},"自动检测内存泄漏",[214,8876,8877,8880],{},[232,8878,8879],{},"Systrace \u002F Perfetto",[232,8881,8882],{},"系统级帧分析",[214,8884,8885,8888],{},[232,8886,8887],{},"Macrobenchmark",[232,8889,8890],{},"启动时间、帧率等自动化基准测试",[214,8892,8893,8896],{},[232,8894,8895],{},"Baseline Profile",[232,8897,8898],{},"预编译热路径提升性能",[214,8900,8901,8904],{},[232,8902,8903],{},"R8",[232,8905,8906],{},"代码缩减、混淆、优化",[311,8908,8910],{"className":4996,"code":8909,"language":4998,"meta":316,"style":316},"\u002F\u002F LeakCanary（Debug 版自动检测泄漏）\ndebugImplementation(\"com.squareup.leakcanary:leakcanary-android:2.14\")\n\u002F\u002F 无需额外代码，泄漏时自动弹出通知\n\n\u002F\u002F Macrobenchmark\n@LargeTest\n@RunWith(AndroidJUnit4::class)\nclass StartupBenchmark {\n    @get:Rule\n    val benchmarkRule = MacrobenchmarkRule()\n\n    @Test\n    fun startupCompilation() = benchmarkRule.measureRepeated(\n        packageName = \"com.example.app\",\n        metrics = listOf(StartupTimingMetric()),\n        iterations = 5,\n        startupMode = StartupMode.COLD\n    ) {\n        pressHome()\n        startActivityAndWait()\n    }\n}\n",[192,8911,8912,8917,8922,8927,8931,8936,8941,8946,8951,8956,8961,8965,8970,8975,8980,8985,8990,8995,9000,9005,9009,9013],{"__ignoreMap":316},[320,8913,8914],{"class":322,"line":323},[320,8915,8916],{},"\u002F\u002F LeakCanary（Debug 版自动检测泄漏）\n",[320,8918,8919],{"class":322,"line":329},[320,8920,8921],{},"debugImplementation(\"com.squareup.leakcanary:leakcanary-android:2.14\")\n",[320,8923,8924],{"class":322,"line":335},[320,8925,8926],{},"\u002F\u002F 无需额外代码，泄漏时自动弹出通知\n",[320,8928,8929],{"class":322,"line":341},[320,8930,357],{"emptyLinePlaceholder":356},[320,8932,8933],{"class":322,"line":347},[320,8934,8935],{},"\u002F\u002F Macrobenchmark\n",[320,8937,8938],{"class":322,"line":353},[320,8939,8940],{},"@LargeTest\n",[320,8942,8943],{"class":322,"line":360},[320,8944,8945],{},"@RunWith(AndroidJUnit4::class)\n",[320,8947,8948],{"class":322,"line":74},[320,8949,8950],{},"class StartupBenchmark {\n",[320,8952,8953],{"class":322,"line":371},[320,8954,8955],{},"    @get:Rule\n",[320,8957,8958],{"class":322,"line":377},[320,8959,8960],{},"    val benchmarkRule = MacrobenchmarkRule()\n",[320,8962,8963],{"class":322,"line":383},[320,8964,357],{"emptyLinePlaceholder":356},[320,8966,8967],{"class":322,"line":388},[320,8968,8969],{},"    @Test\n",[320,8971,8972],{"class":322,"line":394},[320,8973,8974],{},"    fun startupCompilation() = benchmarkRule.measureRepeated(\n",[320,8976,8977],{"class":322,"line":400},[320,8978,8979],{},"        packageName = \"com.example.app\",\n",[320,8981,8982],{"class":322,"line":121},[320,8983,8984],{},"        metrics = listOf(StartupTimingMetric()),\n",[320,8986,8987],{"class":322,"line":411},[320,8988,8989],{},"        iterations = 5,\n",[320,8991,8992],{"class":322,"line":416},[320,8993,8994],{},"        startupMode = StartupMode.COLD\n",[320,8996,8997],{"class":322,"line":421},[320,8998,8999],{},"    ) {\n",[320,9001,9002],{"class":322,"line":427},[320,9003,9004],{},"        pressHome()\n",[320,9006,9007],{"class":322,"line":158},[320,9008,8644],{},[320,9010,9011],{"class":322,"line":438},[320,9012,558],{},[320,9014,9015],{"class":322,"line":615},[320,9016,350],{},[10,9018],{},[13,9020,9022],{"id":9021},"_14-系统机制","14. 系统机制",[17,9024,9026],{"id":9025},"q141-android-的进程优先级和后台限制","Q14.1: Android 的进程优先级和后台限制",[201,9028,9029],{},[204,9030,206],{},[311,9032,9035],{"className":9033,"code":9034,"language":2187},[2185],"进程优先级（从高到低）：\n1. 前台进程（Foreground）     - 用户正在交互的 Activity\n2. 可见进程（Visible）        - 可见但不在前台（如对话框后面的 Activity）\n3. 服务进程（Service）        - 正在运行 startService\n4. 缓存进程（Cached\u002FBackground） - 不可见，可能被系统随时杀死\n5. 空进程（Empty）            - 无活跃组件，最先被杀\n",[192,9036,9034],{"__ignoreMap":316},[201,9038,9039],{},[204,9040,9041],{},"Android 后台限制演进：",[208,9043,9044,9054],{},[211,9045,9046],{},[214,9047,9048,9051],{},[217,9049,9050],{},"版本",[217,9052,9053],{},"限制",[227,9055,9056,9064,9072,9080,9088],{},[214,9057,9058,9061],{},[232,9059,9060],{},"Android 8 (O)",[232,9062,9063],{},"后台服务限制、广播限制",[214,9065,9066,9069],{},[232,9067,9068],{},"Android 9 (P)",[232,9070,9071],{},"Standby Buckets",[214,9073,9074,9077],{},[232,9075,9076],{},"Android 12 (S)",[232,9078,9079],{},"前台服务启动限制、精确闹钟权限",[214,9081,9082,9085],{},[232,9083,9084],{},"Android 13 (T)",[232,9086,9087],{},"通知权限、前台服务类型声明",[214,9089,9090,9093],{},[232,9091,9092],{},"Android 14 (U)",[232,9094,9095],{},"前台服务类型强制声明",[311,9097,9099],{"className":4996,"code":9098,"language":4998,"meta":316,"style":316},"\u002F\u002F 正确的后台工作方式选择\n\u002F\u002F 立即执行 + 长时间 → Foreground Service\n\u002F\u002F 可延迟 + 需要保证执行 → WorkManager\n\u002F\u002F 精确定时 → AlarmManager（需要权限）\n\u002F\u002F 短暂后台操作 → Coroutine（在 ViewModel\u002Flifecycle scope 中）\n",[192,9100,9101,9106,9111,9116,9121],{"__ignoreMap":316},[320,9102,9103],{"class":322,"line":323},[320,9104,9105],{},"\u002F\u002F 正确的后台工作方式选择\n",[320,9107,9108],{"class":322,"line":329},[320,9109,9110],{},"\u002F\u002F 立即执行 + 长时间 → Foreground Service\n",[320,9112,9113],{"class":322,"line":335},[320,9114,9115],{},"\u002F\u002F 可延迟 + 需要保证执行 → WorkManager\n",[320,9117,9118],{"class":322,"line":341},[320,9119,9120],{},"\u002F\u002F 精确定时 → AlarmManager（需要权限）\n",[320,9122,9123],{"class":322,"line":347},[320,9124,9125],{},"\u002F\u002F 短暂后台操作 → Coroutine（在 ViewModel\u002Flifecycle scope 中）\n",[10,9127],{},[17,9129,9131],{"id":9130},"q142-intent-和-intent-filter-的工作机制","Q14.2: Intent 和 Intent Filter 的工作机制",[201,9133,9134],{},[204,9135,206],{},[311,9137,9139],{"className":4996,"code":9138,"language":4998,"meta":316,"style":316},"\u002F\u002F 显式 Intent：指定目标组件\nval intent = Intent(this, DetailActivity::class.java).apply {\n    putExtra(\"item_id\", \"123\")\n}\nstartActivity(intent)\n\n\u002F\u002F 隐式 Intent：通过 Action\u002FCategory\u002FData 匹配\nval intent = Intent(Intent.ACTION_VIEW, Uri.parse(\"https:\u002F\u002Fexample.com\"))\nstartActivity(intent)\n\n\u002F\u002F Intent Filter（在 Manifest 中声明）\n\u002F\u002F \u003Cintent-filter>\n\u002F\u002F     \u003Caction android:name=\"android.intent.action.VIEW\" \u002F>\n\u002F\u002F     \u003Ccategory android:name=\"android.intent.category.DEFAULT\" \u002F>\n\u002F\u002F     \u003Cdata android:scheme=\"https\" android:host=\"example.com\" \u002F>\n\u002F\u002F \u003C\u002Fintent-filter>\n\n\u002F\u002F Deep Link 处理\noverride fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    intent?.data?.let { uri ->\n        when {\n            uri.pathSegments.firstOrNull() == \"product\" -> {\n                val productId = uri.lastPathSegment\n                navigateToProduct(productId)\n            }\n        }\n    }\n}\n\n\u002F\u002F PendingIntent（跨进程传递 Intent）\nval pendingIntent = PendingIntent.getActivity(\n    context, 0, intent,\n    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE\n)\n",[192,9140,9141,9146,9151,9156,9160,9165,9169,9174,9179,9183,9187,9192,9197,9202,9207,9212,9217,9221,9226,9230,9234,9239,9244,9249,9254,9259,9263,9267,9271,9275,9279,9284,9289,9294,9299],{"__ignoreMap":316},[320,9142,9143],{"class":322,"line":323},[320,9144,9145],{},"\u002F\u002F 显式 Intent：指定目标组件\n",[320,9147,9148],{"class":322,"line":329},[320,9149,9150],{},"val intent = Intent(this, DetailActivity::class.java).apply {\n",[320,9152,9153],{"class":322,"line":335},[320,9154,9155],{},"    putExtra(\"item_id\", \"123\")\n",[320,9157,9158],{"class":322,"line":341},[320,9159,350],{},[320,9161,9162],{"class":322,"line":347},[320,9163,9164],{},"startActivity(intent)\n",[320,9166,9167],{"class":322,"line":353},[320,9168,357],{"emptyLinePlaceholder":356},[320,9170,9171],{"class":322,"line":360},[320,9172,9173],{},"\u002F\u002F 隐式 Intent：通过 Action\u002FCategory\u002FData 匹配\n",[320,9175,9176],{"class":322,"line":74},[320,9177,9178],{},"val intent = Intent(Intent.ACTION_VIEW, Uri.parse(\"https:\u002F\u002Fexample.com\"))\n",[320,9180,9181],{"class":322,"line":371},[320,9182,9164],{},[320,9184,9185],{"class":322,"line":377},[320,9186,357],{"emptyLinePlaceholder":356},[320,9188,9189],{"class":322,"line":383},[320,9190,9191],{},"\u002F\u002F Intent Filter（在 Manifest 中声明）\n",[320,9193,9194],{"class":322,"line":388},[320,9195,9196],{},"\u002F\u002F \u003Cintent-filter>\n",[320,9198,9199],{"class":322,"line":394},[320,9200,9201],{},"\u002F\u002F     \u003Caction android:name=\"android.intent.action.VIEW\" \u002F>\n",[320,9203,9204],{"class":322,"line":400},[320,9205,9206],{},"\u002F\u002F     \u003Ccategory android:name=\"android.intent.category.DEFAULT\" \u002F>\n",[320,9208,9209],{"class":322,"line":121},[320,9210,9211],{},"\u002F\u002F     \u003Cdata android:scheme=\"https\" android:host=\"example.com\" \u002F>\n",[320,9213,9214],{"class":322,"line":411},[320,9215,9216],{},"\u002F\u002F \u003C\u002Fintent-filter>\n",[320,9218,9219],{"class":322,"line":416},[320,9220,357],{"emptyLinePlaceholder":356},[320,9222,9223],{"class":322,"line":421},[320,9224,9225],{},"\u002F\u002F Deep Link 处理\n",[320,9227,9228],{"class":322,"line":427},[320,9229,6053],{},[320,9231,9232],{"class":322,"line":158},[320,9233,6058],{},[320,9235,9236],{"class":322,"line":438},[320,9237,9238],{},"    intent?.data?.let { uri ->\n",[320,9240,9241],{"class":322,"line":615},[320,9242,9243],{},"        when {\n",[320,9245,9246],{"class":322,"line":621},[320,9247,9248],{},"            uri.pathSegments.firstOrNull() == \"product\" -> {\n",[320,9250,9251],{"class":322,"line":627},[320,9252,9253],{},"                val productId = uri.lastPathSegment\n",[320,9255,9256],{"class":322,"line":935},[320,9257,9258],{},"                navigateToProduct(productId)\n",[320,9260,9261],{"class":322,"line":941},[320,9262,3939],{},[320,9264,9265],{"class":322,"line":947},[320,9266,1042],{},[320,9268,9269],{"class":322,"line":953},[320,9270,558],{},[320,9272,9273],{"class":322,"line":959},[320,9274,350],{},[320,9276,9277],{"class":322,"line":965},[320,9278,357],{"emptyLinePlaceholder":356},[320,9280,9281],{"class":322,"line":970},[320,9282,9283],{},"\u002F\u002F PendingIntent（跨进程传递 Intent）\n",[320,9285,9286],{"class":322,"line":975},[320,9287,9288],{},"val pendingIntent = PendingIntent.getActivity(\n",[320,9290,9291],{"class":322,"line":980},[320,9292,9293],{},"    context, 0, intent,\n",[320,9295,9296],{"class":322,"line":986},[320,9297,9298],{},"    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE\n",[320,9300,9301],{"class":322,"line":992},[320,9302,6608],{},[10,9304],{},[180,9306,118],{"id":9307},"第三部分flutter-开发-1",[13,9309,9311],{"id":9310},"_15-dart-语言与核心机制","15. Dart 语言与核心机制",[17,9313,9315],{"id":9314},"q151-dart-的事件循环和-isolate","Q15.1: Dart 的事件循环和 Isolate",[201,9317,9318],{},[204,9319,206],{},[311,9321,9325],{"className":9322,"code":9323,"language":9324,"meta":316,"style":316},"language-dart shiki shiki-themes github-light github-dark","\u002F\u002F 事件循环（单线程模型）\n\u002F\u002F 执行顺序：同步代码 → Microtask Queue → Event Queue\n\nvoid main() {\n  print('1');                                    \u002F\u002F 同步\n  Future(() => print('2'));                       \u002F\u002F Event Queue\n  Future.microtask(() => print('3'));             \u002F\u002F Microtask Queue\n  scheduleMicrotask(() => print('4'));            \u002F\u002F Microtask Queue\n  Future(() => print('5'));                       \u002F\u002F Event Queue\n  print('6');                                    \u002F\u002F 同步\n}\n\u002F\u002F 输出：1, 6, 3, 4, 2, 5\n\n\u002F\u002F Isolate：真正的并行（独立内存，消息传递通信）\n\u002F\u002F 适用于 CPU 密集型任务：JSON 解析、图片处理、加密\nfinal result = await Isolate.run(() {\n  return jsonDecode(hugeJsonString); \u002F\u002F 在独立 Isolate 中执行\n});\n\n\u002F\u002F compute() 是 Flutter 封装的便捷方法\nfinal parsed = await compute(parseJson, rawData);\n\n\u002F\u002F 复杂场景：长时间运行的 Isolate + 双向通信\nfinal receivePort = ReceivePort();\nawait Isolate.spawn(heavyWork, receivePort.sendPort);\nreceivePort.listen((message) {\n  if (message is SendPort) {\n    message.send('start');\n  } else {\n    print('Result: $message');\n  }\n});\n","dart",[192,9326,9327,9332,9337,9341,9346,9351,9356,9361,9366,9371,9376,9380,9385,9389,9394,9399,9404,9409,9414,9418,9423,9428,9432,9437,9442,9447,9452,9457,9462,9467,9472,9477],{"__ignoreMap":316},[320,9328,9329],{"class":322,"line":323},[320,9330,9331],{},"\u002F\u002F 事件循环（单线程模型）\n",[320,9333,9334],{"class":322,"line":329},[320,9335,9336],{},"\u002F\u002F 执行顺序：同步代码 → Microtask Queue → Event Queue\n",[320,9338,9339],{"class":322,"line":335},[320,9340,357],{"emptyLinePlaceholder":356},[320,9342,9343],{"class":322,"line":341},[320,9344,9345],{},"void main() {\n",[320,9347,9348],{"class":322,"line":347},[320,9349,9350],{},"  print('1');                                    \u002F\u002F 同步\n",[320,9352,9353],{"class":322,"line":353},[320,9354,9355],{},"  Future(() => print('2'));                       \u002F\u002F Event Queue\n",[320,9357,9358],{"class":322,"line":360},[320,9359,9360],{},"  Future.microtask(() => print('3'));             \u002F\u002F Microtask Queue\n",[320,9362,9363],{"class":322,"line":74},[320,9364,9365],{},"  scheduleMicrotask(() => print('4'));            \u002F\u002F Microtask Queue\n",[320,9367,9368],{"class":322,"line":371},[320,9369,9370],{},"  Future(() => print('5'));                       \u002F\u002F Event Queue\n",[320,9372,9373],{"class":322,"line":377},[320,9374,9375],{},"  print('6');                                    \u002F\u002F 同步\n",[320,9377,9378],{"class":322,"line":383},[320,9379,350],{},[320,9381,9382],{"class":322,"line":388},[320,9383,9384],{},"\u002F\u002F 输出：1, 6, 3, 4, 2, 5\n",[320,9386,9387],{"class":322,"line":394},[320,9388,357],{"emptyLinePlaceholder":356},[320,9390,9391],{"class":322,"line":400},[320,9392,9393],{},"\u002F\u002F Isolate：真正的并行（独立内存，消息传递通信）\n",[320,9395,9396],{"class":322,"line":121},[320,9397,9398],{},"\u002F\u002F 适用于 CPU 密集型任务：JSON 解析、图片处理、加密\n",[320,9400,9401],{"class":322,"line":411},[320,9402,9403],{},"final result = await Isolate.run(() {\n",[320,9405,9406],{"class":322,"line":416},[320,9407,9408],{},"  return jsonDecode(hugeJsonString); \u002F\u002F 在独立 Isolate 中执行\n",[320,9410,9411],{"class":322,"line":421},[320,9412,9413],{},"});\n",[320,9415,9416],{"class":322,"line":427},[320,9417,357],{"emptyLinePlaceholder":356},[320,9419,9420],{"class":322,"line":158},[320,9421,9422],{},"\u002F\u002F compute() 是 Flutter 封装的便捷方法\n",[320,9424,9425],{"class":322,"line":438},[320,9426,9427],{},"final parsed = await compute(parseJson, rawData);\n",[320,9429,9430],{"class":322,"line":615},[320,9431,357],{"emptyLinePlaceholder":356},[320,9433,9434],{"class":322,"line":621},[320,9435,9436],{},"\u002F\u002F 复杂场景：长时间运行的 Isolate + 双向通信\n",[320,9438,9439],{"class":322,"line":627},[320,9440,9441],{},"final receivePort = ReceivePort();\n",[320,9443,9444],{"class":322,"line":935},[320,9445,9446],{},"await Isolate.spawn(heavyWork, receivePort.sendPort);\n",[320,9448,9449],{"class":322,"line":941},[320,9450,9451],{},"receivePort.listen((message) {\n",[320,9453,9454],{"class":322,"line":947},[320,9455,9456],{},"  if (message is SendPort) {\n",[320,9458,9459],{"class":322,"line":953},[320,9460,9461],{},"    message.send('start');\n",[320,9463,9464],{"class":322,"line":959},[320,9465,9466],{},"  } else {\n",[320,9468,9469],{"class":322,"line":965},[320,9470,9471],{},"    print('Result: $message');\n",[320,9473,9474],{"class":322,"line":970},[320,9475,9476],{},"  }\n",[320,9478,9479],{"class":322,"line":975},[320,9480,9413],{},[10,9482],{},[17,9484,9486],{"id":9485},"q152-dart-3-的新特性recordspatternssealed-class","Q15.2: Dart 3 的新特性：Records、Patterns、sealed class",[201,9488,9489],{},[204,9490,206],{},[311,9492,9494],{"className":9322,"code":9493,"language":9324,"meta":316,"style":316},"\u002F\u002F Records（匿名复合类型）\n(String, int) getUserInfo() => ('Alice', 25);\n\nfinal info = getUserInfo();\nprint(info.$1); \u002F\u002F 'Alice'\nprint(info.$2); \u002F\u002F 25\n\n\u002F\u002F 命名字段\n({String name, int age}) getUser() => (name: 'Alice', age: 25);\nfinal user = getUser();\nprint(user.name);\n\n\u002F\u002F Patterns（模式匹配）\n\u002F\u002F switch 表达式\nString describe(Object obj) => switch (obj) {\n  int n when n \u003C 0 => 'negative',\n  int n => 'int: $n',\n  String s => 'string: $s',\n  (int x, int y) => 'point($x, $y)',  \u002F\u002F 解构 Record\n  [int first, ...rest] => 'list starting with $first',\n  {'name': String name} => 'map with name: $name',\n  _ => 'unknown',\n};\n\n\u002F\u002F if-case\nif (json case {'users': [{'name': String name}, ...]}) {\n  print('First user: $name');\n}\n\n\u002F\u002F sealed class + 穷举\nsealed class Shape {}\nclass Circle extends Shape { final double radius; Circle(this.radius); }\nclass Rect extends Shape { final double w, h; Rect(this.w, this.h); }\n\ndouble area(Shape shape) => switch (shape) {\n  Circle(radius: var r) => 3.14 * r * r,\n  Rect(w: var w, h: var h) => w * h,\n};\n\n\u002F\u002F class modifiers（Dart 3）\n\u002F\u002F base class → 只能继承，不能实现\n\u002F\u002F interface class → 只能实现，不能继承\n\u002F\u002F final class → 不能继承也不能实现（同文件除外）\n\u002F\u002F sealed class → 同文件内穷举子类\n",[192,9495,9496,9501,9506,9510,9515,9520,9525,9529,9534,9539,9544,9549,9553,9558,9563,9568,9573,9578,9583,9588,9593,9598,9603,9608,9612,9617,9622,9627,9631,9635,9640,9645,9650,9655,9659,9664,9669,9674,9678,9682,9687,9692,9697,9702],{"__ignoreMap":316},[320,9497,9498],{"class":322,"line":323},[320,9499,9500],{},"\u002F\u002F Records（匿名复合类型）\n",[320,9502,9503],{"class":322,"line":329},[320,9504,9505],{},"(String, int) getUserInfo() => ('Alice', 25);\n",[320,9507,9508],{"class":322,"line":335},[320,9509,357],{"emptyLinePlaceholder":356},[320,9511,9512],{"class":322,"line":341},[320,9513,9514],{},"final info = getUserInfo();\n",[320,9516,9517],{"class":322,"line":347},[320,9518,9519],{},"print(info.$1); \u002F\u002F 'Alice'\n",[320,9521,9522],{"class":322,"line":353},[320,9523,9524],{},"print(info.$2); \u002F\u002F 25\n",[320,9526,9527],{"class":322,"line":360},[320,9528,357],{"emptyLinePlaceholder":356},[320,9530,9531],{"class":322,"line":74},[320,9532,9533],{},"\u002F\u002F 命名字段\n",[320,9535,9536],{"class":322,"line":371},[320,9537,9538],{},"({String name, int age}) getUser() => (name: 'Alice', age: 25);\n",[320,9540,9541],{"class":322,"line":377},[320,9542,9543],{},"final user = getUser();\n",[320,9545,9546],{"class":322,"line":383},[320,9547,9548],{},"print(user.name);\n",[320,9550,9551],{"class":322,"line":388},[320,9552,357],{"emptyLinePlaceholder":356},[320,9554,9555],{"class":322,"line":394},[320,9556,9557],{},"\u002F\u002F Patterns（模式匹配）\n",[320,9559,9560],{"class":322,"line":400},[320,9561,9562],{},"\u002F\u002F switch 表达式\n",[320,9564,9565],{"class":322,"line":121},[320,9566,9567],{},"String describe(Object obj) => switch (obj) {\n",[320,9569,9570],{"class":322,"line":411},[320,9571,9572],{},"  int n when n \u003C 0 => 'negative',\n",[320,9574,9575],{"class":322,"line":416},[320,9576,9577],{},"  int n => 'int: $n',\n",[320,9579,9580],{"class":322,"line":421},[320,9581,9582],{},"  String s => 'string: $s',\n",[320,9584,9585],{"class":322,"line":427},[320,9586,9587],{},"  (int x, int y) => 'point($x, $y)',  \u002F\u002F 解构 Record\n",[320,9589,9590],{"class":322,"line":158},[320,9591,9592],{},"  [int first, ...rest] => 'list starting with $first',\n",[320,9594,9595],{"class":322,"line":438},[320,9596,9597],{},"  {'name': String name} => 'map with name: $name',\n",[320,9599,9600],{"class":322,"line":615},[320,9601,9602],{},"  _ => 'unknown',\n",[320,9604,9605],{"class":322,"line":621},[320,9606,9607],{},"};\n",[320,9609,9610],{"class":322,"line":627},[320,9611,357],{"emptyLinePlaceholder":356},[320,9613,9614],{"class":322,"line":935},[320,9615,9616],{},"\u002F\u002F if-case\n",[320,9618,9619],{"class":322,"line":941},[320,9620,9621],{},"if (json case {'users': [{'name': String name}, ...]}) {\n",[320,9623,9624],{"class":322,"line":947},[320,9625,9626],{},"  print('First user: $name');\n",[320,9628,9629],{"class":322,"line":953},[320,9630,350],{},[320,9632,9633],{"class":322,"line":959},[320,9634,357],{"emptyLinePlaceholder":356},[320,9636,9637],{"class":322,"line":965},[320,9638,9639],{},"\u002F\u002F sealed class + 穷举\n",[320,9641,9642],{"class":322,"line":970},[320,9643,9644],{},"sealed class Shape {}\n",[320,9646,9647],{"class":322,"line":975},[320,9648,9649],{},"class Circle extends Shape { final double radius; Circle(this.radius); }\n",[320,9651,9652],{"class":322,"line":980},[320,9653,9654],{},"class Rect extends Shape { final double w, h; Rect(this.w, this.h); }\n",[320,9656,9657],{"class":322,"line":986},[320,9658,357],{"emptyLinePlaceholder":356},[320,9660,9661],{"class":322,"line":992},[320,9662,9663],{},"double area(Shape shape) => switch (shape) {\n",[320,9665,9666],{"class":322,"line":998},[320,9667,9668],{},"  Circle(radius: var r) => 3.14 * r * r,\n",[320,9670,9671],{"class":322,"line":1003},[320,9672,9673],{},"  Rect(w: var w, h: var h) => w * h,\n",[320,9675,9676],{"class":322,"line":1009},[320,9677,9607],{},[320,9679,9680],{"class":322,"line":1015},[320,9681,357],{"emptyLinePlaceholder":356},[320,9683,9684],{"class":322,"line":1021},[320,9685,9686],{},"\u002F\u002F class modifiers（Dart 3）\n",[320,9688,9689],{"class":322,"line":1027},[320,9690,9691],{},"\u002F\u002F base class → 只能继承，不能实现\n",[320,9693,9694],{"class":322,"line":1033},[320,9695,9696],{},"\u002F\u002F interface class → 只能实现，不能继承\n",[320,9698,9699],{"class":322,"line":1039},[320,9700,9701],{},"\u002F\u002F final class → 不能继承也不能实现（同文件除外）\n",[320,9703,9704],{"class":322,"line":1045},[320,9705,9706],{},"\u002F\u002F sealed class → 同文件内穷举子类\n",[10,9708],{},[13,9710,9712],{"id":9711},"_16-渲染与三棵树","16. 渲染与三棵树",[17,9714,9716],{"id":9715},"q161-widget-element-renderobject-三棵树详解","Q16.1: Widget \u002F Element \u002F RenderObject 三棵树详解",[201,9718,9719],{},[204,9720,206],{},[311,9722,9725],{"className":9723,"code":9724,"language":2187},[2185],"Widget Tree          Element Tree            RenderObject Tree\n(不可变配置)          (可变实例)               (布局+绘制)\n\nScaffold ──────► StatefulElement\n  │                    │\n  ├─ AppBar ────► ComponentElement ──────► RenderFlex\n  │                    │\n  └─ ListView ──► SliverMultiBoxAdaptorElement ──► RenderSliverList\n       │\n       └─ ListTile ──► StatelessElement ──► RenderPadding\n                                               └─ RenderFlex\n",[192,9726,9724],{"__ignoreMap":316},[201,9728,9729],{},[204,9730,9731],{},"核心流程：",[22,9733,9734,9744,9765],{},[25,9735,9736,9739,9740,9743],{},[204,9737,9738],{},"Widget","：不可变的配置描述，每次 ",[192,9741,9742],{},"build()"," 可能创建新实例",[25,9745,9746,9749,9750],{},[204,9747,9748],{},"Element","：Widget 的实例化，管理生命周期，决定是否复用\n",[448,9751,9752],{},[25,9753,9754,9757,9758,2886,9761,9764],{},[192,9755,9756],{},"canUpdate(oldWidget, newWidget)","：",[192,9759,9760],{},"runtimeType",[192,9762,9763],{},"key"," 都相同则复用",[25,9766,9767,9770,9771,9774,9775,9778],{},[204,9768,9769],{},"RenderObject","：真正执行布局（",[192,9772,9773],{},"performLayout","）和绘制（",[192,9776,9777],{},"paint","）",[201,9780,9781],{},[204,9782,9783],{},"约束传递规则：",[311,9785,9788],{"className":9786,"code":9787,"language":2187},[2185],"Constraints go down（父传子约束）\nSizes go up（子回传尺寸）\nParent sets position（父设子位置）\n",[192,9789,9787],{"__ignoreMap":316},[311,9791,9793],{"className":9322,"code":9792,"language":9324,"meta":316,"style":316},"\u002F\u002F 理解约束的典型问题\nSizedBox(\n  width: 100,\n  height: 100,\n  child: Container(\n    width: 200,  \u002F\u002F 无效！父约束 tight=100，子不能大于 100\n    height: 200,\n    color: Colors.red,\n  ),\n)\n\u002F\u002F 实际渲染为 100x100\n\n\u002F\u002F ConstrainedBox 传递约束\nConstrainedBox(\n  constraints: BoxConstraints(maxWidth: 200),\n  child: Container(width: 300),  \u002F\u002F 实际宽度被限制为 200\n)\n",[192,9794,9795,9800,9805,9810,9815,9820,9825,9830,9835,9840,9844,9849,9853,9858,9863,9868,9873],{"__ignoreMap":316},[320,9796,9797],{"class":322,"line":323},[320,9798,9799],{},"\u002F\u002F 理解约束的典型问题\n",[320,9801,9802],{"class":322,"line":329},[320,9803,9804],{},"SizedBox(\n",[320,9806,9807],{"class":322,"line":335},[320,9808,9809],{},"  width: 100,\n",[320,9811,9812],{"class":322,"line":341},[320,9813,9814],{},"  height: 100,\n",[320,9816,9817],{"class":322,"line":347},[320,9818,9819],{},"  child: Container(\n",[320,9821,9822],{"class":322,"line":353},[320,9823,9824],{},"    width: 200,  \u002F\u002F 无效！父约束 tight=100，子不能大于 100\n",[320,9826,9827],{"class":322,"line":360},[320,9828,9829],{},"    height: 200,\n",[320,9831,9832],{"class":322,"line":74},[320,9833,9834],{},"    color: Colors.red,\n",[320,9836,9837],{"class":322,"line":371},[320,9838,9839],{},"  ),\n",[320,9841,9842],{"class":322,"line":377},[320,9843,6608],{},[320,9845,9846],{"class":322,"line":383},[320,9847,9848],{},"\u002F\u002F 实际渲染为 100x100\n",[320,9850,9851],{"class":322,"line":388},[320,9852,357],{"emptyLinePlaceholder":356},[320,9854,9855],{"class":322,"line":394},[320,9856,9857],{},"\u002F\u002F ConstrainedBox 传递约束\n",[320,9859,9860],{"class":322,"line":400},[320,9861,9862],{},"ConstrainedBox(\n",[320,9864,9865],{"class":322,"line":121},[320,9866,9867],{},"  constraints: BoxConstraints(maxWidth: 200),\n",[320,9869,9870],{"class":322,"line":411},[320,9871,9872],{},"  child: Container(width: 300),  \u002F\u002F 实际宽度被限制为 200\n",[320,9874,9875],{"class":322,"line":416},[320,9876,6608],{},[10,9878],{},[13,9880,9882],{"id":9881},"_17-状态管理","17. 状态管理",[17,9884,9886],{"id":9885},"q171-bloc-vs-riverpod-深度对比","Q17.1: BLoC vs Riverpod 深度对比",[201,9888,9889],{},[204,9890,206],{},[201,9892,9893],{},[204,9894,9895],{},"BLoC（Business Logic Component）：",[311,9897,9899],{"className":9322,"code":9898,"language":9324,"meta":316,"style":316},"\u002F\u002F 事件驱动、流式响应\n\u002F\u002F Event → BLoC → State\n\nsealed class AuthEvent {}\nclass LoginRequested extends AuthEvent {\n  final String email, password;\n  LoginRequested(this.email, this.password);\n}\nclass LogoutRequested extends AuthEvent {}\n\nsealed class AuthState {}\nclass AuthInitial extends AuthState {}\nclass AuthLoading extends AuthState {}\nclass AuthSuccess extends AuthState { final User user; AuthSuccess(this.user); }\nclass AuthFailure extends AuthState { final String message; AuthFailure(this.message); }\n\nclass AuthBloc extends Bloc\u003CAuthEvent, AuthState> {\n  final AuthRepository _repo;\n\n  AuthBloc(this._repo) : super(AuthInitial()) {\n    on\u003CLoginRequested>(_onLogin);\n    on\u003CLogoutRequested>(_onLogout);\n  }\n\n  Future\u003Cvoid> _onLogin(LoginRequested event, Emitter\u003CAuthState> emit) async {\n    emit(AuthLoading());\n    try {\n      final user = await _repo.login(event.email, event.password);\n      emit(AuthSuccess(user));\n    } catch (e) {\n      emit(AuthFailure(e.toString()));\n    }\n  }\n\n  Future\u003Cvoid> _onLogout(LogoutRequested event, Emitter\u003CAuthState> emit) async {\n    await _repo.logout();\n    emit(AuthInitial());\n  }\n}\n\n\u002F\u002F UI\nBlocBuilder\u003CAuthBloc, AuthState>(\n  builder: (context, state) => switch (state) {\n    AuthInitial() => LoginForm(),\n    AuthLoading() => CircularProgressIndicator(),\n    AuthSuccess(user: final u) => HomePage(user: u),\n    AuthFailure(message: final m) => ErrorView(message: m),\n  },\n)\n",[192,9900,9901,9906,9911,9915,9920,9925,9930,9935,9939,9944,9948,9953,9958,9963,9968,9973,9977,9982,9987,9991,9996,10001,10006,10010,10014,10019,10024,10028,10033,10038,10043,10048,10052,10056,10060,10065,10070,10075,10079,10083,10087,10092,10097,10102,10107,10112,10117,10122,10127],{"__ignoreMap":316},[320,9902,9903],{"class":322,"line":323},[320,9904,9905],{},"\u002F\u002F 事件驱动、流式响应\n",[320,9907,9908],{"class":322,"line":329},[320,9909,9910],{},"\u002F\u002F Event → BLoC → State\n",[320,9912,9913],{"class":322,"line":335},[320,9914,357],{"emptyLinePlaceholder":356},[320,9916,9917],{"class":322,"line":341},[320,9918,9919],{},"sealed class AuthEvent {}\n",[320,9921,9922],{"class":322,"line":347},[320,9923,9924],{},"class LoginRequested extends AuthEvent {\n",[320,9926,9927],{"class":322,"line":353},[320,9928,9929],{},"  final String email, password;\n",[320,9931,9932],{"class":322,"line":360},[320,9933,9934],{},"  LoginRequested(this.email, this.password);\n",[320,9936,9937],{"class":322,"line":74},[320,9938,350],{},[320,9940,9941],{"class":322,"line":371},[320,9942,9943],{},"class LogoutRequested extends AuthEvent {}\n",[320,9945,9946],{"class":322,"line":377},[320,9947,357],{"emptyLinePlaceholder":356},[320,9949,9950],{"class":322,"line":383},[320,9951,9952],{},"sealed class AuthState {}\n",[320,9954,9955],{"class":322,"line":388},[320,9956,9957],{},"class AuthInitial extends AuthState {}\n",[320,9959,9960],{"class":322,"line":394},[320,9961,9962],{},"class AuthLoading extends AuthState {}\n",[320,9964,9965],{"class":322,"line":400},[320,9966,9967],{},"class AuthSuccess extends AuthState { final User user; AuthSuccess(this.user); }\n",[320,9969,9970],{"class":322,"line":121},[320,9971,9972],{},"class AuthFailure extends AuthState { final String message; AuthFailure(this.message); }\n",[320,9974,9975],{"class":322,"line":411},[320,9976,357],{"emptyLinePlaceholder":356},[320,9978,9979],{"class":322,"line":416},[320,9980,9981],{},"class AuthBloc extends Bloc\u003CAuthEvent, AuthState> {\n",[320,9983,9984],{"class":322,"line":421},[320,9985,9986],{},"  final AuthRepository _repo;\n",[320,9988,9989],{"class":322,"line":427},[320,9990,357],{"emptyLinePlaceholder":356},[320,9992,9993],{"class":322,"line":158},[320,9994,9995],{},"  AuthBloc(this._repo) : super(AuthInitial()) {\n",[320,9997,9998],{"class":322,"line":438},[320,9999,10000],{},"    on\u003CLoginRequested>(_onLogin);\n",[320,10002,10003],{"class":322,"line":615},[320,10004,10005],{},"    on\u003CLogoutRequested>(_onLogout);\n",[320,10007,10008],{"class":322,"line":621},[320,10009,9476],{},[320,10011,10012],{"class":322,"line":627},[320,10013,357],{"emptyLinePlaceholder":356},[320,10015,10016],{"class":322,"line":935},[320,10017,10018],{},"  Future\u003Cvoid> _onLogin(LoginRequested event, Emitter\u003CAuthState> emit) async {\n",[320,10020,10021],{"class":322,"line":941},[320,10022,10023],{},"    emit(AuthLoading());\n",[320,10025,10026],{"class":322,"line":947},[320,10027,7771],{},[320,10029,10030],{"class":322,"line":953},[320,10031,10032],{},"      final user = await _repo.login(event.email, event.password);\n",[320,10034,10035],{"class":322,"line":959},[320,10036,10037],{},"      emit(AuthSuccess(user));\n",[320,10039,10040],{"class":322,"line":965},[320,10041,10042],{},"    } catch (e) {\n",[320,10044,10045],{"class":322,"line":970},[320,10046,10047],{},"      emit(AuthFailure(e.toString()));\n",[320,10049,10050],{"class":322,"line":975},[320,10051,558],{},[320,10053,10054],{"class":322,"line":980},[320,10055,9476],{},[320,10057,10058],{"class":322,"line":986},[320,10059,357],{"emptyLinePlaceholder":356},[320,10061,10062],{"class":322,"line":992},[320,10063,10064],{},"  Future\u003Cvoid> _onLogout(LogoutRequested event, Emitter\u003CAuthState> emit) async {\n",[320,10066,10067],{"class":322,"line":998},[320,10068,10069],{},"    await _repo.logout();\n",[320,10071,10072],{"class":322,"line":1003},[320,10073,10074],{},"    emit(AuthInitial());\n",[320,10076,10077],{"class":322,"line":1009},[320,10078,9476],{},[320,10080,10081],{"class":322,"line":1015},[320,10082,350],{},[320,10084,10085],{"class":322,"line":1021},[320,10086,357],{"emptyLinePlaceholder":356},[320,10088,10089],{"class":322,"line":1027},[320,10090,10091],{},"\u002F\u002F UI\n",[320,10093,10094],{"class":322,"line":1033},[320,10095,10096],{},"BlocBuilder\u003CAuthBloc, AuthState>(\n",[320,10098,10099],{"class":322,"line":1039},[320,10100,10101],{},"  builder: (context, state) => switch (state) {\n",[320,10103,10104],{"class":322,"line":1045},[320,10105,10106],{},"    AuthInitial() => LoginForm(),\n",[320,10108,10109],{"class":322,"line":1050},[320,10110,10111],{},"    AuthLoading() => CircularProgressIndicator(),\n",[320,10113,10114],{"class":322,"line":1055},[320,10115,10116],{},"    AuthSuccess(user: final u) => HomePage(user: u),\n",[320,10118,10119],{"class":322,"line":1060},[320,10120,10121],{},"    AuthFailure(message: final m) => ErrorView(message: m),\n",[320,10123,10124],{"class":322,"line":1066},[320,10125,10126],{},"  },\n",[320,10128,10129],{"class":322,"line":1072},[320,10130,6608],{},[201,10132,10133],{},[204,10134,10135],{},"Riverpod：",[311,10137,10139],{"className":9322,"code":10138,"language":9324,"meta":316,"style":316},"\u002F\u002F 编译安全、无需 context、自动依赖管理\nfinal authRepositoryProvider = Provider((ref) => AuthRepository());\n\nfinal authStateProvider = StateNotifierProvider\u003CAuthNotifier, AuthState>((ref) {\n  return AuthNotifier(ref.read(authRepositoryProvider));\n});\n\nclass AuthNotifier extends StateNotifier\u003CAuthState> {\n  final AuthRepository _repo;\n  AuthNotifier(this._repo) : super(AuthInitial());\n\n  Future\u003Cvoid> login(String email, String password) async {\n    state = AuthLoading();\n    try {\n      final user = await _repo.login(email, password);\n      state = AuthSuccess(user);\n    } catch (e) {\n      state = AuthFailure(e.toString());\n    }\n  }\n}\n\n\u002F\u002F UI\nclass LoginPage extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final authState = ref.watch(authStateProvider);\n    return switch (authState) {\n      AuthInitial() => LoginForm(),\n      AuthLoading() => CircularProgressIndicator(),\n      AuthSuccess(user: final u) => HomePage(user: u),\n      AuthFailure(message: final m) => ErrorView(message: m),\n    };\n  }\n}\n",[192,10140,10141,10146,10151,10155,10160,10165,10169,10173,10178,10182,10187,10191,10196,10201,10205,10210,10215,10219,10224,10228,10232,10236,10240,10244,10249,10254,10259,10264,10269,10274,10279,10284,10289,10294,10298],{"__ignoreMap":316},[320,10142,10143],{"class":322,"line":323},[320,10144,10145],{},"\u002F\u002F 编译安全、无需 context、自动依赖管理\n",[320,10147,10148],{"class":322,"line":329},[320,10149,10150],{},"final authRepositoryProvider = Provider((ref) => AuthRepository());\n",[320,10152,10153],{"class":322,"line":335},[320,10154,357],{"emptyLinePlaceholder":356},[320,10156,10157],{"class":322,"line":341},[320,10158,10159],{},"final authStateProvider = StateNotifierProvider\u003CAuthNotifier, AuthState>((ref) {\n",[320,10161,10162],{"class":322,"line":347},[320,10163,10164],{},"  return AuthNotifier(ref.read(authRepositoryProvider));\n",[320,10166,10167],{"class":322,"line":353},[320,10168,9413],{},[320,10170,10171],{"class":322,"line":360},[320,10172,357],{"emptyLinePlaceholder":356},[320,10174,10175],{"class":322,"line":74},[320,10176,10177],{},"class AuthNotifier extends StateNotifier\u003CAuthState> {\n",[320,10179,10180],{"class":322,"line":371},[320,10181,9986],{},[320,10183,10184],{"class":322,"line":377},[320,10185,10186],{},"  AuthNotifier(this._repo) : super(AuthInitial());\n",[320,10188,10189],{"class":322,"line":383},[320,10190,357],{"emptyLinePlaceholder":356},[320,10192,10193],{"class":322,"line":388},[320,10194,10195],{},"  Future\u003Cvoid> login(String email, String password) async {\n",[320,10197,10198],{"class":322,"line":394},[320,10199,10200],{},"    state = AuthLoading();\n",[320,10202,10203],{"class":322,"line":400},[320,10204,7771],{},[320,10206,10207],{"class":322,"line":121},[320,10208,10209],{},"      final user = await _repo.login(email, password);\n",[320,10211,10212],{"class":322,"line":411},[320,10213,10214],{},"      state = AuthSuccess(user);\n",[320,10216,10217],{"class":322,"line":416},[320,10218,10042],{},[320,10220,10221],{"class":322,"line":421},[320,10222,10223],{},"      state = AuthFailure(e.toString());\n",[320,10225,10226],{"class":322,"line":427},[320,10227,558],{},[320,10229,10230],{"class":322,"line":158},[320,10231,9476],{},[320,10233,10234],{"class":322,"line":438},[320,10235,350],{},[320,10237,10238],{"class":322,"line":615},[320,10239,357],{"emptyLinePlaceholder":356},[320,10241,10242],{"class":322,"line":621},[320,10243,10091],{},[320,10245,10246],{"class":322,"line":627},[320,10247,10248],{},"class LoginPage extends ConsumerWidget {\n",[320,10250,10251],{"class":322,"line":935},[320,10252,10253],{},"  @override\n",[320,10255,10256],{"class":322,"line":941},[320,10257,10258],{},"  Widget build(BuildContext context, WidgetRef ref) {\n",[320,10260,10261],{"class":322,"line":947},[320,10262,10263],{},"    final authState = ref.watch(authStateProvider);\n",[320,10265,10266],{"class":322,"line":953},[320,10267,10268],{},"    return switch (authState) {\n",[320,10270,10271],{"class":322,"line":959},[320,10272,10273],{},"      AuthInitial() => LoginForm(),\n",[320,10275,10276],{"class":322,"line":965},[320,10277,10278],{},"      AuthLoading() => CircularProgressIndicator(),\n",[320,10280,10281],{"class":322,"line":970},[320,10282,10283],{},"      AuthSuccess(user: final u) => HomePage(user: u),\n",[320,10285,10286],{"class":322,"line":975},[320,10287,10288],{},"      AuthFailure(message: final m) => ErrorView(message: m),\n",[320,10290,10291],{"class":322,"line":980},[320,10292,10293],{},"    };\n",[320,10295,10296],{"class":322,"line":986},[320,10297,9476],{},[320,10299,10300],{"class":322,"line":992},[320,10301,350],{},[208,10303,10304,10316],{},[211,10305,10306],{},[214,10307,10308,10310,10313],{},[217,10309],{},[217,10311,10312],{},"BLoC",[217,10314,10315],{},"Riverpod",[227,10317,10318,10328,10337,10348,10362,10373],{},[214,10319,10320,10322,10325],{},[232,10321,2248],{},[232,10323,10324],{},"事件 → BLoC → 状态（严格单向）",[232,10326,10327],{},"函数式、Provider 依赖图",[214,10329,10330,10333,10335],{},[232,10331,10332],{},"学习曲线",[232,10334,4088],{},[232,10336,4056],{},[214,10338,10339,10342,10345],{},[232,10340,10341],{},"模板代码",[232,10343,10344],{},"较多（Event + State + BLoC）",[232,10346,10347],{},"较少",[214,10349,10350,10353,10359],{},[232,10351,10352],{},"测试",[232,10354,10355,10358],{},[192,10356,10357],{},"blocTest()","，非常规范",[232,10360,10361],{},"Provider override，灵活",[214,10363,10364,10367,10370],{},[232,10365,10366],{},"context 依赖",[232,10368,10369],{},"需要（BlocProvider）",[232,10371,10372],{},"不需要（编译安全）",[214,10374,10375,10378,10381],{},[232,10376,10377],{},"适用团队",[232,10379,10380],{},"大型团队（规范强制）",[232,10382,10383],{},"中小型团队（灵活高效）",[10,10385],{},[13,10387,10389],{"id":10388},"_18-平台通信与混合开发","18. 平台通信与混合开发",[17,10391,10393],{"id":10392},"q181-platform-channel-和-add-to-app-方案","Q18.1: Platform Channel 和 Add-to-App 方案",[201,10395,10396],{},[204,10397,206],{},[311,10399,10401],{"className":9322,"code":10400,"language":9324,"meta":316,"style":316},"\u002F\u002F MethodChannel（最常用）\nclass NativeService {\n  static const _channel = MethodChannel('com.app\u002Fnative');\n\n  Future\u003CString> getPlatformVersion() async {\n    return await _channel.invokeMethod('getPlatformVersion');\n  }\n\n  \u002F\u002F 原生调用 Dart\n  void setupCallHandler() {\n    _channel.setMethodCallHandler((call) async {\n      switch (call.method) {\n        case 'onNativeEvent':\n          handleNativeEvent(call.arguments);\n          return 'handled';\n        default:\n          throw MissingPluginException();\n      }\n    });\n  }\n}\n",[192,10402,10403,10408,10413,10418,10422,10427,10432,10436,10440,10445,10450,10455,10460,10465,10470,10475,10480,10485,10490,10495,10499],{"__ignoreMap":316},[320,10404,10405],{"class":322,"line":323},[320,10406,10407],{},"\u002F\u002F MethodChannel（最常用）\n",[320,10409,10410],{"class":322,"line":329},[320,10411,10412],{},"class NativeService {\n",[320,10414,10415],{"class":322,"line":335},[320,10416,10417],{},"  static const _channel = MethodChannel('com.app\u002Fnative');\n",[320,10419,10420],{"class":322,"line":341},[320,10421,357],{"emptyLinePlaceholder":356},[320,10423,10424],{"class":322,"line":347},[320,10425,10426],{},"  Future\u003CString> getPlatformVersion() async {\n",[320,10428,10429],{"class":322,"line":353},[320,10430,10431],{},"    return await _channel.invokeMethod('getPlatformVersion');\n",[320,10433,10434],{"class":322,"line":360},[320,10435,9476],{},[320,10437,10438],{"class":322,"line":74},[320,10439,357],{"emptyLinePlaceholder":356},[320,10441,10442],{"class":322,"line":371},[320,10443,10444],{},"  \u002F\u002F 原生调用 Dart\n",[320,10446,10447],{"class":322,"line":377},[320,10448,10449],{},"  void setupCallHandler() {\n",[320,10451,10452],{"class":322,"line":383},[320,10453,10454],{},"    _channel.setMethodCallHandler((call) async {\n",[320,10456,10457],{"class":322,"line":388},[320,10458,10459],{},"      switch (call.method) {\n",[320,10461,10462],{"class":322,"line":394},[320,10463,10464],{},"        case 'onNativeEvent':\n",[320,10466,10467],{"class":322,"line":400},[320,10468,10469],{},"          handleNativeEvent(call.arguments);\n",[320,10471,10472],{"class":322,"line":121},[320,10473,10474],{},"          return 'handled';\n",[320,10476,10477],{"class":322,"line":411},[320,10478,10479],{},"        default:\n",[320,10481,10482],{"class":322,"line":416},[320,10483,10484],{},"          throw MissingPluginException();\n",[320,10486,10487],{"class":322,"line":421},[320,10488,10489],{},"      }\n",[320,10491,10492],{"class":322,"line":427},[320,10493,10494],{},"    });\n",[320,10496,10497],{"class":322,"line":158},[320,10498,9476],{},[320,10500,10501],{"class":322,"line":438},[320,10502,350],{},[311,10504,10506],{"className":313,"code":10505,"language":315,"meta":316,"style":316},"\u002F\u002F iOS 端（Swift）\nlet channel = FlutterMethodChannel(name: \"com.app\u002Fnative\",\n                                    binaryMessenger: controller.binaryMessenger)\nchannel.setMethodCallHandler { (call, result) in\n    switch call.method {\n    case \"getPlatformVersion\":\n        result(\"iOS \\(UIDevice.current.systemVersion)\")\n    default:\n        result(FlutterMethodNotImplemented)\n    }\n}\n",[192,10507,10508,10513,10518,10523,10528,10533,10538,10543,10548,10553,10557],{"__ignoreMap":316},[320,10509,10510],{"class":322,"line":323},[320,10511,10512],{},"\u002F\u002F iOS 端（Swift）\n",[320,10514,10515],{"class":322,"line":329},[320,10516,10517],{},"let channel = FlutterMethodChannel(name: \"com.app\u002Fnative\",\n",[320,10519,10520],{"class":322,"line":335},[320,10521,10522],{},"                                    binaryMessenger: controller.binaryMessenger)\n",[320,10524,10525],{"class":322,"line":341},[320,10526,10527],{},"channel.setMethodCallHandler { (call, result) in\n",[320,10529,10530],{"class":322,"line":347},[320,10531,10532],{},"    switch call.method {\n",[320,10534,10535],{"class":322,"line":353},[320,10536,10537],{},"    case \"getPlatformVersion\":\n",[320,10539,10540],{"class":322,"line":360},[320,10541,10542],{},"        result(\"iOS \\(UIDevice.current.systemVersion)\")\n",[320,10544,10545],{"class":322,"line":74},[320,10546,10547],{},"    default:\n",[320,10549,10550],{"class":322,"line":371},[320,10551,10552],{},"        result(FlutterMethodNotImplemented)\n",[320,10554,10555],{"class":322,"line":377},[320,10556,558],{},[320,10558,10559],{"class":322,"line":383},[320,10560,350],{},[311,10562,10564],{"className":4996,"code":10563,"language":4998,"meta":316,"style":316},"\u002F\u002F Android 端（Kotlin）\nMethodChannel(flutterEngine.dartExecutor.binaryMessenger, \"com.app\u002Fnative\")\n    .setMethodCallHandler { call, result ->\n        when (call.method) {\n            \"getPlatformVersion\" -> result.success(\"Android ${Build.VERSION.RELEASE}\")\n            else -> result.notImplemented()\n        }\n    }\n",[192,10565,10566,10571,10576,10581,10586,10591,10596,10600],{"__ignoreMap":316},[320,10567,10568],{"class":322,"line":323},[320,10569,10570],{},"\u002F\u002F Android 端（Kotlin）\n",[320,10572,10573],{"class":322,"line":329},[320,10574,10575],{},"MethodChannel(flutterEngine.dartExecutor.binaryMessenger, \"com.app\u002Fnative\")\n",[320,10577,10578],{"class":322,"line":335},[320,10579,10580],{},"    .setMethodCallHandler { call, result ->\n",[320,10582,10583],{"class":322,"line":341},[320,10584,10585],{},"        when (call.method) {\n",[320,10587,10588],{"class":322,"line":347},[320,10589,10590],{},"            \"getPlatformVersion\" -> result.success(\"Android ${Build.VERSION.RELEASE}\")\n",[320,10592,10593],{"class":322,"line":353},[320,10594,10595],{},"            else -> result.notImplemented()\n",[320,10597,10598],{"class":322,"line":360},[320,10599,1042],{},[320,10601,10602],{"class":322,"line":74},[320,10603,558],{},[201,10605,10606],{},[204,10607,10608],{},"Pigeon（类型安全的代码生成替代方案）：",[311,10610,10612],{"className":9322,"code":10611,"language":9324,"meta":316,"style":316},"\u002F\u002F 定义接口（pigeon 文件）\n@HostApi()\nabstract class NativeApi {\n  String getPlatformVersion();\n  @async\n  UserInfo getUserInfo(String userId);\n}\n\n\u002F\u002F 自动生成 Dart\u002FSwift\u002FKotlin 代码，类型安全无需手动序列化\n",[192,10613,10614,10619,10624,10629,10634,10639,10644,10648,10652],{"__ignoreMap":316},[320,10615,10616],{"class":322,"line":323},[320,10617,10618],{},"\u002F\u002F 定义接口（pigeon 文件）\n",[320,10620,10621],{"class":322,"line":329},[320,10622,10623],{},"@HostApi()\n",[320,10625,10626],{"class":322,"line":335},[320,10627,10628],{},"abstract class NativeApi {\n",[320,10630,10631],{"class":322,"line":341},[320,10632,10633],{},"  String getPlatformVersion();\n",[320,10635,10636],{"class":322,"line":347},[320,10637,10638],{},"  @async\n",[320,10640,10641],{"class":322,"line":353},[320,10642,10643],{},"  UserInfo getUserInfo(String userId);\n",[320,10645,10646],{"class":322,"line":360},[320,10647,350],{},[320,10649,10650],{"class":322,"line":74},[320,10651,357],{"emptyLinePlaceholder":356},[320,10653,10654],{"class":322,"line":371},[320,10655,10656],{},"\u002F\u002F 自动生成 Dart\u002FSwift\u002FKotlin 代码，类型安全无需手动序列化\n",[201,10658,10659],{},[204,10660,10661],{},"Add-to-App（在现有原生应用中嵌入 Flutter）：",[311,10663,10665],{"className":313,"code":10664,"language":315,"meta":316,"style":316},"\u002F\u002F iOS：将 Flutter 模块作为 Framework 引入\nlet flutterEngine = FlutterEngine(name: \"my_engine\")\nflutterEngine.run()\n\nlet flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)\npresent(flutterVC, animated: true)\n",[192,10666,10667,10672,10677,10682,10686,10691],{"__ignoreMap":316},[320,10668,10669],{"class":322,"line":323},[320,10670,10671],{},"\u002F\u002F iOS：将 Flutter 模块作为 Framework 引入\n",[320,10673,10674],{"class":322,"line":329},[320,10675,10676],{},"let flutterEngine = FlutterEngine(name: \"my_engine\")\n",[320,10678,10679],{"class":322,"line":335},[320,10680,10681],{},"flutterEngine.run()\n",[320,10683,10684],{"class":322,"line":341},[320,10685,357],{"emptyLinePlaceholder":356},[320,10687,10688],{"class":322,"line":347},[320,10689,10690],{},"let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)\n",[320,10692,10693],{"class":322,"line":353},[320,10694,10695],{},"present(flutterVC, animated: true)\n",[311,10697,10699],{"className":4996,"code":10698,"language":4998,"meta":316,"style":316},"\u002F\u002F Android：FlutterFragment 或 FlutterActivity\nval flutterFragment = FlutterFragment\n    .withCachedEngine(\"my_engine\")\n    .build\u003CFlutterFragment>()\n\nsupportFragmentManager.beginTransaction()\n    .replace(R.id.container, flutterFragment)\n    .commit()\n",[192,10700,10701,10706,10711,10716,10721,10725,10730,10735],{"__ignoreMap":316},[320,10702,10703],{"class":322,"line":323},[320,10704,10705],{},"\u002F\u002F Android：FlutterFragment 或 FlutterActivity\n",[320,10707,10708],{"class":322,"line":329},[320,10709,10710],{},"val flutterFragment = FlutterFragment\n",[320,10712,10713],{"class":322,"line":335},[320,10714,10715],{},"    .withCachedEngine(\"my_engine\")\n",[320,10717,10718],{"class":322,"line":341},[320,10719,10720],{},"    .build\u003CFlutterFragment>()\n",[320,10722,10723],{"class":322,"line":347},[320,10724,357],{"emptyLinePlaceholder":356},[320,10726,10727],{"class":322,"line":353},[320,10728,10729],{},"supportFragmentManager.beginTransaction()\n",[320,10731,10732],{"class":322,"line":360},[320,10733,10734],{},"    .replace(R.id.container, flutterFragment)\n",[320,10736,10737],{"class":322,"line":74},[320,10738,10739],{},"    .commit()\n",[10,10741],{},[13,10743,10745],{"id":10744},"_19-性能优化flutter","19. 性能优化（Flutter）",[17,10747,10749],{"id":10748},"q191-flutter-性能优化全面指南","Q19.1: Flutter 性能优化全面指南",[201,10751,10752],{},[204,10753,206],{},[311,10755,10757],{"className":9322,"code":10756,"language":9324,"meta":316,"style":316},"\u002F\u002F 1. 减少 rebuild 范围\n\u002F\u002F ❌ 整个页面因一个计数器重建\nclass _PageState extends State\u003CPage> {\n  int count = 0;\n  Widget build(context) {\n    return Column(children: [\n      ExpensiveHeader(),    \u002F\u002F 不需要重建\n      Text('$count'),       \u002F\u002F 需要重建\n      ExpensiveFooter(),    \u002F\u002F 不需要重建\n    ]);\n  }\n}\n\n\u002F\u002F ✅ 拆分为独立 Widget\nclass CounterText extends StatefulWidget { ... }\n\u002F\u002F 或使用 ValueListenableBuilder\nValueListenableBuilder\u003Cint>(\n  valueListenable: counter,\n  builder: (context, value, child) => Text('$value'),\n)\n\n\u002F\u002F 2. const 构造函数\nconst SizedBox(height: 16)  \u002F\u002F 编译期创建，可复用\nconst EdgeInsets.all(8)\nconst TextStyle(fontSize: 14)\n\n\u002F\u002F 3. ListView 优化\nListView.builder(\n  itemCount: 10000,\n  itemExtent: 72,          \u002F\u002F 固定高度，跳过布局计算\n  itemBuilder: (ctx, i) => ItemTile(items[i]),\n)\n\n\u002F\u002F 4. 图片优化\nImage.asset('photo.png', cacheWidth: 200)  \u002F\u002F 降采样\nCachedNetworkImage(imageUrl: url)          \u002F\u002F 缓存网络图片\n\n\u002F\u002F 5. 动画性能\nAnimatedBuilder(\n  animation: controller,\n  child: const ExpensiveChild(),  \u002F\u002F 缓存不变的子树\n  builder: (ctx, child) => Transform.scale(\n    scale: controller.value,\n    child: child,  \u002F\u002F 复用\n  ),\n)\n\n\u002F\u002F 6. Shader 预热（Impeller 已大幅缓解）\n\u002F\u002F 收集 SkSL → 编译时打包\n\u002F\u002F flutter run --profile --cache-sksl --purge-persistent-cache\n\u002F\u002F flutter build apk --bundle-sksl-path flutter_01.sksl.json\n\n\u002F\u002F 7. 使用 DevTools 分析\n\u002F\u002F flutter run --profile\n\u002F\u002F 打开 DevTools → Performance → 分析帧耗时\n",[192,10758,10759,10764,10769,10774,10779,10784,10789,10794,10799,10804,10809,10813,10817,10821,10826,10831,10836,10841,10846,10851,10855,10859,10864,10869,10874,10879,10883,10888,10893,10898,10903,10908,10912,10916,10921,10926,10931,10935,10940,10945,10950,10955,10960,10965,10970,10974,10978,10982,10987,10992,10997,11002,11006,11011,11016],{"__ignoreMap":316},[320,10760,10761],{"class":322,"line":323},[320,10762,10763],{},"\u002F\u002F 1. 减少 rebuild 范围\n",[320,10765,10766],{"class":322,"line":329},[320,10767,10768],{},"\u002F\u002F ❌ 整个页面因一个计数器重建\n",[320,10770,10771],{"class":322,"line":335},[320,10772,10773],{},"class _PageState extends State\u003CPage> {\n",[320,10775,10776],{"class":322,"line":341},[320,10777,10778],{},"  int count = 0;\n",[320,10780,10781],{"class":322,"line":347},[320,10782,10783],{},"  Widget build(context) {\n",[320,10785,10786],{"class":322,"line":353},[320,10787,10788],{},"    return Column(children: [\n",[320,10790,10791],{"class":322,"line":360},[320,10792,10793],{},"      ExpensiveHeader(),    \u002F\u002F 不需要重建\n",[320,10795,10796],{"class":322,"line":74},[320,10797,10798],{},"      Text('$count'),       \u002F\u002F 需要重建\n",[320,10800,10801],{"class":322,"line":371},[320,10802,10803],{},"      ExpensiveFooter(),    \u002F\u002F 不需要重建\n",[320,10805,10806],{"class":322,"line":377},[320,10807,10808],{},"    ]);\n",[320,10810,10811],{"class":322,"line":383},[320,10812,9476],{},[320,10814,10815],{"class":322,"line":388},[320,10816,350],{},[320,10818,10819],{"class":322,"line":394},[320,10820,357],{"emptyLinePlaceholder":356},[320,10822,10823],{"class":322,"line":400},[320,10824,10825],{},"\u002F\u002F ✅ 拆分为独立 Widget\n",[320,10827,10828],{"class":322,"line":121},[320,10829,10830],{},"class CounterText extends StatefulWidget { ... }\n",[320,10832,10833],{"class":322,"line":411},[320,10834,10835],{},"\u002F\u002F 或使用 ValueListenableBuilder\n",[320,10837,10838],{"class":322,"line":416},[320,10839,10840],{},"ValueListenableBuilder\u003Cint>(\n",[320,10842,10843],{"class":322,"line":421},[320,10844,10845],{},"  valueListenable: counter,\n",[320,10847,10848],{"class":322,"line":427},[320,10849,10850],{},"  builder: (context, value, child) => Text('$value'),\n",[320,10852,10853],{"class":322,"line":158},[320,10854,6608],{},[320,10856,10857],{"class":322,"line":438},[320,10858,357],{"emptyLinePlaceholder":356},[320,10860,10861],{"class":322,"line":615},[320,10862,10863],{},"\u002F\u002F 2. const 构造函数\n",[320,10865,10866],{"class":322,"line":621},[320,10867,10868],{},"const SizedBox(height: 16)  \u002F\u002F 编译期创建，可复用\n",[320,10870,10871],{"class":322,"line":627},[320,10872,10873],{},"const EdgeInsets.all(8)\n",[320,10875,10876],{"class":322,"line":935},[320,10877,10878],{},"const TextStyle(fontSize: 14)\n",[320,10880,10881],{"class":322,"line":941},[320,10882,357],{"emptyLinePlaceholder":356},[320,10884,10885],{"class":322,"line":947},[320,10886,10887],{},"\u002F\u002F 3. ListView 优化\n",[320,10889,10890],{"class":322,"line":953},[320,10891,10892],{},"ListView.builder(\n",[320,10894,10895],{"class":322,"line":959},[320,10896,10897],{},"  itemCount: 10000,\n",[320,10899,10900],{"class":322,"line":965},[320,10901,10902],{},"  itemExtent: 72,          \u002F\u002F 固定高度，跳过布局计算\n",[320,10904,10905],{"class":322,"line":970},[320,10906,10907],{},"  itemBuilder: (ctx, i) => ItemTile(items[i]),\n",[320,10909,10910],{"class":322,"line":975},[320,10911,6608],{},[320,10913,10914],{"class":322,"line":980},[320,10915,357],{"emptyLinePlaceholder":356},[320,10917,10918],{"class":322,"line":986},[320,10919,10920],{},"\u002F\u002F 4. 图片优化\n",[320,10922,10923],{"class":322,"line":992},[320,10924,10925],{},"Image.asset('photo.png', cacheWidth: 200)  \u002F\u002F 降采样\n",[320,10927,10928],{"class":322,"line":998},[320,10929,10930],{},"CachedNetworkImage(imageUrl: url)          \u002F\u002F 缓存网络图片\n",[320,10932,10933],{"class":322,"line":1003},[320,10934,357],{"emptyLinePlaceholder":356},[320,10936,10937],{"class":322,"line":1009},[320,10938,10939],{},"\u002F\u002F 5. 动画性能\n",[320,10941,10942],{"class":322,"line":1015},[320,10943,10944],{},"AnimatedBuilder(\n",[320,10946,10947],{"class":322,"line":1021},[320,10948,10949],{},"  animation: controller,\n",[320,10951,10952],{"class":322,"line":1027},[320,10953,10954],{},"  child: const ExpensiveChild(),  \u002F\u002F 缓存不变的子树\n",[320,10956,10957],{"class":322,"line":1033},[320,10958,10959],{},"  builder: (ctx, child) => Transform.scale(\n",[320,10961,10962],{"class":322,"line":1039},[320,10963,10964],{},"    scale: controller.value,\n",[320,10966,10967],{"class":322,"line":1045},[320,10968,10969],{},"    child: child,  \u002F\u002F 复用\n",[320,10971,10972],{"class":322,"line":1050},[320,10973,9839],{},[320,10975,10976],{"class":322,"line":1055},[320,10977,6608],{},[320,10979,10980],{"class":322,"line":1060},[320,10981,357],{"emptyLinePlaceholder":356},[320,10983,10984],{"class":322,"line":1066},[320,10985,10986],{},"\u002F\u002F 6. Shader 预热（Impeller 已大幅缓解）\n",[320,10988,10989],{"class":322,"line":1072},[320,10990,10991],{},"\u002F\u002F 收集 SkSL → 编译时打包\n",[320,10993,10994],{"class":322,"line":1078},[320,10995,10996],{},"\u002F\u002F flutter run --profile --cache-sksl --purge-persistent-cache\n",[320,10998,10999],{"class":322,"line":1084},[320,11000,11001],{},"\u002F\u002F flutter build apk --bundle-sksl-path flutter_01.sksl.json\n",[320,11003,11004],{"class":322,"line":1089},[320,11005,357],{"emptyLinePlaceholder":356},[320,11007,11008],{"class":322,"line":1094},[320,11009,11010],{},"\u002F\u002F 7. 使用 DevTools 分析\n",[320,11012,11013],{"class":322,"line":1100},[320,11014,11015],{},"\u002F\u002F flutter run --profile\n",[320,11017,11018],{"class":322,"line":1106},[320,11019,11020],{},"\u002F\u002F 打开 DevTools → Performance → 分析帧耗时\n",[10,11022],{},[180,11024,155],{"id":11025},"第四部分跨平台通用-1",[13,11027,11029],{"id":11028},"_20-网络与安全","20. 网络与安全",[17,11031,11033],{"id":11032},"q201-https证书固定certificate-pinning和网络安全最佳实践","Q20.1: HTTPS、证书固定（Certificate Pinning）和网络安全最佳实践",[201,11035,11036],{},[204,11037,206],{},[311,11039,11042],{"className":11040,"code":11041,"language":2187},[2185],"TLS 握手过程：\nClient ──→ ClientHello（支持的密码套件）──→ Server\nClient ←── ServerHello + 证书 ←── Server\nClient 验证证书链 → 信任 CA → 建立加密连接\n",[192,11043,11041],{"__ignoreMap":316},[201,11045,11046],{},[204,11047,11048],{},"证书固定（Certificate Pinning）：",[311,11050,11052],{"className":313,"code":11051,"language":315,"meta":316,"style":316},"\u002F\u002F iOS：使用 URLSession 实现\nclass PinningDelegate: NSObject, URLSessionDelegate {\n    let pinnedHashes: Set\u003CString> = [\"sha256\u002FBBBBBBBBB...\"]\n\n    func urlSession(_ session: URLSession,\n                    didReceive challenge: URLAuthenticationChallenge,\n                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {\n        guard let serverTrust = challenge.protectionSpace.serverTrust,\n              let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {\n            completionHandler(.cancelAuthenticationChallenge, nil)\n            return\n        }\n\n        let serverHash = sha256(SecCertificateCopyData(certificate) as Data)\n        if pinnedHashes.contains(serverHash) {\n            completionHandler(.useCredential, URLCredential(trust: serverTrust))\n        } else {\n            completionHandler(.cancelAuthenticationChallenge, nil)\n        }\n    }\n}\n",[192,11053,11054,11059,11064,11069,11073,11078,11083,11088,11093,11098,11103,11108,11112,11116,11121,11126,11131,11135,11139,11143,11147],{"__ignoreMap":316},[320,11055,11056],{"class":322,"line":323},[320,11057,11058],{},"\u002F\u002F iOS：使用 URLSession 实现\n",[320,11060,11061],{"class":322,"line":329},[320,11062,11063],{},"class PinningDelegate: NSObject, URLSessionDelegate {\n",[320,11065,11066],{"class":322,"line":335},[320,11067,11068],{},"    let pinnedHashes: Set\u003CString> = [\"sha256\u002FBBBBBBBBB...\"]\n",[320,11070,11071],{"class":322,"line":341},[320,11072,357],{"emptyLinePlaceholder":356},[320,11074,11075],{"class":322,"line":347},[320,11076,11077],{},"    func urlSession(_ session: URLSession,\n",[320,11079,11080],{"class":322,"line":353},[320,11081,11082],{},"                    didReceive challenge: URLAuthenticationChallenge,\n",[320,11084,11085],{"class":322,"line":360},[320,11086,11087],{},"                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {\n",[320,11089,11090],{"class":322,"line":74},[320,11091,11092],{},"        guard let serverTrust = challenge.protectionSpace.serverTrust,\n",[320,11094,11095],{"class":322,"line":371},[320,11096,11097],{},"              let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {\n",[320,11099,11100],{"class":322,"line":377},[320,11101,11102],{},"            completionHandler(.cancelAuthenticationChallenge, nil)\n",[320,11104,11105],{"class":322,"line":383},[320,11106,11107],{},"            return\n",[320,11109,11110],{"class":322,"line":388},[320,11111,1042],{},[320,11113,11114],{"class":322,"line":394},[320,11115,357],{"emptyLinePlaceholder":356},[320,11117,11118],{"class":322,"line":400},[320,11119,11120],{},"        let serverHash = sha256(SecCertificateCopyData(certificate) as Data)\n",[320,11122,11123],{"class":322,"line":121},[320,11124,11125],{},"        if pinnedHashes.contains(serverHash) {\n",[320,11127,11128],{"class":322,"line":411},[320,11129,11130],{},"            completionHandler(.useCredential, URLCredential(trust: serverTrust))\n",[320,11132,11133],{"class":322,"line":416},[320,11134,3783],{},[320,11136,11137],{"class":322,"line":421},[320,11138,11102],{},[320,11140,11141],{"class":322,"line":427},[320,11142,1042],{},[320,11144,11145],{"class":322,"line":158},[320,11146,558],{},[320,11148,11149],{"class":322,"line":438},[320,11150,350],{},[311,11152,11154],{"className":4996,"code":11153,"language":4998,"meta":316,"style":316},"\u002F\u002F Android：OkHttp 证书固定\nval client = OkHttpClient.Builder()\n    .certificatePinner(CertificatePinner.Builder()\n        .add(\"api.example.com\", \"sha256\u002FBBBBBBBBB...\")\n        .add(\"api.example.com\", \"sha256\u002Fbackup-pin...\")  \u002F\u002F 备用 pin\n        .build())\n    .build()\n",[192,11155,11156,11161,11166,11171,11176,11181,11185],{"__ignoreMap":316},[320,11157,11158],{"class":322,"line":323},[320,11159,11160],{},"\u002F\u002F Android：OkHttp 证书固定\n",[320,11162,11163],{"class":322,"line":329},[320,11164,11165],{},"val client = OkHttpClient.Builder()\n",[320,11167,11168],{"class":322,"line":335},[320,11169,11170],{},"    .certificatePinner(CertificatePinner.Builder()\n",[320,11172,11173],{"class":322,"line":341},[320,11174,11175],{},"        .add(\"api.example.com\", \"sha256\u002FBBBBBBBBB...\")\n",[320,11177,11178],{"class":322,"line":347},[320,11179,11180],{},"        .add(\"api.example.com\", \"sha256\u002Fbackup-pin...\")  \u002F\u002F 备用 pin\n",[320,11182,11183],{"class":322,"line":353},[320,11184,6428],{},[320,11186,11187],{"class":322,"line":360},[320,11188,6438],{},[311,11190,11194],{"className":11191,"code":11192,"language":11193,"meta":316,"style":316},"language-xml shiki shiki-themes github-light github-dark","\u003C!-- Android：Network Security Config（推荐） -->\n\u003C!-- res\u002Fxml\u002Fnetwork_security_config.xml -->\n\u003Cnetwork-security-config>\n    \u003Cdomain-config cleartextTrafficPermitted=\"false\">\n        \u003Cdomain includeSubdomains=\"true\">api.example.com\u003C\u002Fdomain>\n        \u003Cpin-set expiration=\"2025-12-31\">\n            \u003Cpin digest=\"SHA-256\">base64-encoded-hash\u003C\u002Fpin>\n            \u003Cpin digest=\"SHA-256\">backup-pin-hash\u003C\u002Fpin>\n        \u003C\u002Fpin-set>\n    \u003C\u002Fdomain-config>\n\u003C\u002Fnetwork-security-config>\n","xml",[192,11195,11196,11201,11206,11211,11216,11221,11226,11231,11236,11241,11246],{"__ignoreMap":316},[320,11197,11198],{"class":322,"line":323},[320,11199,11200],{},"\u003C!-- Android：Network Security Config（推荐） -->\n",[320,11202,11203],{"class":322,"line":329},[320,11204,11205],{},"\u003C!-- res\u002Fxml\u002Fnetwork_security_config.xml -->\n",[320,11207,11208],{"class":322,"line":335},[320,11209,11210],{},"\u003Cnetwork-security-config>\n",[320,11212,11213],{"class":322,"line":341},[320,11214,11215],{},"    \u003Cdomain-config cleartextTrafficPermitted=\"false\">\n",[320,11217,11218],{"class":322,"line":347},[320,11219,11220],{},"        \u003Cdomain includeSubdomains=\"true\">api.example.com\u003C\u002Fdomain>\n",[320,11222,11223],{"class":322,"line":353},[320,11224,11225],{},"        \u003Cpin-set expiration=\"2025-12-31\">\n",[320,11227,11228],{"class":322,"line":360},[320,11229,11230],{},"            \u003Cpin digest=\"SHA-256\">base64-encoded-hash\u003C\u002Fpin>\n",[320,11232,11233],{"class":322,"line":74},[320,11234,11235],{},"            \u003Cpin digest=\"SHA-256\">backup-pin-hash\u003C\u002Fpin>\n",[320,11237,11238],{"class":322,"line":371},[320,11239,11240],{},"        \u003C\u002Fpin-set>\n",[320,11242,11243],{"class":322,"line":377},[320,11244,11245],{},"    \u003C\u002Fdomain-config>\n",[320,11247,11248],{"class":322,"line":383},[320,11249,11250],{},"\u003C\u002Fnetwork-security-config>\n",[201,11252,11253],{},[204,11254,11255],{},"安全存储：",[208,11257,11258,11270],{},[211,11259,11260],{},[214,11261,11262,11265,11268],{},[217,11263,11264],{},"平台",[217,11266,11267],{},"安全存储方案",[217,11269,1266],{},[227,11271,11272,11283,11294],{},[214,11273,11274,11277,11280],{},[232,11275,11276],{},"iOS",[232,11278,11279],{},"Keychain",[232,11281,11282],{},"Token、密码、证书",[214,11284,11285,11288,11291],{},[232,11286,11287],{},"Android",[232,11289,11290],{},"EncryptedSharedPreferences \u002F Keystore",[232,11292,11293],{},"Token、敏感配置",[214,11295,11296,11299,11302],{},[232,11297,11298],{},"Flutter",[232,11300,11301],{},"flutter_secure_storage（底层用 Keychain\u002FKeystore）",[232,11303,11304],{},"跨平台安全存储",[311,11306,11308],{"className":4996,"code":11307,"language":4998,"meta":316,"style":316},"\u002F\u002F Android EncryptedSharedPreferences\nval masterKey = MasterKey.Builder(context)\n    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)\n    .build()\n\nval prefs = EncryptedSharedPreferences.create(\n    context, \"secret_prefs\", masterKey,\n    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,\n    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM\n)\nprefs.edit().putString(\"auth_token\", token).apply()\n",[192,11309,11310,11315,11320,11325,11329,11333,11338,11343,11348,11353,11357],{"__ignoreMap":316},[320,11311,11312],{"class":322,"line":323},[320,11313,11314],{},"\u002F\u002F Android EncryptedSharedPreferences\n",[320,11316,11317],{"class":322,"line":329},[320,11318,11319],{},"val masterKey = MasterKey.Builder(context)\n",[320,11321,11322],{"class":322,"line":335},[320,11323,11324],{},"    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)\n",[320,11326,11327],{"class":322,"line":341},[320,11328,6438],{},[320,11330,11331],{"class":322,"line":347},[320,11332,357],{"emptyLinePlaceholder":356},[320,11334,11335],{"class":322,"line":353},[320,11336,11337],{},"val prefs = EncryptedSharedPreferences.create(\n",[320,11339,11340],{"class":322,"line":360},[320,11341,11342],{},"    context, \"secret_prefs\", masterKey,\n",[320,11344,11345],{"class":322,"line":74},[320,11346,11347],{},"    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,\n",[320,11349,11350],{"class":322,"line":371},[320,11351,11352],{},"    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM\n",[320,11354,11355],{"class":322,"line":377},[320,11356,6608],{},[320,11358,11359],{"class":322,"line":383},[320,11360,11361],{},"prefs.edit().putString(\"auth_token\", token).apply()\n",[10,11363],{},[17,11365,11367],{"id":11366},"q202-移动端常见安全威胁与防护","Q20.2: 移动端常见安全威胁与防护",[201,11369,11370],{},[204,11371,206],{},[208,11373,11374,11384],{},[211,11375,11376],{},[214,11377,11378,11381],{},[217,11379,11380],{},"威胁",[217,11382,11383],{},"防护措施",[227,11385,11386,11394,11402,11410,11418,11426,11434],{},[214,11387,11388,11391],{},[232,11389,11390],{},"中间人攻击（MITM）",[232,11392,11393],{},"证书固定、TLS 1.3",[214,11395,11396,11399],{},[232,11397,11398],{},"逆向工程",[232,11400,11401],{},"代码混淆（ProGuard\u002FR8）、防调试检测",[214,11403,11404,11407],{},[232,11405,11406],{},"数据泄露",[232,11408,11409],{},"加密存储、不在日志中打印敏感信息",[214,11411,11412,11415],{},[232,11413,11414],{},"越狱\u002FRoot 检测",[232,11416,11417],{},"Jailbreak\u002FRoot 检测 + 降级功能",[214,11419,11420,11423],{},[232,11421,11422],{},"不安全的 WebView",[232,11424,11425],{},"禁用 JavaScript（除非必要）、验证 URL scheme",[214,11427,11428,11431],{},[232,11429,11430],{},"剪贴板泄露",[232,11432,11433],{},"敏感字段禁止复制、清除剪贴板",[214,11435,11436,11439],{},[232,11437,11438],{},"截屏泄露",[232,11440,11441,11444,11445,11448],{},[192,11442,11443],{},"FLAG_SECURE","（Android）、",[192,11446,11447],{},"applicationDidEnterBackground"," 遮挡（iOS）",[311,11450,11452],{"className":313,"code":11451,"language":315,"meta":316,"style":316},"\u002F\u002F iOS 防截屏\nfunc applicationWillResignActive(_ application: UIApplication) {\n    let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))\n    blurView.tag = 999\n    window?.addSubview(blurView)\n}\n",[192,11453,11454,11459,11464,11469,11474,11479],{"__ignoreMap":316},[320,11455,11456],{"class":322,"line":323},[320,11457,11458],{},"\u002F\u002F iOS 防截屏\n",[320,11460,11461],{"class":322,"line":329},[320,11462,11463],{},"func applicationWillResignActive(_ application: UIApplication) {\n",[320,11465,11466],{"class":322,"line":335},[320,11467,11468],{},"    let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))\n",[320,11470,11471],{"class":322,"line":341},[320,11472,11473],{},"    blurView.tag = 999\n",[320,11475,11476],{"class":322,"line":347},[320,11477,11478],{},"    window?.addSubview(blurView)\n",[320,11480,11481],{"class":322,"line":353},[320,11482,350],{},[311,11484,11486],{"className":4996,"code":11485,"language":4998,"meta":316,"style":316},"\u002F\u002F Android 防截屏\nwindow.setFlags(\n    WindowManager.LayoutParams.FLAG_SECURE,\n    WindowManager.LayoutParams.FLAG_SECURE\n)\n",[192,11487,11488,11493,11498,11503,11508],{"__ignoreMap":316},[320,11489,11490],{"class":322,"line":323},[320,11491,11492],{},"\u002F\u002F Android 防截屏\n",[320,11494,11495],{"class":322,"line":329},[320,11496,11497],{},"window.setFlags(\n",[320,11499,11500],{"class":322,"line":335},[320,11501,11502],{},"    WindowManager.LayoutParams.FLAG_SECURE,\n",[320,11504,11505],{"class":322,"line":341},[320,11506,11507],{},"    WindowManager.LayoutParams.FLAG_SECURE\n",[320,11509,11510],{"class":322,"line":347},[320,11511,6608],{},[10,11513],{},[13,11515,11517],{"id":11516},"_21-cicd-与发布","21. CI\u002FCD 与发布",[17,11519,11521],{"id":11520},"q211-移动端-cicd-流水线设计","Q21.1: 移动端 CI\u002FCD 流水线设计",[201,11523,11524],{},[204,11525,206],{},[311,11527,11531],{"className":11528,"code":11529,"language":11530,"meta":316,"style":316},"language-yaml shiki shiki-themes github-light github-dark","# 典型流水线（以 GitHub Actions 为例）\n# .github\u002Fworkflows\u002Fmobile.yml\n\nname: Mobile CI\u002FCD\n\non:\n  pull_request:\n    branches: [main]\n  push:\n    tags: ['v*']\n\njobs:\n  test:\n    runs-on: macos-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      # Flutter\n      - uses: subosito\u002Fflutter-action@v2\n        with:\n          flutter-version: '3.24.0'\n      - run: flutter pub get\n      - run: flutter analyze\n      - run: flutter test --coverage\n\n      # iOS\n      - run: xcodebuild test -workspace App.xcworkspace -scheme App -destination 'platform=iOS Simulator,name=iPhone 15'\n\n      # Android\n      - run: .\u002Fgradlew test\n      - run: .\u002Fgradlew lint\n\n  build-ios:\n    needs: test\n    runs-on: macos-latest\n    if: startsWith(github.ref, 'refs\u002Ftags\u002F')\n    steps:\n      - run: flutter build ipa --release --export-options-plist=ExportOptions.plist\n      - uses: apple-actions\u002Fupload-testflight-build@v1\n\n  build-android:\n    needs: test\n    runs-on: ubuntu-latest\n    if: startsWith(github.ref, 'refs\u002Ftags\u002F')\n    steps:\n      - run: flutter build appbundle --release\n      - uses: r0adkll\u002Fupload-google-play@v1\n        with:\n          track: internal\n","yaml",[192,11532,11533,11539,11544,11548,11562,11566,11575,11582,11596,11603,11615,11619,11626,11633,11643,11650,11663,11667,11672,11683,11690,11700,11711,11722,11733,11737,11742,11753,11757,11762,11773,11784,11788,11795,11805,11813,11823,11829,11840,11851,11855,11862,11870,11879,11887,11893,11904,11915,11921],{"__ignoreMap":316},[320,11534,11535],{"class":322,"line":323},[320,11536,11538],{"class":11537},"sJ8bj","# 典型流水线（以 GitHub Actions 为例）\n",[320,11540,11541],{"class":322,"line":329},[320,11542,11543],{"class":11537},"# .github\u002Fworkflows\u002Fmobile.yml\n",[320,11545,11546],{"class":322,"line":335},[320,11547,357],{"emptyLinePlaceholder":356},[320,11549,11550,11554,11558],{"class":322,"line":341},[320,11551,11553],{"class":11552},"s9eBZ","name",[320,11555,11557],{"class":11556},"sVt8B",": ",[320,11559,11561],{"class":11560},"sZZnC","Mobile CI\u002FCD\n",[320,11563,11564],{"class":322,"line":347},[320,11565,357],{"emptyLinePlaceholder":356},[320,11567,11568,11572],{"class":322,"line":353},[320,11569,11571],{"class":11570},"sj4cs","on",[320,11573,11574],{"class":11556},":\n",[320,11576,11577,11580],{"class":322,"line":360},[320,11578,11579],{"class":11552},"  pull_request",[320,11581,11574],{"class":11556},[320,11583,11584,11587,11590,11593],{"class":322,"line":74},[320,11585,11586],{"class":11552},"    branches",[320,11588,11589],{"class":11556},": [",[320,11591,11592],{"class":11560},"main",[320,11594,11595],{"class":11556},"]\n",[320,11597,11598,11601],{"class":322,"line":371},[320,11599,11600],{"class":11552},"  push",[320,11602,11574],{"class":11556},[320,11604,11605,11608,11610,11613],{"class":322,"line":377},[320,11606,11607],{"class":11552},"    tags",[320,11609,11589],{"class":11556},[320,11611,11612],{"class":11560},"'v*'",[320,11614,11595],{"class":11556},[320,11616,11617],{"class":322,"line":383},[320,11618,357],{"emptyLinePlaceholder":356},[320,11620,11621,11624],{"class":322,"line":388},[320,11622,11623],{"class":11552},"jobs",[320,11625,11574],{"class":11556},[320,11627,11628,11631],{"class":322,"line":394},[320,11629,11630],{"class":11552},"  test",[320,11632,11574],{"class":11556},[320,11634,11635,11638,11640],{"class":322,"line":400},[320,11636,11637],{"class":11552},"    runs-on",[320,11639,11557],{"class":11556},[320,11641,11642],{"class":11560},"macos-latest\n",[320,11644,11645,11648],{"class":322,"line":121},[320,11646,11647],{"class":11552},"    steps",[320,11649,11574],{"class":11556},[320,11651,11652,11655,11658,11660],{"class":322,"line":411},[320,11653,11654],{"class":11556},"      - ",[320,11656,11657],{"class":11552},"uses",[320,11659,11557],{"class":11556},[320,11661,11662],{"class":11560},"actions\u002Fcheckout@v4\n",[320,11664,11665],{"class":322,"line":416},[320,11666,357],{"emptyLinePlaceholder":356},[320,11668,11669],{"class":322,"line":421},[320,11670,11671],{"class":11537},"      # Flutter\n",[320,11673,11674,11676,11678,11680],{"class":322,"line":427},[320,11675,11654],{"class":11556},[320,11677,11657],{"class":11552},[320,11679,11557],{"class":11556},[320,11681,11682],{"class":11560},"subosito\u002Fflutter-action@v2\n",[320,11684,11685,11688],{"class":322,"line":158},[320,11686,11687],{"class":11552},"        with",[320,11689,11574],{"class":11556},[320,11691,11692,11695,11697],{"class":322,"line":438},[320,11693,11694],{"class":11552},"          flutter-version",[320,11696,11557],{"class":11556},[320,11698,11699],{"class":11560},"'3.24.0'\n",[320,11701,11702,11704,11706,11708],{"class":322,"line":615},[320,11703,11654],{"class":11556},[320,11705,5314],{"class":11552},[320,11707,11557],{"class":11556},[320,11709,11710],{"class":11560},"flutter pub get\n",[320,11712,11713,11715,11717,11719],{"class":322,"line":621},[320,11714,11654],{"class":11556},[320,11716,5314],{"class":11552},[320,11718,11557],{"class":11556},[320,11720,11721],{"class":11560},"flutter analyze\n",[320,11723,11724,11726,11728,11730],{"class":322,"line":627},[320,11725,11654],{"class":11556},[320,11727,5314],{"class":11552},[320,11729,11557],{"class":11556},[320,11731,11732],{"class":11560},"flutter test --coverage\n",[320,11734,11735],{"class":322,"line":935},[320,11736,357],{"emptyLinePlaceholder":356},[320,11738,11739],{"class":322,"line":941},[320,11740,11741],{"class":11537},"      # iOS\n",[320,11743,11744,11746,11748,11750],{"class":322,"line":947},[320,11745,11654],{"class":11556},[320,11747,5314],{"class":11552},[320,11749,11557],{"class":11556},[320,11751,11752],{"class":11560},"xcodebuild test -workspace App.xcworkspace -scheme App -destination 'platform=iOS Simulator,name=iPhone 15'\n",[320,11754,11755],{"class":322,"line":953},[320,11756,357],{"emptyLinePlaceholder":356},[320,11758,11759],{"class":322,"line":959},[320,11760,11761],{"class":11537},"      # Android\n",[320,11763,11764,11766,11768,11770],{"class":322,"line":965},[320,11765,11654],{"class":11556},[320,11767,5314],{"class":11552},[320,11769,11557],{"class":11556},[320,11771,11772],{"class":11560},".\u002Fgradlew test\n",[320,11774,11775,11777,11779,11781],{"class":322,"line":970},[320,11776,11654],{"class":11556},[320,11778,5314],{"class":11552},[320,11780,11557],{"class":11556},[320,11782,11783],{"class":11560},".\u002Fgradlew lint\n",[320,11785,11786],{"class":322,"line":975},[320,11787,357],{"emptyLinePlaceholder":356},[320,11789,11790,11793],{"class":322,"line":980},[320,11791,11792],{"class":11552},"  build-ios",[320,11794,11574],{"class":11556},[320,11796,11797,11800,11802],{"class":322,"line":986},[320,11798,11799],{"class":11552},"    needs",[320,11801,11557],{"class":11556},[320,11803,11804],{"class":11560},"test\n",[320,11806,11807,11809,11811],{"class":322,"line":992},[320,11808,11637],{"class":11552},[320,11810,11557],{"class":11556},[320,11812,11642],{"class":11560},[320,11814,11815,11818,11820],{"class":322,"line":998},[320,11816,11817],{"class":11552},"    if",[320,11819,11557],{"class":11556},[320,11821,11822],{"class":11560},"startsWith(github.ref, 'refs\u002Ftags\u002F')\n",[320,11824,11825,11827],{"class":322,"line":1003},[320,11826,11647],{"class":11552},[320,11828,11574],{"class":11556},[320,11830,11831,11833,11835,11837],{"class":322,"line":1009},[320,11832,11654],{"class":11556},[320,11834,5314],{"class":11552},[320,11836,11557],{"class":11556},[320,11838,11839],{"class":11560},"flutter build ipa --release --export-options-plist=ExportOptions.plist\n",[320,11841,11842,11844,11846,11848],{"class":322,"line":1015},[320,11843,11654],{"class":11556},[320,11845,11657],{"class":11552},[320,11847,11557],{"class":11556},[320,11849,11850],{"class":11560},"apple-actions\u002Fupload-testflight-build@v1\n",[320,11852,11853],{"class":322,"line":1021},[320,11854,357],{"emptyLinePlaceholder":356},[320,11856,11857,11860],{"class":322,"line":1027},[320,11858,11859],{"class":11552},"  build-android",[320,11861,11574],{"class":11556},[320,11863,11864,11866,11868],{"class":322,"line":1033},[320,11865,11799],{"class":11552},[320,11867,11557],{"class":11556},[320,11869,11804],{"class":11560},[320,11871,11872,11874,11876],{"class":322,"line":1039},[320,11873,11637],{"class":11552},[320,11875,11557],{"class":11556},[320,11877,11878],{"class":11560},"ubuntu-latest\n",[320,11880,11881,11883,11885],{"class":322,"line":1045},[320,11882,11817],{"class":11552},[320,11884,11557],{"class":11556},[320,11886,11822],{"class":11560},[320,11888,11889,11891],{"class":322,"line":1050},[320,11890,11647],{"class":11552},[320,11892,11574],{"class":11556},[320,11894,11895,11897,11899,11901],{"class":322,"line":1055},[320,11896,11654],{"class":11556},[320,11898,5314],{"class":11552},[320,11900,11557],{"class":11556},[320,11902,11903],{"class":11560},"flutter build appbundle --release\n",[320,11905,11906,11908,11910,11912],{"class":322,"line":1060},[320,11907,11654],{"class":11556},[320,11909,11657],{"class":11552},[320,11911,11557],{"class":11556},[320,11913,11914],{"class":11560},"r0adkll\u002Fupload-google-play@v1\n",[320,11916,11917,11919],{"class":322,"line":1066},[320,11918,11687],{"class":11552},[320,11920,11574],{"class":11556},[320,11922,11923,11926,11928],{"class":322,"line":1072},[320,11924,11925],{"class":11552},"          track",[320,11927,11557],{"class":11556},[320,11929,11930],{"class":11560},"internal\n",[201,11932,11933],{},[204,11934,11935],{},"代码签名管理：",[208,11937,11938,11950],{},[211,11939,11940],{},[214,11941,11942,11944,11947],{},[217,11943,11264],{},[217,11945,11946],{},"方案",[217,11948,11949],{},"说明",[227,11951,11952,11962,11972],{},[214,11953,11954,11956,11959],{},[232,11955,11276],{},[232,11957,11958],{},"fastlane match",[232,11960,11961],{},"集中管理证书和 Provisioning Profile",[214,11963,11964,11966,11969],{},[232,11965,11287],{},[232,11967,11968],{},"Play App Signing",[232,11970,11971],{},"Google 管理签名密钥",[214,11973,11974,11977,11980],{},[232,11975,11976],{},"通用",[232,11978,11979],{},"CI 环境变量 + Secrets",[232,11981,11982],{},"不在代码中存储密钥",[10,11984],{},[13,11986,11988],{"id":11987},"_22-架构设计与系统设计","22. 架构设计与系统设计",[17,11990,11992],{"id":11991},"q221-如何设计一个离线优先offline-first的移动应用","Q22.1: 如何设计一个离线优先（Offline-First）的移动应用？",[201,11994,11995],{},[204,11996,206],{},[311,11998,12001],{"className":11999,"code":12000,"language":2187},[2185],"┌────────────────────────────────────────────────┐\n│                   UI Layer                      │\n│    展示数据（始终从本地数据库读取）                │\n├────────────────────────────────────────────────┤\n│                Repository Layer                 │\n│  ┌──────────┐    ┌─────────────┐               │\n│  │ Local DB  │←──│ Sync Engine │               │\n│  │ (Room\u002F    │   │             │               │\n│  │  SQLite\u002F  │──→│ Conflict    │               │\n│  │  Hive)    │   │ Resolution  │               │\n│  └──────────┘    └──────┬──────┘               │\n│                         │                       │\n│                  ┌──────▼──────┐               │\n│                  │  Remote API  │               │\n│                  └─────────────┘               │\n└────────────────────────────────────────────────┘\n",[192,12002,12000],{"__ignoreMap":316},[311,12004,12006],{"className":4996,"code":12005,"language":4998,"meta":316,"style":316},"\u002F\u002F Repository：先写本地，后台同步\nclass TodoRepository(\n    private val dao: TodoDao,\n    private val api: TodoApi,\n    private val syncManager: SyncManager\n) {\n    \u002F\u002F 读取：始终从本地\n    fun observeTodos(): Flow\u003CList\u003CTodo>> = dao.observeAll()\n\n    \u002F\u002F 写入：先本地，然后排队同步\n    suspend fun addTodo(todo: Todo) {\n        val localTodo = todo.copy(syncStatus = SyncStatus.PENDING)\n        dao.insert(localTodo)\n        syncManager.enqueueSync(localTodo.id)\n    }\n\n    \u002F\u002F 同步引擎\n    suspend fun sync() {\n        \u002F\u002F 1. 上传本地未同步的变更\n        val pending = dao.getPendingSyncItems()\n        for (item in pending) {\n            try {\n                api.upsert(item)\n                dao.updateSyncStatus(item.id, SyncStatus.SYNCED)\n            } catch (e: ConflictException) {\n                resolveConflict(item, e.serverVersion)\n            }\n        }\n\n        \u002F\u002F 2. 拉取远程变更\n        val lastSync = prefs.lastSyncTimestamp\n        val remoteChanges = api.getChangesSince(lastSync)\n        dao.upsertAll(remoteChanges)\n        prefs.lastSyncTimestamp = System.currentTimeMillis()\n    }\n}\n",[192,12007,12008,12013,12018,12023,12028,12033,12037,12042,12047,12051,12056,12061,12066,12071,12076,12080,12084,12089,12094,12099,12104,12109,12113,12118,12123,12128,12133,12137,12141,12145,12150,12155,12160,12165,12170,12174],{"__ignoreMap":316},[320,12009,12010],{"class":322,"line":323},[320,12011,12012],{},"\u002F\u002F Repository：先写本地，后台同步\n",[320,12014,12015],{"class":322,"line":329},[320,12016,12017],{},"class TodoRepository(\n",[320,12019,12020],{"class":322,"line":335},[320,12021,12022],{},"    private val dao: TodoDao,\n",[320,12024,12025],{"class":322,"line":341},[320,12026,12027],{},"    private val api: TodoApi,\n",[320,12029,12030],{"class":322,"line":347},[320,12031,12032],{},"    private val syncManager: SyncManager\n",[320,12034,12035],{"class":322,"line":353},[320,12036,5870],{},[320,12038,12039],{"class":322,"line":360},[320,12040,12041],{},"    \u002F\u002F 读取：始终从本地\n",[320,12043,12044],{"class":322,"line":74},[320,12045,12046],{},"    fun observeTodos(): Flow\u003CList\u003CTodo>> = dao.observeAll()\n",[320,12048,12049],{"class":322,"line":371},[320,12050,357],{"emptyLinePlaceholder":356},[320,12052,12053],{"class":322,"line":377},[320,12054,12055],{},"    \u002F\u002F 写入：先本地，然后排队同步\n",[320,12057,12058],{"class":322,"line":383},[320,12059,12060],{},"    suspend fun addTodo(todo: Todo) {\n",[320,12062,12063],{"class":322,"line":388},[320,12064,12065],{},"        val localTodo = todo.copy(syncStatus = SyncStatus.PENDING)\n",[320,12067,12068],{"class":322,"line":394},[320,12069,12070],{},"        dao.insert(localTodo)\n",[320,12072,12073],{"class":322,"line":400},[320,12074,12075],{},"        syncManager.enqueueSync(localTodo.id)\n",[320,12077,12078],{"class":322,"line":121},[320,12079,558],{},[320,12081,12082],{"class":322,"line":411},[320,12083,357],{"emptyLinePlaceholder":356},[320,12085,12086],{"class":322,"line":416},[320,12087,12088],{},"    \u002F\u002F 同步引擎\n",[320,12090,12091],{"class":322,"line":421},[320,12092,12093],{},"    suspend fun sync() {\n",[320,12095,12096],{"class":322,"line":427},[320,12097,12098],{},"        \u002F\u002F 1. 上传本地未同步的变更\n",[320,12100,12101],{"class":322,"line":158},[320,12102,12103],{},"        val pending = dao.getPendingSyncItems()\n",[320,12105,12106],{"class":322,"line":438},[320,12107,12108],{},"        for (item in pending) {\n",[320,12110,12111],{"class":322,"line":615},[320,12112,7078],{},[320,12114,12115],{"class":322,"line":621},[320,12116,12117],{},"                api.upsert(item)\n",[320,12119,12120],{"class":322,"line":627},[320,12121,12122],{},"                dao.updateSyncStatus(item.id, SyncStatus.SYNCED)\n",[320,12124,12125],{"class":322,"line":935},[320,12126,12127],{},"            } catch (e: ConflictException) {\n",[320,12129,12130],{"class":322,"line":941},[320,12131,12132],{},"                resolveConflict(item, e.serverVersion)\n",[320,12134,12135],{"class":322,"line":947},[320,12136,3939],{},[320,12138,12139],{"class":322,"line":953},[320,12140,1042],{},[320,12142,12143],{"class":322,"line":959},[320,12144,357],{"emptyLinePlaceholder":356},[320,12146,12147],{"class":322,"line":965},[320,12148,12149],{},"        \u002F\u002F 2. 拉取远程变更\n",[320,12151,12152],{"class":322,"line":970},[320,12153,12154],{},"        val lastSync = prefs.lastSyncTimestamp\n",[320,12156,12157],{"class":322,"line":975},[320,12158,12159],{},"        val remoteChanges = api.getChangesSince(lastSync)\n",[320,12161,12162],{"class":322,"line":980},[320,12163,12164],{},"        dao.upsertAll(remoteChanges)\n",[320,12166,12167],{"class":322,"line":986},[320,12168,12169],{},"        prefs.lastSyncTimestamp = System.currentTimeMillis()\n",[320,12171,12172],{"class":322,"line":992},[320,12173,558],{},[320,12175,12176],{"class":322,"line":998},[320,12177,350],{},[201,12179,12180],{},[204,12181,12182],{},"冲突解决策略：",[448,12184,12185,12191,12197,12203,12209],{},[25,12186,12187,12190],{},[204,12188,12189],{},"Last Write Wins","：最后修改时间戳大的胜出（简单但可能丢数据）",[25,12192,12193,12196],{},[204,12194,12195],{},"Server Wins","：服务器版本始终优先",[25,12198,12199,12202],{},[204,12200,12201],{},"Client Wins","：客户端版本始终优先",[25,12204,12205,12208],{},[204,12206,12207],{},"Manual Merge","：提示用户手动解决冲突",[25,12210,12211,12214],{},[204,12212,12213],{},"CRDT","：无冲突复制数据类型（适合协作编辑）",[10,12216],{},[17,12218,12220],{"id":12219},"q222-移动端如何实现推送通知系统","Q22.2: 移动端如何实现推送通知系统？",[201,12222,12223],{},[204,12224,206],{},[311,12226,12229],{"className":12227,"code":12228,"language":2187},[2185],"┌─────────┐    ┌──────────┐    ┌──────────────┐    ┌────────────┐\n│ Backend  │───→│ Push     │───→│ APNs \u002F FCM   │───→│ Mobile App │\n│ Server   │    │ Service  │    │              │    │            │\n└─────────┘    └──────────┘    └──────────────┘    └────────────┘\n",[192,12230,12228],{"__ignoreMap":316},[311,12232,12234],{"className":313,"code":12233,"language":315,"meta":316,"style":316},"\u002F\u002F iOS：APNs 注册\nfunc application(_ application: UIApplication,\n                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in\n        guard granted else { return }\n        DispatchQueue.main.async { application.registerForRemoteNotifications() }\n    }\n    return true\n}\n\nfunc application(_ application: UIApplication,\n                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n    let token = deviceToken.map { String(format: \"%02x\", $0) }.joined()\n    api.registerPushToken(token)\n}\n\n\u002F\u002F 处理推送\nextension AppDelegate: UNUserNotificationCenterDelegate {\n    \u002F\u002F 前台收到通知\n    func userNotificationCenter(_ center: UNUserNotificationCenter,\n                                willPresent notification: UNNotification,\n                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {\n        completionHandler([.banner, .sound])\n    }\n\n    \u002F\u002F 用户点击通知\n    func userNotificationCenter(_ center: UNUserNotificationCenter,\n                                didReceive response: UNNotificationResponse,\n                                withCompletionHandler completionHandler: @escaping () -> Void) {\n        let userInfo = response.notification.request.content.userInfo\n        handleDeepLink(from: userInfo)\n        completionHandler()\n    }\n}\n",[192,12235,12236,12241,12245,12249,12254,12259,12264,12268,12272,12276,12280,12284,12289,12294,12299,12303,12307,12312,12317,12322,12327,12332,12337,12342,12346,12350,12355,12359,12364,12369,12374,12379,12384,12388],{"__ignoreMap":316},[320,12237,12238],{"class":322,"line":323},[320,12239,12240],{},"\u002F\u002F iOS：APNs 注册\n",[320,12242,12243],{"class":322,"line":329},[320,12244,4492],{},[320,12246,12247],{"class":322,"line":335},[320,12248,4497],{},[320,12250,12251],{"class":322,"line":341},[320,12252,12253],{},"    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in\n",[320,12255,12256],{"class":322,"line":347},[320,12257,12258],{},"        guard granted else { return }\n",[320,12260,12261],{"class":322,"line":353},[320,12262,12263],{},"        DispatchQueue.main.async { application.registerForRemoteNotifications() }\n",[320,12265,12266],{"class":322,"line":360},[320,12267,558],{},[320,12269,12270],{"class":322,"line":74},[320,12271,4549],{},[320,12273,12274],{"class":322,"line":371},[320,12275,350],{},[320,12277,12278],{"class":322,"line":377},[320,12279,357],{"emptyLinePlaceholder":356},[320,12281,12282],{"class":322,"line":383},[320,12283,4492],{},[320,12285,12286],{"class":322,"line":388},[320,12287,12288],{},"                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n",[320,12290,12291],{"class":322,"line":394},[320,12292,12293],{},"    let token = deviceToken.map { String(format: \"%02x\", $0) }.joined()\n",[320,12295,12296],{"class":322,"line":400},[320,12297,12298],{},"    api.registerPushToken(token)\n",[320,12300,12301],{"class":322,"line":121},[320,12302,350],{},[320,12304,12305],{"class":322,"line":411},[320,12306,357],{"emptyLinePlaceholder":356},[320,12308,12309],{"class":322,"line":416},[320,12310,12311],{},"\u002F\u002F 处理推送\n",[320,12313,12314],{"class":322,"line":421},[320,12315,12316],{},"extension AppDelegate: UNUserNotificationCenterDelegate {\n",[320,12318,12319],{"class":322,"line":427},[320,12320,12321],{},"    \u002F\u002F 前台收到通知\n",[320,12323,12324],{"class":322,"line":158},[320,12325,12326],{},"    func userNotificationCenter(_ center: UNUserNotificationCenter,\n",[320,12328,12329],{"class":322,"line":438},[320,12330,12331],{},"                                willPresent notification: UNNotification,\n",[320,12333,12334],{"class":322,"line":615},[320,12335,12336],{},"                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {\n",[320,12338,12339],{"class":322,"line":621},[320,12340,12341],{},"        completionHandler([.banner, .sound])\n",[320,12343,12344],{"class":322,"line":627},[320,12345,558],{},[320,12347,12348],{"class":322,"line":935},[320,12349,357],{"emptyLinePlaceholder":356},[320,12351,12352],{"class":322,"line":941},[320,12353,12354],{},"    \u002F\u002F 用户点击通知\n",[320,12356,12357],{"class":322,"line":947},[320,12358,12326],{},[320,12360,12361],{"class":322,"line":953},[320,12362,12363],{},"                                didReceive response: UNNotificationResponse,\n",[320,12365,12366],{"class":322,"line":959},[320,12367,12368],{},"                                withCompletionHandler completionHandler: @escaping () -> Void) {\n",[320,12370,12371],{"class":322,"line":965},[320,12372,12373],{},"        let userInfo = response.notification.request.content.userInfo\n",[320,12375,12376],{"class":322,"line":970},[320,12377,12378],{},"        handleDeepLink(from: userInfo)\n",[320,12380,12381],{"class":322,"line":975},[320,12382,12383],{},"        completionHandler()\n",[320,12385,12386],{"class":322,"line":980},[320,12387,558],{},[320,12389,12390],{"class":322,"line":986},[320,12391,350],{},[311,12393,12395],{"className":4996,"code":12394,"language":4998,"meta":316,"style":316},"\u002F\u002F Android：FCM\nclass MyFirebaseService : FirebaseMessagingService() {\n    override fun onNewToken(token: String) {\n        api.registerPushToken(token)\n    }\n\n    override fun onMessageReceived(remoteMessage: RemoteMessage) {\n        \u002F\u002F 数据消息（自定义处理）\n        remoteMessage.data[\"type\"]?.let { type ->\n            when (type) {\n                \"chat\" -> showChatNotification(remoteMessage.data)\n                \"order\" -> showOrderNotification(remoteMessage.data)\n            }\n        }\n\n        \u002F\u002F 通知消息（系统自动显示）\n        remoteMessage.notification?.let { notification ->\n            showNotification(notification.title, notification.body)\n        }\n    }\n\n    private fun showNotification(title: String?, body: String?) {\n        val intent = Intent(this, MainActivity::class.java).apply {\n            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK\n        }\n        val pendingIntent = PendingIntent.getActivity(this, 0, intent,\n            PendingIntent.FLAG_IMMUTABLE)\n\n        val notification = NotificationCompat.Builder(this, CHANNEL_ID)\n            .setSmallIcon(R.drawable.ic_notification)\n            .setContentTitle(title)\n            .setContentText(body)\n            .setContentIntent(pendingIntent)\n            .setAutoCancel(true)\n            .build()\n\n        NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, notification)\n    }\n}\n",[192,12396,12397,12402,12407,12412,12417,12421,12425,12430,12435,12440,12445,12450,12455,12459,12463,12467,12472,12477,12482,12486,12490,12494,12499,12504,12509,12513,12518,12523,12527,12532,12537,12542,12547,12552,12557,12562,12566,12571,12575],{"__ignoreMap":316},[320,12398,12399],{"class":322,"line":323},[320,12400,12401],{},"\u002F\u002F Android：FCM\n",[320,12403,12404],{"class":322,"line":329},[320,12405,12406],{},"class MyFirebaseService : FirebaseMessagingService() {\n",[320,12408,12409],{"class":322,"line":335},[320,12410,12411],{},"    override fun onNewToken(token: String) {\n",[320,12413,12414],{"class":322,"line":341},[320,12415,12416],{},"        api.registerPushToken(token)\n",[320,12418,12419],{"class":322,"line":347},[320,12420,558],{},[320,12422,12423],{"class":322,"line":353},[320,12424,357],{"emptyLinePlaceholder":356},[320,12426,12427],{"class":322,"line":360},[320,12428,12429],{},"    override fun onMessageReceived(remoteMessage: RemoteMessage) {\n",[320,12431,12432],{"class":322,"line":74},[320,12433,12434],{},"        \u002F\u002F 数据消息（自定义处理）\n",[320,12436,12437],{"class":322,"line":371},[320,12438,12439],{},"        remoteMessage.data[\"type\"]?.let { type ->\n",[320,12441,12442],{"class":322,"line":377},[320,12443,12444],{},"            when (type) {\n",[320,12446,12447],{"class":322,"line":383},[320,12448,12449],{},"                \"chat\" -> showChatNotification(remoteMessage.data)\n",[320,12451,12452],{"class":322,"line":388},[320,12453,12454],{},"                \"order\" -> showOrderNotification(remoteMessage.data)\n",[320,12456,12457],{"class":322,"line":394},[320,12458,3939],{},[320,12460,12461],{"class":322,"line":400},[320,12462,1042],{},[320,12464,12465],{"class":322,"line":121},[320,12466,357],{"emptyLinePlaceholder":356},[320,12468,12469],{"class":322,"line":411},[320,12470,12471],{},"        \u002F\u002F 通知消息（系统自动显示）\n",[320,12473,12474],{"class":322,"line":416},[320,12475,12476],{},"        remoteMessage.notification?.let { notification ->\n",[320,12478,12479],{"class":322,"line":421},[320,12480,12481],{},"            showNotification(notification.title, notification.body)\n",[320,12483,12484],{"class":322,"line":427},[320,12485,1042],{},[320,12487,12488],{"class":322,"line":158},[320,12489,558],{},[320,12491,12492],{"class":322,"line":438},[320,12493,357],{"emptyLinePlaceholder":356},[320,12495,12496],{"class":322,"line":615},[320,12497,12498],{},"    private fun showNotification(title: String?, body: String?) {\n",[320,12500,12501],{"class":322,"line":621},[320,12502,12503],{},"        val intent = Intent(this, MainActivity::class.java).apply {\n",[320,12505,12506],{"class":322,"line":627},[320,12507,12508],{},"            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK\n",[320,12510,12511],{"class":322,"line":935},[320,12512,1042],{},[320,12514,12515],{"class":322,"line":941},[320,12516,12517],{},"        val pendingIntent = PendingIntent.getActivity(this, 0, intent,\n",[320,12519,12520],{"class":322,"line":947},[320,12521,12522],{},"            PendingIntent.FLAG_IMMUTABLE)\n",[320,12524,12525],{"class":322,"line":953},[320,12526,357],{"emptyLinePlaceholder":356},[320,12528,12529],{"class":322,"line":959},[320,12530,12531],{},"        val notification = NotificationCompat.Builder(this, CHANNEL_ID)\n",[320,12533,12534],{"class":322,"line":965},[320,12535,12536],{},"            .setSmallIcon(R.drawable.ic_notification)\n",[320,12538,12539],{"class":322,"line":970},[320,12540,12541],{},"            .setContentTitle(title)\n",[320,12543,12544],{"class":322,"line":975},[320,12545,12546],{},"            .setContentText(body)\n",[320,12548,12549],{"class":322,"line":980},[320,12550,12551],{},"            .setContentIntent(pendingIntent)\n",[320,12553,12554],{"class":322,"line":986},[320,12555,12556],{},"            .setAutoCancel(true)\n",[320,12558,12559],{"class":322,"line":992},[320,12560,12561],{},"            .build()\n",[320,12563,12564],{"class":322,"line":998},[320,12565,357],{"emptyLinePlaceholder":356},[320,12567,12568],{"class":322,"line":1003},[320,12569,12570],{},"        NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, notification)\n",[320,12572,12573],{"class":322,"line":1009},[320,12574,558],{},[320,12576,12577],{"class":322,"line":1015},[320,12578,350],{},[311,12580,12582],{"className":9322,"code":12581,"language":9324,"meta":316,"style":316},"\u002F\u002F Flutter：使用 firebase_messaging\nFirebaseMessaging.instance.requestPermission();\n\nFirebaseMessaging.instance.getToken().then((token) {\n  api.registerPushToken(token!);\n});\n\n\u002F\u002F 前台消息\nFirebaseMessaging.onMessage.listen((RemoteMessage message) {\n  showLocalNotification(message);\n});\n\n\u002F\u002F 后台\u002F终止态 点击通知\nFirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {\n  navigateToScreen(message.data);\n});\n",[192,12583,12584,12589,12594,12598,12603,12608,12612,12616,12621,12626,12631,12635,12639,12644,12649,12654],{"__ignoreMap":316},[320,12585,12586],{"class":322,"line":323},[320,12587,12588],{},"\u002F\u002F Flutter：使用 firebase_messaging\n",[320,12590,12591],{"class":322,"line":329},[320,12592,12593],{},"FirebaseMessaging.instance.requestPermission();\n",[320,12595,12596],{"class":322,"line":335},[320,12597,357],{"emptyLinePlaceholder":356},[320,12599,12600],{"class":322,"line":341},[320,12601,12602],{},"FirebaseMessaging.instance.getToken().then((token) {\n",[320,12604,12605],{"class":322,"line":347},[320,12606,12607],{},"  api.registerPushToken(token!);\n",[320,12609,12610],{"class":322,"line":353},[320,12611,9413],{},[320,12613,12614],{"class":322,"line":360},[320,12615,357],{"emptyLinePlaceholder":356},[320,12617,12618],{"class":322,"line":74},[320,12619,12620],{},"\u002F\u002F 前台消息\n",[320,12622,12623],{"class":322,"line":371},[320,12624,12625],{},"FirebaseMessaging.onMessage.listen((RemoteMessage message) {\n",[320,12627,12628],{"class":322,"line":377},[320,12629,12630],{},"  showLocalNotification(message);\n",[320,12632,12633],{"class":322,"line":383},[320,12634,9413],{},[320,12636,12637],{"class":322,"line":388},[320,12638,357],{"emptyLinePlaceholder":356},[320,12640,12641],{"class":322,"line":394},[320,12642,12643],{},"\u002F\u002F 后台\u002F终止态 点击通知\n",[320,12645,12646],{"class":322,"line":400},[320,12647,12648],{},"FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {\n",[320,12650,12651],{"class":322,"line":121},[320,12652,12653],{},"  navigateToScreen(message.data);\n",[320,12655,12656],{"class":322,"line":411},[320,12657,9413],{},[10,12659],{},[17,12661,12663],{"id":12662},"q223-如何设计一个高效的图片加载框架","Q22.3: 如何设计一个高效的图片加载框架？",[201,12665,12666],{},[204,12667,206],{},[311,12669,12672],{"className":12670,"code":12671,"language":2187},[2185],"请求 URL\n   ↓\n┌──────────────┐\n│ 内存缓存      │ → 命中 → 返回\n│ (LRU Cache)  │\n└──────┬───────┘\n       ↓ 未命中\n┌──────────────┐\n│ 磁盘缓存      │ → 命中 → 解码 → 存入内存缓存 → 返回\n│ (文件系统)    │\n└──────┬───────┘\n       ↓ 未命中\n┌──────────────┐\n│ 网络请求      │ → 下载 → 存入磁盘缓存 → 解码 → 存入内存缓存 → 返回\n│ (HTTP)       │\n└──────────────┘\n",[192,12673,12671],{"__ignoreMap":316},[201,12675,12676],{},[204,12677,12678],{},"各平台常用方案：",[208,12680,12681,12693],{},[211,12682,12683],{},[214,12684,12685,12687,12690],{},[217,12686,11264],{},[217,12688,12689],{},"框架",[217,12691,12692],{},"特点",[227,12694,12695,12705,12715],{},[214,12696,12697,12699,12702],{},[232,12698,11276],{},[232,12700,12701],{},"Kingfisher \u002F SDWebImage \u002F Nuke",[232,12703,12704],{},"自动缓存、降采样、动画",[214,12706,12707,12709,12712],{},[232,12708,11287],{},[232,12710,12711],{},"Coil \u002F Glide",[232,12713,12714],{},"生命周期感知、协程支持",[214,12716,12717,12719,12722],{},[232,12718,11298],{},[232,12720,12721],{},"cached_network_image",[232,12723,12724],{},"内存+磁盘缓存",[311,12726,12728],{"className":4996,"code":12727,"language":4998,"meta":316,"style":316},"\u002F\u002F Android Coil 示例\nAsyncImage(\n    model = ImageRequest.Builder(LocalContext.current)\n        .data(\"https:\u002F\u002Fexample.com\u002Fphoto.jpg\")\n        .crossfade(true)\n        .size(200, 200)           \u002F\u002F 降采样到指定尺寸\n        .memoryCachePolicy(CachePolicy.ENABLED)\n        .diskCachePolicy(CachePolicy.ENABLED)\n        .build(),\n    contentDescription = null,\n    modifier = Modifier.size(200.dp),\n    placeholder = painterResource(R.drawable.placeholder),\n    error = painterResource(R.drawable.error),\n)\n",[192,12729,12730,12735,12740,12745,12750,12755,12760,12765,12770,12775,12780,12785,12790,12795],{"__ignoreMap":316},[320,12731,12732],{"class":322,"line":323},[320,12733,12734],{},"\u002F\u002F Android Coil 示例\n",[320,12736,12737],{"class":322,"line":329},[320,12738,12739],{},"AsyncImage(\n",[320,12741,12742],{"class":322,"line":335},[320,12743,12744],{},"    model = ImageRequest.Builder(LocalContext.current)\n",[320,12746,12747],{"class":322,"line":341},[320,12748,12749],{},"        .data(\"https:\u002F\u002Fexample.com\u002Fphoto.jpg\")\n",[320,12751,12752],{"class":322,"line":347},[320,12753,12754],{},"        .crossfade(true)\n",[320,12756,12757],{"class":322,"line":353},[320,12758,12759],{},"        .size(200, 200)           \u002F\u002F 降采样到指定尺寸\n",[320,12761,12762],{"class":322,"line":360},[320,12763,12764],{},"        .memoryCachePolicy(CachePolicy.ENABLED)\n",[320,12766,12767],{"class":322,"line":74},[320,12768,12769],{},"        .diskCachePolicy(CachePolicy.ENABLED)\n",[320,12771,12772],{"class":322,"line":371},[320,12773,12774],{},"        .build(),\n",[320,12776,12777],{"class":322,"line":377},[320,12778,12779],{},"    contentDescription = null,\n",[320,12781,12782],{"class":322,"line":383},[320,12783,12784],{},"    modifier = Modifier.size(200.dp),\n",[320,12786,12787],{"class":322,"line":388},[320,12788,12789],{},"    placeholder = painterResource(R.drawable.placeholder),\n",[320,12791,12792],{"class":322,"line":394},[320,12793,12794],{},"    error = painterResource(R.drawable.error),\n",[320,12796,12797],{"class":322,"line":400},[320,12798,6608],{},[201,12800,12801],{},[204,12802,12803],{},"关键设计考量：",[448,12805,12806,12812,12818,12824,12830,12836],{},[25,12807,12808,12811],{},[204,12809,12810],{},"内存缓存大小","：通常为可用内存的 1\u002F8",[25,12813,12814,12817],{},[204,12815,12816],{},"磁盘缓存大小","：通常 50-250MB",[25,12819,12820,12823],{},[204,12821,12822],{},"图片降采样","：根据 ImageView 实际尺寸解码，避免全尺寸解码浪费内存",[25,12825,12826,12829],{},[204,12827,12828],{},"请求合并","：相同 URL 的并发请求合并为一个",[25,12831,12832,12835],{},[204,12833,12834],{},"优先级管理","：可见区域的图片优先加载",[25,12837,12838,12841],{},[204,12839,12840],{},"生命周期绑定","：页面销毁时取消请求",[10,12843],{},[17,12845,12847],{"id":12846},"q224-设计一个聊天应用的消息系统websocket-本地存储","Q22.4: 设计一个聊天应用的消息系统（WebSocket + 本地存储）",[201,12849,12850],{},[204,12851,206],{},[311,12853,12856],{"className":12854,"code":12855,"language":2187},[2185],"┌────────────────────────────────────────────────────────────┐\n│                     Chat Architecture                       │\n│                                                            │\n│   UI Layer                                                 │\n│   ├── MessageList (observe local DB)                       │\n│   └── MessageInput → send()                                │\n│                                                            │\n│   Domain Layer                                             │\n│   └── ChatRepository                                       │\n│       ├── observeMessages() → Flow\u003CList\u003CMessage>>          │\n│       ├── sendMessage(text) → optimistic insert            │\n│       └── syncMessages() → pull missing                    │\n│                                                            │\n│   Data Layer                                               │\n│   ├── WebSocketManager (实时消息收发)                       │\n│   ├── MessageDao (本地持久化)                               │\n│   └── ChatApi (HTTP 补充：历史消息、文件上传)               │\n└────────────────────────────────────────────────────────────┘\n",[192,12857,12855],{"__ignoreMap":316},[311,12859,12861],{"className":4996,"code":12860,"language":4998,"meta":316,"style":316},"\u002F\u002F 消息模型\n@Entity\ndata class Message(\n    @PrimaryKey val id: String = UUID.randomUUID().toString(),\n    val chatId: String,\n    val senderId: String,\n    val text: String,\n    val timestamp: Long,\n    val status: MessageStatus = MessageStatus.SENDING\n)\n\nenum class MessageStatus { SENDING, SENT, DELIVERED, READ, FAILED }\n\n\u002F\u002F WebSocket 管理\nclass WebSocketManager(private val dao: MessageDao) {\n    private var socket: WebSocket? = null\n    private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)\n\n    fun connect() {\n        val request = Request.Builder().url(\"wss:\u002F\u002Fchat.example.com\u002Fws\").build()\n        socket = okHttpClient.newWebSocket(request, object : WebSocketListener() {\n            override fun onMessage(webSocket: WebSocket, text: String) {\n                val message = json.decodeFromString\u003CMessage>(text)\n                scope.launch { dao.insert(message) }  \u002F\u002F 收到消息存入本地\n            }\n\n            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {\n                _connectionState.value = ConnectionState.DISCONNECTED\n                reconnectWithBackoff()  \u002F\u002F 指数退避重连\n            }\n        })\n    }\n\n    \u002F\u002F 指数退避重连\n    private fun reconnectWithBackoff() {\n        scope.launch {\n            var delay = 1000L\n            repeat(10) {\n                delay(delay)\n                try { connect(); return@launch }\n                catch (e: Exception) { delay = minOf(delay * 2, 30_000L) }\n            }\n        }\n    }\n\n    fun send(message: Message) {\n        socket?.send(json.encodeToString(message))\n    }\n}\n\n\u002F\u002F Repository：乐观更新\nclass ChatRepository(\n    private val dao: MessageDao,\n    private val wsManager: WebSocketManager\n) {\n    fun observeMessages(chatId: String): Flow\u003CList\u003CMessage>> =\n        dao.observeMessages(chatId)\n\n    suspend fun sendMessage(chatId: String, text: String) {\n        val message = Message(\n            chatId = chatId,\n            senderId = currentUserId,\n            text = text,\n            timestamp = System.currentTimeMillis(),\n            status = MessageStatus.SENDING\n        )\n\n        \u002F\u002F 乐观插入：先显示在 UI 上\n        dao.insert(message)\n\n        try {\n            wsManager.send(message)\n            dao.updateStatus(message.id, MessageStatus.SENT)\n        } catch (e: Exception) {\n            dao.updateStatus(message.id, MessageStatus.FAILED)\n        }\n    }\n}\n",[192,12862,12863,12868,12873,12878,12883,12888,12893,12898,12903,12908,12912,12916,12921,12925,12930,12935,12940,12945,12949,12954,12959,12964,12969,12974,12979,12983,12987,12992,12997,13002,13006,13011,13015,13019,13024,13029,13034,13039,13044,13049,13054,13059,13063,13067,13071,13075,13080,13085,13089,13093,13097,13102,13107,13112,13117,13121,13126,13131,13135,13141,13147,13153,13159,13165,13171,13177,13183,13188,13194,13200,13205,13211,13217,13223,13229,13235,13240,13245],{"__ignoreMap":316},[320,12864,12865],{"class":322,"line":323},[320,12866,12867],{},"\u002F\u002F 消息模型\n",[320,12869,12870],{"class":322,"line":329},[320,12871,12872],{},"@Entity\n",[320,12874,12875],{"class":322,"line":335},[320,12876,12877],{},"data class Message(\n",[320,12879,12880],{"class":322,"line":341},[320,12881,12882],{},"    @PrimaryKey val id: String = UUID.randomUUID().toString(),\n",[320,12884,12885],{"class":322,"line":347},[320,12886,12887],{},"    val chatId: String,\n",[320,12889,12890],{"class":322,"line":353},[320,12891,12892],{},"    val senderId: String,\n",[320,12894,12895],{"class":322,"line":360},[320,12896,12897],{},"    val text: String,\n",[320,12899,12900],{"class":322,"line":74},[320,12901,12902],{},"    val timestamp: Long,\n",[320,12904,12905],{"class":322,"line":371},[320,12906,12907],{},"    val status: MessageStatus = MessageStatus.SENDING\n",[320,12909,12910],{"class":322,"line":377},[320,12911,6608],{},[320,12913,12914],{"class":322,"line":383},[320,12915,357],{"emptyLinePlaceholder":356},[320,12917,12918],{"class":322,"line":388},[320,12919,12920],{},"enum class MessageStatus { SENDING, SENT, DELIVERED, READ, FAILED }\n",[320,12922,12923],{"class":322,"line":394},[320,12924,357],{"emptyLinePlaceholder":356},[320,12926,12927],{"class":322,"line":400},[320,12928,12929],{},"\u002F\u002F WebSocket 管理\n",[320,12931,12932],{"class":322,"line":121},[320,12933,12934],{},"class WebSocketManager(private val dao: MessageDao) {\n",[320,12936,12937],{"class":322,"line":411},[320,12938,12939],{},"    private var socket: WebSocket? = null\n",[320,12941,12942],{"class":322,"line":416},[320,12943,12944],{},"    private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)\n",[320,12946,12947],{"class":322,"line":421},[320,12948,357],{"emptyLinePlaceholder":356},[320,12950,12951],{"class":322,"line":427},[320,12952,12953],{},"    fun connect() {\n",[320,12955,12956],{"class":322,"line":158},[320,12957,12958],{},"        val request = Request.Builder().url(\"wss:\u002F\u002Fchat.example.com\u002Fws\").build()\n",[320,12960,12961],{"class":322,"line":438},[320,12962,12963],{},"        socket = okHttpClient.newWebSocket(request, object : WebSocketListener() {\n",[320,12965,12966],{"class":322,"line":615},[320,12967,12968],{},"            override fun onMessage(webSocket: WebSocket, text: String) {\n",[320,12970,12971],{"class":322,"line":621},[320,12972,12973],{},"                val message = json.decodeFromString\u003CMessage>(text)\n",[320,12975,12976],{"class":322,"line":627},[320,12977,12978],{},"                scope.launch { dao.insert(message) }  \u002F\u002F 收到消息存入本地\n",[320,12980,12981],{"class":322,"line":935},[320,12982,3939],{},[320,12984,12985],{"class":322,"line":941},[320,12986,357],{"emptyLinePlaceholder":356},[320,12988,12989],{"class":322,"line":947},[320,12990,12991],{},"            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {\n",[320,12993,12994],{"class":322,"line":953},[320,12995,12996],{},"                _connectionState.value = ConnectionState.DISCONNECTED\n",[320,12998,12999],{"class":322,"line":959},[320,13000,13001],{},"                reconnectWithBackoff()  \u002F\u002F 指数退避重连\n",[320,13003,13004],{"class":322,"line":965},[320,13005,3939],{},[320,13007,13008],{"class":322,"line":970},[320,13009,13010],{},"        })\n",[320,13012,13013],{"class":322,"line":975},[320,13014,558],{},[320,13016,13017],{"class":322,"line":980},[320,13018,357],{"emptyLinePlaceholder":356},[320,13020,13021],{"class":322,"line":986},[320,13022,13023],{},"    \u002F\u002F 指数退避重连\n",[320,13025,13026],{"class":322,"line":992},[320,13027,13028],{},"    private fun reconnectWithBackoff() {\n",[320,13030,13031],{"class":322,"line":998},[320,13032,13033],{},"        scope.launch {\n",[320,13035,13036],{"class":322,"line":1003},[320,13037,13038],{},"            var delay = 1000L\n",[320,13040,13041],{"class":322,"line":1009},[320,13042,13043],{},"            repeat(10) {\n",[320,13045,13046],{"class":322,"line":1015},[320,13047,13048],{},"                delay(delay)\n",[320,13050,13051],{"class":322,"line":1021},[320,13052,13053],{},"                try { connect(); return@launch }\n",[320,13055,13056],{"class":322,"line":1027},[320,13057,13058],{},"                catch (e: Exception) { delay = minOf(delay * 2, 30_000L) }\n",[320,13060,13061],{"class":322,"line":1033},[320,13062,3939],{},[320,13064,13065],{"class":322,"line":1039},[320,13066,1042],{},[320,13068,13069],{"class":322,"line":1045},[320,13070,558],{},[320,13072,13073],{"class":322,"line":1050},[320,13074,357],{"emptyLinePlaceholder":356},[320,13076,13077],{"class":322,"line":1055},[320,13078,13079],{},"    fun send(message: Message) {\n",[320,13081,13082],{"class":322,"line":1060},[320,13083,13084],{},"        socket?.send(json.encodeToString(message))\n",[320,13086,13087],{"class":322,"line":1066},[320,13088,558],{},[320,13090,13091],{"class":322,"line":1072},[320,13092,350],{},[320,13094,13095],{"class":322,"line":1078},[320,13096,357],{"emptyLinePlaceholder":356},[320,13098,13099],{"class":322,"line":1084},[320,13100,13101],{},"\u002F\u002F Repository：乐观更新\n",[320,13103,13104],{"class":322,"line":1089},[320,13105,13106],{},"class ChatRepository(\n",[320,13108,13109],{"class":322,"line":1094},[320,13110,13111],{},"    private val dao: MessageDao,\n",[320,13113,13114],{"class":322,"line":1100},[320,13115,13116],{},"    private val wsManager: WebSocketManager\n",[320,13118,13119],{"class":322,"line":1106},[320,13120,5870],{},[320,13122,13123],{"class":322,"line":1112},[320,13124,13125],{},"    fun observeMessages(chatId: String): Flow\u003CList\u003CMessage>> =\n",[320,13127,13128],{"class":322,"line":1118},[320,13129,13130],{},"        dao.observeMessages(chatId)\n",[320,13132,13133],{"class":322,"line":7556},[320,13134,357],{"emptyLinePlaceholder":356},[320,13136,13138],{"class":322,"line":13137},59,[320,13139,13140],{},"    suspend fun sendMessage(chatId: String, text: String) {\n",[320,13142,13144],{"class":322,"line":13143},60,[320,13145,13146],{},"        val message = Message(\n",[320,13148,13150],{"class":322,"line":13149},61,[320,13151,13152],{},"            chatId = chatId,\n",[320,13154,13156],{"class":322,"line":13155},62,[320,13157,13158],{},"            senderId = currentUserId,\n",[320,13160,13162],{"class":322,"line":13161},63,[320,13163,13164],{},"            text = text,\n",[320,13166,13168],{"class":322,"line":13167},64,[320,13169,13170],{},"            timestamp = System.currentTimeMillis(),\n",[320,13172,13174],{"class":322,"line":13173},65,[320,13175,13176],{},"            status = MessageStatus.SENDING\n",[320,13178,13180],{"class":322,"line":13179},66,[320,13181,13182],{},"        )\n",[320,13184,13186],{"class":322,"line":13185},67,[320,13187,357],{"emptyLinePlaceholder":356},[320,13189,13191],{"class":322,"line":13190},68,[320,13192,13193],{},"        \u002F\u002F 乐观插入：先显示在 UI 上\n",[320,13195,13197],{"class":322,"line":13196},69,[320,13198,13199],{},"        dao.insert(message)\n",[320,13201,13203],{"class":322,"line":13202},70,[320,13204,357],{"emptyLinePlaceholder":356},[320,13206,13208],{"class":322,"line":13207},71,[320,13209,13210],{},"        try {\n",[320,13212,13214],{"class":322,"line":13213},72,[320,13215,13216],{},"            wsManager.send(message)\n",[320,13218,13220],{"class":322,"line":13219},73,[320,13221,13222],{},"            dao.updateStatus(message.id, MessageStatus.SENT)\n",[320,13224,13226],{"class":322,"line":13225},74,[320,13227,13228],{},"        } catch (e: Exception) {\n",[320,13230,13232],{"class":322,"line":13231},75,[320,13233,13234],{},"            dao.updateStatus(message.id, MessageStatus.FAILED)\n",[320,13236,13238],{"class":322,"line":13237},76,[320,13239,1042],{},[320,13241,13243],{"class":322,"line":13242},77,[320,13244,558],{},[320,13246,13248],{"class":322,"line":13247},78,[320,13249,350],{},[10,13251],{},[13,13253,13255],{"id":13254},"附录高频面试知识点速查","附录：高频面试知识点速查",[17,13257,13259],{"id":13258},"ios-核心","iOS 核心",[208,13261,13262,13272],{},[211,13263,13264],{},[214,13265,13266,13269],{},[217,13267,13268],{},"主题",[217,13270,13271],{},"关键词",[227,13273,13274,13282,13289,13296,13304,13311],{},[214,13275,13276,13279],{},[232,13277,13278],{},"Swift 语言",[232,13280,13281],{},"struct vs class、POP、enum 关联值、some\u002Fany、@propertyWrapper",[214,13283,13284,13286],{},[232,13285,37],{},[232,13287,13288],{},"ARC、weak\u002Funowned、闭包 capture list、autoreleasepool",[214,13290,13291,13293],{},[232,13292,2208],{},[232,13294,13295],{},"@State\u002F@StateObject\u002F@ObservedObject、声明式 UI、body 重算",[214,13297,13298,13301],{},[232,13299,13300],{},"并发",[232,13302,13303],{},"async\u002Fawait、Actor\u002F@MainActor、Sendable、TaskGroup",[214,13305,13306,13308],{},[232,13307,1495],{},[232,13309,13310],{},"Instruments、离屏渲染、图片降采样、启动优化",[214,13312,13313,13316],{},[232,13314,13315],{},"系统",[232,13317,13318],{},"RunLoop、App Lifecycle、Scene、Background Task",[17,13320,13322],{"id":13321},"android-核心","Android 核心",[208,13324,13325,13333],{},[211,13326,13327],{},[214,13328,13329,13331],{},[217,13330,13268],{},[217,13332,13271],{},[227,13334,13335,13343,13351,13359,13367,13375],{},[214,13336,13337,13340],{},[232,13338,13339],{},"Kotlin 语言",[232,13341,13342],{},"data\u002Fsealed\u002Fobject class、作用域函数、委托、inline\u002Freified",[214,13344,13345,13348],{},[232,13346,13347],{},"组件",[232,13349,13350],{},"Activity\u002FFragment 生命周期、ViewModel、配置变更",[214,13352,13353,13356],{},[232,13354,13355],{},"Compose",[232,13357,13358],{},"Recomposition、remember、derivedStateOf、Stability",[214,13360,13361,13364],{},[232,13362,13363],{},"Coroutines",[232,13365,13366],{},"结构化并发、Flow 冷热流、StateFlow\u002FSharedFlow",[214,13368,13369,13372],{},[232,13370,13371],{},"Jetpack",[232,13373,13374],{},"Room、Hilt、WorkManager、Navigation、Paging",[214,13376,13377,13379],{},[232,13378,1495],{},[232,13380,13381],{},"Baseline Profile、LeakCanary、R8、Macrobenchmark",[17,13383,13385],{"id":13384},"flutter-核心","Flutter 核心",[208,13387,13388,13396],{},[211,13389,13390],{},[214,13391,13392,13394],{},[217,13393,13268],{},[217,13395,13271],{},[227,13397,13398,13406,13414,13421,13429],{},[214,13399,13400,13403],{},[232,13401,13402],{},"Dart 语言",[232,13404,13405],{},"Null Safety、Isolate、事件循环、Records\u002FPatterns",[214,13407,13408,13411],{},[232,13409,13410],{},"渲染",[232,13412,13413],{},"三棵树、Constraints、RepaintBoundary、Impeller",[214,13415,13416,13418],{},[232,13417,139],{},[232,13419,13420],{},"BLoC vs Riverpod、InheritedWidget 原理",[214,13422,13423,13426],{},[232,13424,13425],{},"平台通信",[232,13427,13428],{},"MethodChannel\u002FEventChannel\u002FFFI、Pigeon",[214,13430,13431,13433],{},[232,13432,1495],{},[232,13434,13435],{},"const Widget、ListView.builder、AnimatedBuilder child",[17,13437,13438],{"id":13438},"跨平台通用",[208,13440,13441,13449],{},[211,13442,13443],{},[214,13444,13445,13447],{},[217,13446,13268],{},[217,13448,13271],{},[227,13450,13451,13459,13466,13474],{},[214,13452,13453,13456],{},[232,13454,13455],{},"网络",[232,13457,13458],{},"HTTPS\u002FTLS、证书固定、安全存储",[214,13460,13461,13463],{},[232,13462,4018],{},[232,13464,13465],{},"Clean Architecture、单向数据流、离线优先",[214,13467,13468,13471],{},[232,13469,13470],{},"系统设计",[232,13472,13473],{},"推送通知、图片缓存、WebSocket 聊天",[214,13475,13476,13479],{},[232,13477,13478],{},"CI\u002FCD",[232,13480,13481],{},"自动化测试、代码签名、分发",[13483,13484,13485],"style",{},"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":316,"searchDepth":329,"depth":329,"links":13487},[13488,13494,13506,13510,13516,13520,13523,13526,13531,13538,13543,13548,13552,13556,13560,13564,13568,13571,13574,13577,13580,13584,13587,13593],{"id":15,"depth":329,"text":15,"children":13489},[13490,13491,13492,13493],{"id":19,"depth":335,"text":20},{"id":70,"depth":335,"text":71},{"id":117,"depth":335,"text":118},{"id":154,"depth":335,"text":155},{"id":185,"depth":329,"text":186,"children":13495},[13496,13498,13500,13502,13504],{"id":189,"depth":335,"text":13497},"Q1.1: struct 和 class 的区别？什么时候用哪个？",{"id":495,"depth":335,"text":13499},"Q1.2: Swift 中的 Protocol 和 Protocol-Oriented Programming 是什么？",{"id":802,"depth":335,"text":13501},"Q1.3: Swift 中的 enum 有哪些高级用法？",{"id":1125,"depth":335,"text":13503},"Q1.4: 解释 Swift 的 @propertyWrapper",{"id":1353,"depth":335,"text":13505},"Q1.5: some 和 any 关键字的区别是什么？",{"id":1597,"depth":329,"text":1598,"children":13507},[13508,13509],{"id":1601,"depth":335,"text":1602},{"id":1930,"depth":335,"text":1931},{"id":2035,"depth":329,"text":2036,"children":13511},[13512,13513,13515],{"id":2039,"depth":335,"text":2040},{"id":2281,"depth":335,"text":13514},"Q3.2: SwiftUI 中 @State、@StateObject、@ObservedObject、@EnvironmentObject 的区别？",{"id":2619,"depth":335,"text":2620},{"id":2901,"depth":329,"text":2902,"children":13517},[13518,13519],{"id":2905,"depth":335,"text":2906},{"id":3346,"depth":335,"text":3347},{"id":3615,"depth":329,"text":3616,"children":13521},[13522],{"id":3619,"depth":335,"text":3620},{"id":4101,"depth":329,"text":4102,"children":13524},[13525],{"id":4105,"depth":335,"text":4106},{"id":4402,"depth":329,"text":4403,"children":13527},[13528,13529,13530],{"id":4406,"depth":335,"text":4407},{"id":4558,"depth":335,"text":4559},{"id":4687,"depth":335,"text":4688},{"id":4971,"depth":329,"text":4972,"children":13532},[13533,13535,13536,13537],{"id":4975,"depth":335,"text":13534},"Q8.1: Kotlin 中的 data class、sealed class、object、companion object 各有什么用？",{"id":5188,"depth":335,"text":5189},{"id":5520,"depth":335,"text":5521},{"id":5761,"depth":335,"text":5762},{"id":5939,"depth":329,"text":5940,"children":13539},[13540,13541,13542],{"id":5943,"depth":335,"text":5944},{"id":6110,"depth":335,"text":6111},{"id":6327,"depth":335,"text":6328},{"id":6613,"depth":329,"text":6614,"children":13544},[13545,13546,13547],{"id":6617,"depth":335,"text":6618},{"id":6925,"depth":335,"text":6926},{"id":7276,"depth":335,"text":7277},{"id":7563,"depth":329,"text":7564,"children":13549},[13550,13551],{"id":7567,"depth":335,"text":7568},{"id":7838,"depth":335,"text":7839},{"id":8115,"depth":329,"text":8116,"children":13553},[13554,13555],{"id":8119,"depth":335,"text":8120},{"id":8324,"depth":335,"text":8325},{"id":8531,"depth":329,"text":8532,"children":13557},[13558,13559],{"id":8535,"depth":335,"text":8536},{"id":8832,"depth":335,"text":8833},{"id":9021,"depth":329,"text":9022,"children":13561},[13562,13563],{"id":9025,"depth":335,"text":9026},{"id":9130,"depth":335,"text":9131},{"id":9310,"depth":329,"text":9311,"children":13565},[13566,13567],{"id":9314,"depth":335,"text":9315},{"id":9485,"depth":335,"text":9486},{"id":9711,"depth":329,"text":9712,"children":13569},[13570],{"id":9715,"depth":335,"text":9716},{"id":9881,"depth":329,"text":9882,"children":13572},[13573],{"id":9885,"depth":335,"text":9886},{"id":10388,"depth":329,"text":10389,"children":13575},[13576],{"id":10392,"depth":335,"text":10393},{"id":10744,"depth":329,"text":10745,"children":13578},[13579],{"id":10748,"depth":335,"text":10749},{"id":11028,"depth":329,"text":11029,"children":13581},[13582,13583],{"id":11032,"depth":335,"text":11033},{"id":11366,"depth":335,"text":11367},{"id":11516,"depth":329,"text":11517,"children":13585},[13586],{"id":11520,"depth":335,"text":11521},{"id":11987,"depth":329,"text":11988,"children":13588},[13589,13590,13591,13592],{"id":11991,"depth":335,"text":11992},{"id":12219,"depth":335,"text":12220},{"id":12662,"depth":335,"text":12663},{"id":12846,"depth":335,"text":12847},{"id":13254,"depth":329,"text":13255,"children":13594},[13595,13596,13597,13598],{"id":13258,"depth":335,"text":13259},{"id":13321,"depth":335,"text":13322},{"id":13384,"depth":335,"text":13385},{"id":13438,"depth":335,"text":13438},"2026-05-03 10:40:00 CST","覆盖 iOS、Android、Flutter 三端的面试核心考点，包括语言基础、内存管理、并发编程和架构设计。","md",{},"\u002Fnotes\u002F2026-05-03-mobile-engineer-interview",{"title":5,"description":13600},"移动端全栈工程师面试详解，涵盖 iOS Swift、Android Kotlin、Flutter Dart 三端的语言基础、架构、性能优化。","移动端全栈工程师面试题与详解｜个人笔记","mobile-engineer-interview","notes\u002F2026-05-03-mobile-engineer-interview","Wr4vwOGBGmmPVZBGAEFjZ5GcDK1_yvh78vh5TTUXQeQ",1777744305315]