【學(xué)習(xí)總結(jié)】03 | Auto Layout 是怎么進(jìn)行自動布局的,性能如何?

1、前言

首先,我認(rèn)為學(xué)習(xí)總結(jié),要有所總,所結(jié),就是有歸納后,能用自己的話告訴別人!有所結(jié),就是有所收獲輸出,一般我認(rèn)為是思維導(dǎo)圖,所以,每篇文章前,我都會先給出文章的腦圖:

iOS開發(fā)高手課-03-AutoLayout是怎么進(jìn)行自動布局的,性能如何?.png

2、正文

注意,本系列總結(jié)不會引用或提供原課程文章所有的內(nèi)容或代碼,只會作出思維導(dǎo)圖,需要學(xué)習(xí)可購買課程 《iOS開發(fā)高手課 - 極客時間》

帶問題找答案

  1. Auto Layout 如何實現(xiàn)自動布局的?
  2. 這種布局算法真的會影響性能嗎?
  3. 應(yīng)該選擇手動布局還是選擇Auto Layout呢?

文中提了3個問題,那么這3個問題怎么解答呢?可以利用第一篇文章說的,一個知識點的方法論來解答。這里就不展開了,具體可以查看 如何建立你自己的開發(fā)知識體系 | iHTCboy's blog

Auto Layout

  1. 為什么需要 Auto Layout
  2. 什么是 Auto Layout
  3. 怎么使用 Auto Layout
  4. 使用 Auto Layout 時注意的問題
  5. Auto Layout 的應(yīng)用領(lǐng)域
  6. Auto Layout 的優(yōu)缺點
  7. Auto Layout 觸類旁通

1. 為什么需要 Auto Layout

為什么需要?一般遇到解答不了的問題,可以試試逆向!,那就反推,就是 沒有 Auto Layout 之前是怎么樣的 ?沒有 Auto Layout 時,我們是通過設(shè)置元素的 Frame 來手動指定界面布局的大小和位置。

剛開始,大家的App并不復(fù)雜,頁面布局也很簡單,并且有一個歷史原因,就是 iPhone4/s(960x640像素)時代 (更早的 iPhone 3G/S,3G網(wǎng)絡(luò),S是速度 Speed,因為08、09年那會,一般國內(nèi)開發(fā)者估計都沒有見過,所以這里就簡單提一下。) ,iPhone 的寬度都是 640 像素, 開發(fā)時用 320 個點計算, 直到 2012年9月發(fā)布 iPhone5 (1136x640像素),屏幕高度增長了!4寸,當(dāng)時三星都開始5寸大屏了,大家當(dāng)年吐槽iPhone長的圖片大家可以搜索看看!此時此刻,適配 iPhone5 依然并沒有太大難度,蘋果默認(rèn)針對沒有適配 iPhone5 的App,在 iPhone5 打開App時,上下2端黑屏,這樣來過度。

就在當(dāng)年,2012年的 WWDC2012 蘋果發(fā)布了 Auto Layout 技術(shù),從 iOS6 以后開始支持(Xcode4)。2013年9月發(fā)布 iPhone5s,但是大多數(shù)的開發(fā)者還是習(xí)慣使用傳統(tǒng)的UI布局方式,大家的開發(fā)App布局并不會覺得麻煩,直到2014年才發(fā)生變化!

2014年9月,蘋果發(fā)布了 iPhoe 6(1334x750像素)、iPhone 6 plus(1920x1080像素),屏幕適配工作變得非常必要!如果用計算數(shù)值的方法工作量增加了幾倍。因為4個尺寸的屏幕不一樣!iPhone4/s(960x640像素)、iPhone 5/s (1136x640像素)、iPhoe 6(1334x750像素)、iPhone 6 plus(1920x1080像素),雖然大家還是可以按比例計算做縮放,但是這樣并不能解決所有問題,因為如果想像素級還原設(shè)計、效果圖調(diào)整尺寸等,可能都需要重新手動計算一次。所以,這樣算過程,應(yīng)該是2014年后才是 Auto Layout技術(shù)被大家廣泛應(yīng)用。我在 GitHub 查看了 iOS 最經(jīng)典的 Masonry 庫是 2013年7月22號 創(chuàng)建的,也是符合這個技術(shù)歷史的進(jìn)程啊~ (ps: Masonry 源碼值得研究學(xué)習(xí),有很多可學(xué)習(xí)的知識,詳細(xì)搜索引擎一下,已經(jīng)有很多好的文章啦~)

那么到此,大家明白了,為什么需要 Auto Layout 了嗎?

2. 什么是 Auto Layout

Auto Layout 是一種基于約束的、描述性的布局系統(tǒng)。也就是使用約束條件來描述布局,View 的 Frame 會根據(jù)這些描述來進(jìn)行計算。

3. 怎么使用 Auto Layout

2012年,Xcode4,iOS6 引入了 NSLayoutConstraint 類,并且 VFL (Visual Format Language,視覺格式語言) 的方式創(chuàng)建約束。通過下面2個方式來生成布局約束組:

+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
+(id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

關(guān)于具體使用和 VFL 相關(guān)使用可以查看官方文檔:Auto Layout Guide: Visual Format Language,所有學(xué)習(xí)資料官方文檔是第一手!

4. 使用 Auto Layout 時注意的問題

Constraint Churn(約束流失)
  1. Avoid removing all constraints (避免刪除所有約束)
  2. Add static constraints once (僅一次添加靜態(tài)約束,且不要再改變它們)
  3. Only change the constraints that need changing(只更改需要更改的約束)
  4. Hide views instead of removing them(隱藏視圖而不是刪除它們)

注:來自:High Performance Auto Layout - WWDC 2018 - Videos - Apple Developer

Summary
  1. Stack Views help build easily maintainable layouts (堆棧視圖有助于構(gòu)建易于維護(hù)的布局)
  2. Use activate and deactivate for constraints (使用激活和停用來約束)
  3. Determine size through constraints (通過約束確定尺寸)
  4. Override intrinsicContentSize judiciously (明智地覆蓋內(nèi)在內(nèi)容大小)
  5. Use priorities to properly solve your layout (使用優(yōu)先級來正確解決您的布局)
  6. Alignment goes beyond top, bottom, and center (對齊超出頂部,底部和中心)
  7. Keep localization in mind (記住本地化)

注:來自:Mysteries of Auto Layout, Part 1 - WWDC 2015 - Videos - Apple Developer

以上注意事項來自 WWDC,具體視頻可參考文章末尾的引用來源。這里不打算詳細(xì)解說,因為視頻真的說的很好,推薦大家去看看啊。

5. Auto Layout 的應(yīng)用領(lǐng)域

Auto Layout 其實,除了可以用代碼來創(chuàng)建,蘋果的可視化布局,也有使用,nib,xib,storyboard,那么大家對這3個東西了解熟悉嗎?

Interface Builder 工具

Interface Builder 在 Xcode 4 之前,是一個獨立的軟件,Xcode 4 開始集成到Xcode 中的。這個大家現(xiàn)在比較熟悉,就是可以用鼠標(biāo)以圖形化的方式,拖拉元素來創(chuàng)建UI界面。

NIB、XIB 區(qū)別

Xcode 3 前,Interface Builder 創(chuàng)建的文件是 NIB(二進(jìn)制格式,NeXT Interface Builder),不利于版本控制。

Xcode 3 開始,Interface Builder 使用了一種新的文件格式 XIB(XML文本格式,OS X Interface Builder或XML Interface Builder),XIB在工程編譯時被轉(zhuǎn)換成NIB;

XIB 文件

XIB 是一個描述文件,包含了用戶界面及相關(guān)元素;一個 XIB 文件對應(yīng)一個ViewController,也可以通過使用XIB來自定義View。

StoryBoard 故事板

iOS 5 (Xcode3)開始,Apple提供了一種全新的布局界面方式 StoryBoard 來拖拉創(chuàng)建界面;StoryBoard 是一組 ViewController 對應(yīng)的 XIB,以及它們之間的轉(zhuǎn)換方式的集合;在StoryBoard 中,不僅可以看到每個 ViewController 的布局樣式,也可以知道各個ViewController 之間的轉(zhuǎn)換關(guān)系。

對于2013~2015年,當(dāng)然非常的缺乏 iOS 開發(fā)者,所以一般的公司只有一個 iOS 開發(fā),那么這時候,StoryBoard 就是最快速的創(chuàng)建界面的工具!此時,面對多種設(shè)備時,Auto Layout 就是錦上添花,可以大幅提高 UI 開發(fā)效率,一次性做出適合所有屏幕尺寸的 UI。

現(xiàn)在,對使用 StoryBoard + Auto Layout 還是使用 代碼 + Masonry/SnapKit,依然沒有最終的答案,因為各有優(yōu)缺點。

6. Auto Layout 的優(yōu)缺點

Auto Layout的優(yōu)點不用多說,解決手動計算每個屏幕尺寸的布局,提高工程效率!缺點的話,大概就是適當(dāng)?shù)膶?dǎo)致性能降低?

Auto Layout 導(dǎo)致性能降低?是嗎?為什么是?為什么不是?前面的“什么是 Auto Layout”只是簡單的簡介,Auto Layout 是一個 布局系統(tǒng),沒有深入的介紹,不知道大家有沒有讀到那里時,產(chǎn)生疑問或興趣呢?

所以,要知道 Auto Layout 的優(yōu)缺點,還需要深入了解它的原理,才能理解優(yōu)缺點!否則,死記硬背過后還是不明不白。Auto Layout 是一套 布局引擎系統(tǒng),叫作 Layout Engine ,是 Auto Layout 的核心。了解 Layout Engine 的布局原理,是理解它的性能(優(yōu)缺點)的基礎(chǔ)。

所以在這里補(bǔ)充一下,主要參考蘋果官方的 WWDC 視頻來解說 Layout Engine,引用主講者 Jason Yao 說的:我們并不只想說這樣不好,我們相信大家真正的理解它,理解這個過程!剝開表面!了解真正發(fā)生了什么?

The Render Loop (渲染循環(huán))
01-The-Render-Loop.jpg

布局引擎是工作流程如上圖所示,Update Constraints(更新約束)流,從父視圖的約束開始更新,傳遞到子視圖,再到子子視圖,這里有2點要說明,一是這個更新約束只是從有約束變化的視圖開始,并不是所有視圖都更新;二是這個傳遞過程為什么是從父級開始,因為當(dāng)一個約束變化時,這個約束是自己與父級的關(guān)系或自己與子級的關(guān)系,所以會通知給子級視圖來響應(yīng)!Layout(布局)流,是從子視圖開始布局,為什么是反向呢?一個視圖的布局,它一定是由自己和所有的子視圖組成,那自己的布局一定是受子視圖的布局和約束影響,所以先確認(rèn)所有子視圖的約束,那自己的視圖就確實了,這個可能有點繞,大家可以看看原視頻來理解。Display (渲染顯示)流,因為子視圖布局確定了,那顯示的大小和位置就能確定,所以也是從子視圖開始顯示。

What is updateConstraints?(什么是 updateConstraints ?)
02-The-Render-Loop.jpg

updateConstraints() 是視圖的約束更新時會調(diào)用的方法,可以重寫這個方法來自行設(shè)置約束條件。圖中列出了 Update Constraints(更新約束)、Layout(布局)、Display (渲染顯示) 的對應(yīng)關(guān)系的方法。這里與第一個圖并不是對應(yīng)的關(guān)系!而是這3個狀態(tài)的生命周期分別有對應(yīng)的方法來響應(yīng),要怎么理解?

要理解這個圖,最簡單是從了解 layoutSubviews()、setNeedsLayout()、layoutIfNeeded() 三者的關(guān)聯(lián)和作用,明白這3個方法的作用,那么就知道這個生命周期是什么意思。

setNeedsLayout():當(dāng)一個UIView對象此方法時,實際上等同于做了一個標(biāo)記,告訴系統(tǒng)需要重新布局,但不會立刻執(zhí)行,直到 drawing cycle 循環(huán)到達(dá)該節(jié)點時,才會調(diào)用layoutSubviews() 方法重新布局。

layoutIfNeeded():允許在 drawing cycle 循環(huán)到達(dá)該節(jié)點之前,就立刻執(zhí)行布局刷新調(diào)用 layoutSubviews() 方法。

layoutSubviews():在上面2個方法調(diào)用后,都會被調(diào)用。另外,當(dāng) addSubviewsize 改變或滑動UIScrollView、旋轉(zhuǎn)Screen等都會觸發(fā)。

回到 updateConstraints()、setNeedsUpdateConstraints()updateConstraintsIfNeeded(),那它們的作用也是對應(yīng)的關(guān)系?,F(xiàn)在剩下的問題就是,為什么需要這些方法???我們在開發(fā)中,是不是會改變視圖后,更馬上刷新這個頁面就會用到這些方法。那么同時,我們改變(更新)約束后,是不是也希望刷新約束?所以,這些方法的作用就是這樣,那么為什么要這樣做,有什么好處呢?因為約束更新,需要重新計算一次,那我們可以自己來控制要不要更新,能減少Constraint Churn(約束流失),減少性能損耗。為什么?舉例為說,你給一個Lable設(shè)置了新的字體,此時要不要馬上更新約束?如果你下一句代碼是設(shè)置新的字體大小呢?所以,你可以控制一組約束,什么時候才更新,現(xiàn)在明白了這些方法和它們對應(yīng)的關(guān)系了吧。剩下的 draw(_:)setNeedsDisplay() 也是同理,只是層級更底,渲染的調(diào)用刷新。

Activating a Constraint(激活一個約束)
03-Activating-a-Constraint.jpg

說了這么多,好像還沒有說到 Layout Engine 布局引擎的工作流程。這個圖就是這個流程,每個 Window 下有一個 Engine(引擎),那么 View(視圖) 包含 Variable(變量)和 Constraint(約束),那么 Engine 與 View 之間通過 Equation(等式) 聯(lián)系。

那么要理解布局引擎的工作,其實很簡單,就是Constraint約束描述了View視圖的位置和大?。╯ize),那么就通過 Variable(變量)來向引擎獲取,需要獲取什么?
minY minX height width,那現(xiàn)在引擎需要做的就是把約束解析出這4個值,所以就是通過 Equation(等式)求解。Equation等式其實也很容易理解,就是像下面這些:

 text1.minX = 8
 text1.width = 100
 text2.minX = text1.minX + text1.width +20

最后解出等式,等到結(jié)果:

 text1.minX = 8
 text1.width = 100
 text2.minX = 128

所以,布局引擎的核心就是這個等式的計算,我們初中就算了一元二次方程式,后來學(xué)習(xí)了多次方程式求解,是不是覺得很簡單?那么這些數(shù)學(xué)問題,我們讀書時就知道數(shù)字題目答案只有一個,但是解法有很多!,所以原文章提到 Cassowary 算法、Simplex 算法 就是解決這個方程式求解的問題。我們?nèi)丝梢宰鲆恍}操作,但是要程序操作,一定是通用的解法,這也是算法為什么難!數(shù)學(xué)為什難!因為找到規(guī)律的人,往往是牛逼的人!

Equation(等式)求解的結(jié)果,通過 setNeedsLayout() 通知所有的視圖,視圖通過 UIView.layoutSubviews() 方法從引擎中復(fù)制數(shù)據(jù)到子視圖(Copying data from engine to subview)。

所以,這個就是整個引擎工作的過程!不知道我說的明白不明白!理解了這個過程,就能回復(fù)前面的 Auto Layout 會導(dǎo)致性能降低?從原理上說,引擎的工作方式和我們開發(fā)者手動計算的過程是一樣的!導(dǎo)致性能降低的原因,是因為一些約束流失(Constraint Churn)、不可滿足約束(Unsatisfiable Constraints)等導(dǎo)致消耗大量計算??赡苣銜幸蓡枺簽槭裁匆娌粫錾凳履兀勘热缥易约河嬎愕牟季?,我都緩存了,引擎做了嗎?答案是肯定的,Engine is a layout cache and tracker(引擎是一個布局緩存和約束跟蹤器),也就是你設(shè)置的布局,如果不必更新是不會更新約束的;同時,約束更新時引擎會知道那些約束需要重新計算,那么不需要再計算!所以,引擎系統(tǒng)導(dǎo)致性能降低本質(zhì)來說可以忽視,剩下的就是,如何避免我們設(shè)置約束時不合理導(dǎo)致性能降低呢?可以參考前面一節(jié):“4. 使用 Auto Layout 時注意的問題”,詳細(xì)的所有原則這里就不展開了,下面會重點提幾個。到此,引擎的原理問題算是解決啦!

Building a More Performant Layout(構(gòu)建一個更好的性能布局)
04-Building-a-More-Performant-Layout.jpg

這些列舉了我們常見的 TableView 滾動卡頓的問題,其實我們知道原因,就是滾動過程中 Cell 要重繪,需要我們知道 Cell 可以重用,但是每個 Cell 的布局和長度可能千變?nèi)f化,所以卡頓的問題,有一個就是 Cell 變化過程中,重新布局的問題。首先,避免刪除所有約束,因為所有約束重新計算可能不是必要的,比如上圖的用戶頭像的位置和大小,固定約束后就不要去改變它啦!那么,可能有一些 Cell 有圖片,有一些 Cell 沒有時,可以通過 setHidden: 方法 和 noImageConstraints 圖片的單獨約束來控制,這樣,盡最大可以減少約束的計算,導(dǎo)致性能的降低,從而盡可能的避免卡頓!另外,使用 Auto Layout 可以多使用 Compression Resistance PriorityHugging Priority,利用優(yōu)先級的設(shè)置,讓布局更加靈活,代碼更少,更易于維護(hù)。

以上內(nèi)容來源: High Performance Auto Layout - WWDC 2018 - Videos - Apple Developer

Independent Sibling Views(獨立的兄弟視圖)
05-Independent-Sibling-Views.jpg

前面說到 如何避免我們設(shè)置約束時不合理導(dǎo)致性能降低呢?我們已經(jīng)知道引擎需要計算方程式求解,對于每個元素視圖單獨約束,相互不依賴時,其實就是解決一元一次方程式,所以就是一條直線。對于我們開發(fā)來說,避免約束的相互依賴就能減少性能消耗,所以我們設(shè)置約束時,能不依賴的約束的視圖,讓他們保持獨立!就是最優(yōu)解!

Dependent Sibling Views(互相依賴的兄弟視圖)
06-Dependent-Sibling-Views.jpg

對于相互依賴的約束,它們就構(gòu)形了多元方程式,依賴關(guān)系越多,曲線就越陡峭。所以,我們能做的還是一樣,盡量減少多個視圖之前的約束依賴!

Nested Views(嵌套的視圖)

07-Nested-Views.jpg

對于嵌套的視圖,同理,減少視圖的嵌套,減少嵌套的層級,都是解決性能的重要手段!

以上內(nèi)容來源:What's New in Cocoa Touch - WWDC 2018 - Videos - Apple Developer 。更多的技巧,可以參考前面一節(jié):“4. 使用 Auto Layout 時注意的問題”。

Building the Layout(構(gòu)建布局)
08-Building-the-Layout.jpg

前面只是簡單的說明了引擎就是計算出視圖的位置和大小,那么具體是怎么計算的呢?這個圖片顯示了布局引擎的工作流。每個視圖在得到自己的布局之前,Layout Engine 會將視圖、約束、優(yōu)先級、固定大小等通過計算轉(zhuǎn)換成最終的位置和大小。

09-Building-the-Layout.jpg

所以,最終的 Layout Engine 計算到布局就是這樣的過程,細(xì)節(jié)點還有很多。還是那句話,授人以魚不如授人以漁!大家有“漁”后,自然要自己捉魚!,這里就不詳細(xì)解進(jìn)了,可觀看 WWDC 視頻:Mysteries of Auto Layout, Part 1 - WWDC 2015 - Videos - Apple Developer

最后,關(guān)于 Auto Layout 還有很多知識,比如怎么調(diào)試 Auto Layout 的 Debug?可以查看 What's New in Auto Layout - WWDC 2016 - Videos - Apple Developer 視頻。Auto Layout 的流程(The Layout Cycle),可以查看 Mysteries of Auto Layout, Part 2 - WWDC 2015 - Videos - Apple Developer。

7. Auto Layout 觸類旁通

UIStackView 與 Flexbox

UIStackView 是2015年 iOS9 蘋果推出的一套 API,它可以很好地減輕手動寫或拖 constraint 帶來的重復(fù)繁瑣的工作,也可以自動化的處理排列和元素個數(shù)的變化。(當(dāng)年因為需要iOS9+,導(dǎo)致很少有開發(fā)者使用,放在2020年的今年,這個控件可以熟悉一下?。。┡c Web 前端的 Flexbox 響應(yīng)式布局是一個原理。UIStackView 特點有下面4個:

  1. Easy to build(容易構(gòu)建)
  2. Easy to maintain (容易維護(hù))
  3. Composable Stack Views (可組合的堆棧視圖)
  4. Lightweight (輕量級)

這里并不打算講解 UIStackView 有多利害!確實它很利害。具體可以查看 WWDC 演示的Demo Mysteries of Auto Layout, Part 1 - WWDC 2015 - Videos - Apple Developer。

關(guān)于 Flexbox 的思想可以看看文章 30 分鐘學(xué)會 Flex 布局 - 知乎Flex 布局教程:語法篇 - 阮一峰的網(wǎng)絡(luò)日志,個人覺得文章寫得很好。另外關(guān)于 UIStackView 的強(qiáng)大就借 UIStackView 入坑指南 - 掘金 文章的一張圖來總結(jié)吧:

10-UIStackView-Layout.png
SwiftUI
  • 命令式編程(Imperative Programming):命令“機(jī)器”如何去做事情(how),這樣不管你想要的是什么(what),它都會按照你的命令實現(xiàn)。
  • 聲明式編程(Declarative Programming):告訴“機(jī)器”你想要的是什么(what),讓機(jī)器想出如何去做(how)。

SwiftUI聲明式編程,還有函數(shù)式編程、響應(yīng)式編程 等編程思想,這里就不說了。這里提的原因是,SwiftUI 采用不同的布局方式,但是依然使用 Auto Layout,并且 VStack 也是天合之作吧!

3、總結(jié)

首先,關(guān)于 iOS 、 Xcode 和對應(yīng)年份的關(guān)系,可以梳理一下,2012年 Xcode4 對應(yīng) iOS 6 ,2013年 Xcode5 和 iOS 7。它們相差2,所以,2020年將發(fā)布 Xcode12 和 iOS 14。這個就是一個數(shù)字游戲,可記可不記,就是想說明,記憶可以找規(guī)律的。

回到最前面提到的3個疑問題,你是不是已經(jīng)有了自己的答案了呢?

  1. Auto Layout 如何實現(xiàn)自動布局的?
  2. 這種布局算法真的會影響性能嗎?
  3. 應(yīng)該選擇手動布局還是選擇Auto Layout呢?

本章內(nèi)容夠不夠深不深入?這個大家的了解水平不一樣,如果覺得不夠深入,還是可以參考本文末的參考擴(kuò)展了解更多,因為 WWDC 視頻中提到了很多細(xì)節(jié)的東西,有一些很棒,有一些很有趣,這里就不一一列出。因為原文還提到了 Cassowary 算法Simplex 算法,本文并沒有把它作為主角色來解讀,為什么呢?因為,它并不是我們了解和理解 Auto Layout 最核心的必備知識,并且它對技術(shù)有要求,不可能每個人都能看懂,否則文章一上來就會讓讀者害怕,適得其反。所以,在本文的基礎(chǔ)上,如果大家還想深入了解,那這將是一道窗口,而不是一道門口!

以后關(guān)于 Auto Layout 的知識,你是不是能更好的跟別人講解呢?這些就是本文想要做的事情,當(dāng)然還有更深入的知識可以研究,切記這只是開始!修行在個人~


注:更多關(guān)于 iOS 開發(fā)和程序開發(fā)相關(guān)的內(nèi)容,可以查看系列,目前還在連載中 【學(xué)習(xí)總結(jié)】iOS開發(fā)高手課 -- (連載中) | iHTCboy's blog,以上,希望對你有用!

參考

WWDC:

Article:


  • 如有侵權(quán),聯(lián)系必刪!
  • 如有不正確的地方,歡迎指導(dǎo)!
  • 如有疑問,歡迎在評論區(qū)一起討論!


注:本文首發(fā)于 iHTCboy's blog,如若轉(zhuǎn)載,請注來源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容