11

?

[TOC]

1.引言

1.1目的

? 本文檔的目標用戶為iOS開發人者,提供在iOS項目開發過程中的代碼編寫規范,以提高代碼的可讀性、可維護性和可擴展性。

1.2術語與縮略語

  • 方法

    指object-c/swift中的class的實例方法(method)或類方法。

  • 函數

    object-c中允許與c語言混編,這里指獨立的c函數。

  • #pragma mark

    object-c中的編譯指令,導航中分隔相關屬性或方法,便于查找

  • //MARK:

    swift中的編譯指令,導航中分隔相關屬性或方法,便于查找

1.3參考資料

《Coding Guidelines for Cocoa》,Apple,https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html

《Objective-c-style-guide》,New York Times, Robots & Pencils, https://github.com/raywenderlich/objective-c-style-guide

2.命名規范

? 在進行項目開發工作時,我們經常需要定義一些新的類名、協議名、變量名、方法簽名、函數簽名、常量和枚舉等,在對這些元素進行命名的時候要盡量做到看詞達意,力求能夠精確描述其代表的含義或提供的功能,杜絕命名的二義性和隨意性。

2.1命名空間-前綴

? object-c/swift并沒有提供命名空間機制,為了防止命名沖突,可以在類名、協議名、常量名前加一個前綴防止與系統類庫或第三方類庫之間的命名沖突。前綴一般為大寫的二個或三個字母,可以為公司或項目名稱縮寫的大寫形式。

? 一個標準的帶前綴的命名格式形如:PREFIXNameType,PREFIX為前綴,Name為命名,Type為可選的類型名稱后綴,表示自定義類繼承于Cocoa的某個類。例如NS為蘋果前公司NeXTSTEP的縮寫,Cocoa庫中的好多類用它做前綴。再比如類名AECRegistrationViewController,AEC為前綴,Registration為類名,表示應用注冊功能的控制器,ViewController為后綴,表示該自定義類繼承自UIViewController。

? 不要在方法名中使用前綴,因為方法是屬于類的命名空間之下。同理,結構體的字段也不應使用前綴。

注:可以使用公司英文縮寫作為前綴,比如:USMARTLoginViewController

2.2類名與協議名

  • 類名和協議名采用大駱駝拼寫法,即每個單詞的第一個字母都需要大寫。
  • 類名和協議名使用前綴。其中AppDelegate類命名方法不應采用此規則,仍保持原有命名方式。
  • 類名和協議名中應使用一個名詞來描述該類或協議的主要功能或作用。
  • 協議名后面加Protocol或Delegate作為后綴。
  • 繼承于UIViewController的子類的命名添加后綴ViewController。
  • 繼承于UIView的子類的命名添加后綴View。
  • 用于業務數據處理的類添加后綴Model。
  • Delegate實例變量統一命名為delegate

2.3變量名

  • 變量名使用小駱駝拼寫法,即第一個單詞首字母小寫,其余單詞首字母大寫。第一單詞是常用縮寫除外。
  • 變量名要有意義,要能表達其所代表的含義。
  • 避免過多使用全局變量。
  • 變量的命名應當遵循apple設計初衷,盡量完整,少用縮寫
  • 對于一些不能夠通變量名來表征其類型的,需要通過后綴類型的方式讓使用者知道其類型,如下:
oc swift 例子
NSArrary Array userInformationArray
NSDictionary Dictionary userInformationDictionary

注:Objc和Swift通用

  • 對于常用基本數據類型應適當添加后綴或前綴,如下表:
類型 單精度浮點 全局 Static
命名規范 f后綴 g_前綴 s_前綴

2.4常量名

? 遵循小駱駝拼寫法,即首字母小寫,其它單詞首字母大寫

2.5枚舉

枚舉類型命名和其成員均使用大寫,以Objc為例如下:

注:swif靈活使用,此例子僅適用于Objc

2.6方法的命名規范

  • 使用小駱駝拼寫法,首字母小寫,其余單詞首字母大寫
  • 方法名命名要有意義,最好能夠表示方法所提供的功能。
  • 如果方法提供操作功能,則方法名以動詞開頭。
  • 對于objc如果方法名需要輸入參數,則方法名的最后一個關鍵字應當描述參數。Swfit則可以忽略這項

注:因為objc的語法特性,要求函數名和參數描述基本上要是完整的一句話描述。所以方法名的最后一個關鍵字應當描述緊跟其后的第一個參數。但是swift因為語法不同則不在此過多要求。

2.7圖片資源命名

圖片命名名全部小寫。命名可以分為三段式且中間應當以_來分割:

? 1.功能名稱

? 2.控件類型

? 3.圖片更詳細的使用目的描述,如顏色位置,及放在描述最末的圖片狀態等

例如:

? share_button_selected.png

? share_button_selected@2x.png

? share_button_selected@3x.png

注:后續同UI商定具體的命名規則。

2.8字符串資源命名

字符串資源的命名全部小寫。命名可以分為主要的四個部分并且以_來分隔:

? 1.功能模塊名稱

? 2.功能性控件名稱

? 3.系統控件名稱

? 4.顯示文字英文標示

例如:

? share_username_textfield_username = “Username”

*注:對于通用的字符串,應當使用common來做前綴。例如:common_ok = “Ok”

對于一些變量名字已經包含控件名稱的,定義時候可以去重處理.比如,變量名字為usernameTextField.那么定義字符串的時候就不用重復再寫textfield.*

2.9保留字

? 保留字又名關鍵字,為iOS系統專用,禁止用作變量名,類名,方法名。

3.代碼規范

3.1排版格式

3.1.1基本格式排版

類文件中代碼排布應當遵循整齊統一的原則,不能散亂。

? 1.ViewController文件排版

? 2.生命周期

? 3.IBActions方法

? 4.系統控件delegate方法,例如鍵盤相應,TableView delegate方法等

? 5.系統控件datasource方法

? 6.自定義delegate 和 notification方法

? 7.其它業務邏輯方法

? 8.View 文件排版

? 9.初始化方法

? 10.詳悉布局方法

? 11.同viewcontroller通訊方法

注:所有的實例變量聲明必須放在文件開頭部分

3.1.2詳細格式排版

? 1.文件說明與頭文件包含(import)之間空1行

? 2.頭文件包含(import)與@class(如果有聲明)、@interface和@protocol之間各空1行

? 3.如果聲明屬性和方法,先聲明屬性,后聲明方法。屬性聲明與方法聲明之間空1行,兩者內部如果需要分類區別,各類別之間空1行。

? 4.屬性聲明和方法聲明整體與@interface/@protocol和@end之間各空1行

? 5.方法實現與@implementation和@end之間各空1行。

? 6.方法實現之間空1行

? 7.#pragma mark或//MARK:與相關關系第一個方法之間不應有空行,而于其上方方法實現之間空1行

? 8.方法聲明和實現中-/+與返回值之間空一格

? 9.方法實現、if語句、switch語句、while語句和do-while語句中“{”在第一行以空格分開,“}”在另一行作為結束。

? 10.方法實現第一行代碼和最后一行代碼不應當和“{}”作用域符號間有空行

? 11.一元運算符如&!與操作數之間不能有空格,兩元運算符與兩個操作數之間以空格分隔。

? 12.三目運算符中每個運算符都應當和操作數中間有一個空格。

? 13.關鍵字與其后面的表達式用空格分隔。

3.2變量

3.2.1全局變量

盡可能少的使用全局變量

3.2.2實例變量

  • IBOutlet類型實例變量

    IBOutlet屬性實例變量名字需要和在xib或者storyboard中定義的名稱一直,以方便維護。

    swift中定義的IBOutlet屬性實例變量可以直接以!來聲明

  • 一般類型實例變量

    swift中自定義變量如果不明確其值,不能強制使用!來聲明,應當統一以?來聲明。在之后的訪問中需要使用有效性檢查來進行操作。

注:聲明屬性代替定義變量,方便系統內存管理。

3.3屬性

  • 除非需要,不要手動去synthesize屬性
  • 對于引用類型的屬性聲明,正確使用weak/strong特性,方便系統內存管理:代理、IBOutlet使用weak,NSString一般使用copy,其它情況一般使用strong。
  • 如果屬性需要只讀,則在聲明時候添加readOlny特性。
  • objc私有屬性必須放到匿名擴展類中。
  • swift私有屬性和方法使用private關鍵字。

3.4封裝

  • objc中需要暴露給外的接口和屬性,統一放到.h頭文件中
  • objc中的私有屬性和方法需要在.m文件中定義單獨的私有作用域。
  • Swift中的私有屬性和方法需要使用private關鍵字

3.5拒絕硬編碼

在編碼過程中應該杜絕硬編碼,即不直接使用字符串或數字在表達式里面。而改用能表達其意義或作用的局部變量或系統配置的方式作為代替,以提高代碼的可讀性和可維護性。常見硬編碼形式:

  • 數值類型的硬編碼
  • RGB色值的硬編碼
  • 字符串顯示定義的硬編碼
  • URL的硬編碼
  • 網絡狀態碼的硬編碼
  • 控件Frame的硬編碼

下圖以objc為例:

#define DEFAULT_AGE 10使用宏定義

RGB

Button.backgroundColor = [UIColor colorWithRed:38.0f/255.0f green:40.0f/255.0f blue:90.0f/255.0f]

Button.backgroundColor = UICOLORFROMRGB(38.0f, 40.0f, 90.0f, 1.0f) 

#define UICOLORFROMRGB(r, g, b, a) [UIColor colorWithRed:r/255.0f..]
使用宏定義

字符串

Label.text = “Login”

NSLocalizedStringFromTable (@”str_name”, @”StringTableName”, @”Comment”)

自定義字符串資源文件,對字符串資源統一管理

URL

loginRequest.baseURL = “https://……..”

loginRequest.baseURL = [URLManager getloginBaseUrl]

應當定義單獨的url管理工具類,針對各個需要的功能分別創建get方法。而domain域的baseurl需要在單獨的文件中區分不同的編譯模式來宏定義。比如可以分為DEBUG, TEST, PRODUCT來分別配置

網絡狀態碼

Case: 300 {

……..

}

Case: NETWORK_REDIRECTION

同樣需要以宏的形式定義

控件frame的設置

UIButton* button = [[UIButton alloc]initWithFrame:CGRectMake(30.0f, 50.0f, 120.0f, 80.0f)];

使用相對Frame或者約束

3.6圖片資源文件

? 在設置圖片資源的時候,文件名字和后綴必須都要寫全。例如,xxx.png xxx.gif 因為有可能同樣名稱的圖片資源,如果不添加后綴,往往引用不到指定的圖片資源。

3.7方法

3.7.1目標

? 根據單一職責原理,一個方法應當只做特定的操作或完成特定的任務,與方法名保持統一。同時,在方法體內不要出現太多層級的嵌套,如if、switch、for循環等之間的相互嵌套。

3.7.2方法組織

? 這一章節的目的在于更加快速定位方法集合并使得代碼組織更加清晰

3.7.2.1ViewController方法組織

以objc為例,建議使用如下組織方式對方法進行分類,方便定位和閱讀:

? 1.生命周期 #pragma mark - life cycle

? 2.IBActions方法 #pragma mark – IBActions

? 3.系統控件Delegate方法, 以tableview為例 #pragma mark – UITableViewDelegate

? 4.系統控件DataSource方法,以tableview為例 #pragma mark – UITextFieldDataSource

? 5.自定義public方法 #pragma mark – Public

? 6.自定義private方法 #pragma mark – Private

#pragma mark - life cycle

IBActions

#pragma mark – IBActions

系統控件Delegate方法

#pragma mark – UITableViewDelegate

系統控件DataSource方

#pragma mark – UITextFieldDataSource

自定義public方法

#pragma mark – Public

自定義private方法

#pragma mark – Private

3.7.2.2View方法組織

? 1.初始化方法 #pragma mark – Init

? 2.布局方法 #pragma mark – Layout

? 3.展示數據讀取方法 #pragma mark – Datas parse

? 4.和viewcontroller通訊Delegate #pragma mark – Delegate

組織模塊

組織命名

初始化方法

#pragma mark – Init

布局方法

#pragma mark – Layout

展示數據讀取方法

#pragma mark – Datas parse

Delegate

#pragma mark – Delegate

3.7.2.3Model方法組織

? 1.初始化方法 #pragma mark – Init

? 2.數據解析方法 #pragma mark – Datas parse

組織模塊

組織命名

初始化方法

#pragma mark – Init

數據解析方法

#pragma mark – Datas parse

3.7.3通用方法規范

  • 方法體與“{”,與“}”間不需要空行。
  • 方法體最大行數為100行,超長情況按功能進行拆分。
  • 如果方法體內嵌套if、switch、for或while語句,嵌套層數不能超過四層。層級過深的時候考慮拆分。
  • 黃金路徑:在使用條件分支時,當方法體不滿足繼續執行條件時,使用return語句結束函數。可以多次使用return。
  • 對方法入口參數進行有效性檢查。

3.8控制語句

控制語句包括if語句、switch語句、for循環語句、while語句和do-while語句。

  • 控制語句關鍵字與其后表達式用一個空格隔開。
  • 控制語句分支中的函數體必須用大括號括起來,一行代碼也需要。

3.8.1If語句中的條件運算符

? 如果遇到多個條件運算符的情況,需要以運算符為臨界換行輸出。

3.9單例

? 單例的實例對象需要采用線程安全的方式還創建,一般使用GCD的方式,例如:

+ (instancetype)sharedInstance {

   static id sharedInstance = nil;

   static dispatch_once_t onceToken;

   dispatch_once(&onceToken, ^{

      sharedInstance = [[self alloc] init];

   });  

   return sharedInstance;

}

3.10函數

? 函數的行數不能超過50行,縱向嵌套層數不能超過三層。

3.11CGRect函數

? 當訪問CGRect的屬性x、y、width、height的時候使用CGGeometry的函數,不要直接訪問其結構體。例如:

避免使用

CGFloat x = frame.origin.x;

CGFloat x = CGRectGetMinX(frame);

CGFloat y = frame.origin.y;

CGFloat y = CGRectGetMinY(frame);

CGFloat width = frame.size.width;

CGFloat width = CGRectGetWidth(frame);

CGFloat height = frame.size.height;

CGFloat height = CGRectGetHeight(frame);

CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

CGRect frame = CGRectMake(0.0, 0.0, width, height);

3.12注釋

3.12.1注釋方法

? 單行注釋使用“//”,多行則使用“/”與“/”組合

3.12.2注釋規則

? 1.注釋要與被注釋的內容保持一致,需要及時更新。

? 2.盡量做到代碼自解釋,少使用注釋,編寫framework或靜態庫除外。

? 3.注釋語言應當使用英文。

? 4.代碼對齊

? 5.代碼必須做對齊處理。

4.工程目錄組織結構

4.1主目錄結構

主要遵循MVC設計模式

? ViewControllers

? Widgets

? Models

4.1.1目錄結構功能劃分

  • ViewControllers

視圖控制器,主要處理view和model的通訊。

  • Widgets

視圖布局,主要處理視圖的具體排布和讀取展示數據已經向controller發送觸發消息。應當根據不同的功能模塊分別創建Group,并且以功能模塊來命名。

  • Models

數據層,應當僅包含各功能模塊業務邏輯數據處理。Models層應當根據不同的功能模塊分別創建Group,并且以功能模塊來命名。

4.1.2各目錄組織功能涵蓋

  • ViewControllers

    1.生命周期

    2.調用對應widgets初始化接口布局view

    3.系統控件delegate和datasource方法

    4.調用網絡request實例初始化網絡請求必須數據

    5.調用Model層網絡請求業務處理邏輯來發起請求。

    6.接收網絡請求處理狀態,分別以succed和failed方式來響應。

    7.將Model層回傳數據傳遞給view以供展示

  • Widgets

    1.布局控件

    2.讀取需展示內容

    3.向控制器發送事件trigger命令

  • Models

    1.網絡數據的請求數據抽象

    2.網絡數據的response數據抽象解析

    3.同控制器通訊傳遞數據

4.2擴展目錄結構

? 1.Helpers

? 2.Resource

? 3.ThirdParty

? 4.Externsions

? 5.Category

4.2.1目錄結構功能劃分

  • Helpers

    工具類,包含例如字符串,字典類型的有效性檢查等。

  • Resource

    字符串資源文件和圖片,音頻等文件的存放。

  • ThirdParty

    第三方庫,且未有通過cocoapods來加載。

  • Externsions

    類的擴展,比如自定義string的方法等。

  • Category

    類別,為需要的類添加新的方法。

4.2.2各目錄組織功能涵蓋

  • Helpers

    1.數據庫管理類,包含基本的增,刪,改,查等功能。

    2.DateFormatter工具類,包含字符串和Date類型的相互轉換,Date顯示格式的動態完成等功能。

    3.Objc中字符串,字典等常用collection類型的有效性檢查。

    4.UserDefault的工具類,包含設置,更新,清空等功能。

    5.Keychain的工具類,多見于用戶名和密碼的存儲。常用接口應提供設置,讀取,更新和清空功能。

    6.URL的管理類,為各個功能模塊提供所需的URL get 方法。

    7.全局性的宏定義類,包含不同編譯模式下的domain域Base URL設置;顏色值宏定義方法;網絡狀態碼的宏定義等。

  • Resource

    存放圖片,音視頻,字符串等資源文件

  • ThirdParty

    存放除去cocoapods外的其它第三方庫

5.工程設置規范

? 本章節主要涵蓋工程設置方面的規范,包含編譯模式的設定,文件路徑的設置等。均以objc為例,swift適用。

5.1多個編譯模式設置

5.1.1目標

? 在開發過程中,往往存在不同環境下的網絡請求URL訪問和日志過濾需求。那么就需要為工程配置相應的編譯模式切換條件。

5.1.2常見使用場景

預編譯宏

使用場景

  • DEBUG

? 調試版本下對應的網絡環境URL的訪問

? 日志信息的打印

  • TEST

? 送測版本下對應的網絡環境URL的訪問

? 日志信息的過濾

  • PRODUCTION

? 最終發布版本下對應的網絡環境URL的訪問

? 日志信息的過濾

5.2自定義日志信息

? 自定義日志文件而不直接使用系統日志文件的意義在于可以更好地做到日志輸出信息的自定義和日志過濾。

? 1.pch文件中簡單封裝,多見于輸出類名,方法名和行號的形式。

? 2.日志文件庫,擴展功能更加豐富,例如除去常見的日志輸出信息,還可以顯示日志輸出者,對日志等級的詳細劃分(warning, error…),時間戳等。

5.3路徑設置

5.3.1目標

? 在開發過程中,會用到第三方Framework等的使用需要。往往需要在工程中配置相應的搜索路徑。常見的路徑設置無外乎絕對路徑和相對路徑。使用絕對路徑雖然同樣可以達到預想的目的,但是在實際開發過程中,尤其是多人協作的過程中,使用絕對路徑往往會導致編譯失敗的常見問題。因此,使用相對路徑是需要注意的地方。

5.3.2常用相對路徑設置

名稱

代表路徑

實例

$(PROJECT_DIR)

當前項目完整路徑

/Users/delta-aec-app/Desktop/…

$(SRCROOT)

項目根目錄即 .xcodeproj文件所在的路徑

N/A

$(BUILT_PRODUCTS_DIR)

Build成功后的最終產品路徑

Build/xxx

$(CURRENT_PROJECT_VERSION)

當前項目版本號

N/A

6.版本管理

6.1目標

? 這一章節主要囊括了開發過程中版本管理方面的注意事項。目的在于更好地維護開發版本和后期維護的順利進行。

6.2版本號

? Version一般有2段或者3段式, 如:1.0, 1.0.0

? Ios規范版本號形式為三位形式,即:主版本號+副版本號+發布號

  • 基礎版本號

    版本號的初始值應當為v1.0.0

  • 版本號管理規則

    分段版本號

  • 變更規則

    主版本號 (Major Version)

    產品的主體構件進行重大修改,主版本號加1

    產品的主體構件之間的接口協議重大修改,主版本號加1

  • 副版本號(Minor version)

    1. 主版本號變更時,副版本號置0;

    2. 數據結構變更(新增或修改注釋含義的情況除外),副版本號加1;

    3. 若副版本號累加至超過20時,采用主版本號進位制,主版本號加1,副版本號重新置0。

    4. 有新增的小功能模塊時,副版本號加1;

  • 發布號(Release)

    平時的迭代送測版本,疊加1;

    1. 主版本號或副版本號變更時,Release號置0;
    2. 若發布的版本無數據結構變更,則Release號加1。

6.3版本送測

6.3.1打包版本

? 送測打包版本應當首先設置為TEST版本,即不應當開啟日志功能,即不能為DEBUG版本。

6.3.2打包工具

? 盡量不要使用手動大包的形式,應當使用諸如TestFlight工具來編譯發布。這樣很大程度上會提升送測的效率,使得送測點更加清晰。

6.4代碼提交

代碼提交習慣

  • 新的開發起始,要首先Update工程。
  • 對于沖突項,應和相關人一起查看Merge代碼。

提交代碼時如果有會引發多人協作編譯沖突類的文件,需要在提交初期做忽略處理。例如Cocoapods的提交,一般需要忽略CocoaPods的兩個文件,即Pods、Podfile.lock,但不能忽略*.a。當其他人員Checkout下來后,再進行pod install操作,就可以正常使用了

  • Commit信息要盡量對修改點進行描述,分兩類:
  • 普通修改:說明修改點。
  • Issue修改:應當遵循 #Issue number + 換行 + 補充描述的格式。

7.補充

7.1第三方庫的使用合法性

? 對于工程中可能出現大量使用第三方庫的情況,但是需要遵循一定的規則,才能做到合理,合法的使用。通常可以在其Git代碼首頁查看license或者readme中查找。

7.1.1開源性

? 需要保證代碼的開源性,如果有任何對商用需要收費限制甚至明確說明非開源,就不能使用。

7.1.2可維護性

? 使用的第三方庫盡可能用當下的主流程序,好處在于相關資料較易查找,并且出現問題會有新版本的不斷升級。

7.1.3授權協議

第三方庫必須有正規的授權協議,常見的協議有:

  • 協議名稱
  • 介紹
  • 權利
  • Apache (Apache License)

7.1.3.1Apache許可協議

  • 永久權利一旦被授權,永久擁有。
  • 全球范圍的權利在一個國家獲得授權,適用于所有國家。
  • 授權免費,且無版稅前期,后期均無任何費用。
  • 授權無排他性任何人都可以獲得授權
  • 授權不可撤消一旦獲得授權,沒有任何人可以取消。比如,你基于該產品代碼開發了衍生產品,你不用擔心會在某一天被禁止使用該代碼。

7.1.3.2BSD (Berkeley Software Distribution)

  • 伯克利軟件分發許可協議

  • 新 BSD 協議在軟件分發方面,除需要包含一份版權提示和免責聲明之外,沒有任何限制。

  • MIT (Massachusetts Institute of Technology)

  • MIT許可協議之名源自麻省理工學院,又稱“X許可協議”或“X11許可協議”

  • 你可以自由使用,復制,修改,可以用于自己的項目。

  • 可以免費分發或用來盈利。

  • 唯一的限制是必須包含許可聲明。

    GPL (GNU General Public License)

    GNU通用公共許可協議

  • 可自由復制**你可以將軟件復制到你的電腦,你客戶的電腦,或者任何地方。復制份數沒有任何限制。

  • 可自由分**發在你的網站提供他人下載,拷貝到U盤送人。

  • 可以用來盈利**你可以在分發軟件的時候收費,但你必須在收費前向你的客戶提供該軟件的 GNU GPL 許可協議,以便讓他們知道,他們可以從別的渠道免費得到這份軟件,以及你收費的理由。

  • 可自由修改**如果你想添加或刪除某個功能,沒問題,如果你想在別的項目中使用部分代碼,也沒問題,唯一的要求是,使用了這段代碼的項目也必須使用 GPL 協議。

  • 需要注意的是,分發的時候,需要明確提供源代碼和二進制文件,另外,用于某些程序的某些協議有一些問題和限制,使用 GPL 協議,你必須在源代碼代碼中包含相應信息,以及協議本身。

  • LGPL (GNU Lesser General Public License)

  • GNU寬通用公共許可協議

  • LGPL 適合那些用于非 GPL 或非開源產品的開源類庫或框架

  • MPL (Mozilla Public License)

  • Mozilla公共許可協議

注:在開發人員確定好要使用的第三方庫時,應當發送相關使用范圍說明文檔。文檔格式建議如下:

Developer

Project

ThirdParty

License

License Address

Ues

xxx

iPEMS

AFNetworking

MIT

https://github.com/AFNetworking/AFNetworking/

7.2資源文件的使用合法性

? 嚴禁使用從已上架App中拷貝使用任何資源文件,包括圖片,音視頻文件等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容