iOS代碼規范 (好代碼其實不如“好看”的代碼^^)

編寫的公司iOS端代碼規范 (已經脫敏了)

注釋規范

  • 好的注釋可以大大的提高代碼的閱讀性、也能讓自己快速的看懂自己幾個月前寫的代碼(……_)

  • 類注釋需要在 interface 之上,這樣可以在其他地方看到該類的注釋

    /******************************************************************************************************************************
     這里寫注釋
     ******************************************************************************************************************************/
     @interface ViewController : UIViewController
    
  • UI類注釋 在.h文件頭部添加描述和當前UI的雛形,"X" 代表不隱藏, “O”代碼隱藏;當該cell有多種排版時,需要一一畫出來

    /**
     列表模板視圖2:
      --------------------------
     | XXXXXXXXXXXXX    -----   |
     | XXXXX           |     |  |
     | xxxxxxxxxxx     |     |  |
     |                  -----   |
     | xx OO           OOO xxx  |
      --------------------------
     */
    
  • 功能類注釋 除了UI界面外,其他的類最好在.h文件頭部中添加一個描述,闡述一下功能和思路,如下

    //
    //  xxxxxx.h
    //  bookclub
    //
    //  Created by youhui on 2018/11/21.
    //  Copyright ? 2018年 luke.chen. All rights reserved.
    //
    /******************************************************************************************************************************
     功能:
     用來解決vc生命周期已經執行,但是卻被其他圖層擋住,導致無法準確的拿到頁面“可見化”的時機
     通過傳入target 在debut、each 兩個block中可以分別拿到首次、每次的“可見化”回調
     思路:
     1、采用CADisplayLink 這個和屏幕同頻率的定時器,監聽keywindow 是否被占用.通過block 傳回給調用方的頁面 debut(初次出現),each(每次出現),eachDismiss(每次消失)的生命周期時機
     2、判定keywindow是否被占用: 存在不是tabBar的容器但卻覆蓋屏幕的圖層,則判定為window被占用 (有可能存在兩個視圖拼接起來覆蓋屏幕的圖層,當前版本尚未發現,后續優化)
     ******************************************************************************************************************************/
    
  • 函數注釋--.h文件

    /**
     開始監聽位置(一次),并在block中把 地址/經緯度傳回來
     
     @param needAreaId NO:不會請求區域碼 
     @param addressBlock callBackBlock
     */
    - (void)xxxxxx:(BOOL)needAreaId addressBlock:(BCLocationManagerAddressBlock)addressBlock;
    
  • 函數注釋--.m文件 一般情況下,在.m文件中,不需要那么詳細的注釋,簡單注釋能看明白函數的作用即可,太長會影響可讀性,需要查看詳細的細節,可以到.h中查看

    //開始監聽位置(一次),并在block中把 地址/經緯度傳回來
    - (void)xxxxxx:(BOOL)needAreaId addressBlock:(BCLocationManagerAddressBlock)addressBlock {
    
    }
    
  • 屬性注釋--.h文件 如果必須要傳,要在注釋的首行加上 “@required”

    /**
    @required
     每次block
    
     @param times “可見化”次數
     */
    typedef void(^BCLifeCircleManagerEachBlock)(long long times);
    
  • 屬性注釋--.m文件 緊貼著屬性的定義后面注釋

    @property (nonatomic, readwrite, strong) CADisplayLink *displayLink;//屏幕刷新同步器
    
  • 業務代碼注釋 在編寫業務代碼時,往往會是一串又長又臭的代碼,(原則上,一個函數最好不要超過100行)這個時候,需要對代碼進行模塊話的注釋,同一類功能添加一個注釋.如下所示

    //用戶角色改變
    - (void)xxxxx:(NSNotification *)noti {
        //重置
        xxxxx
    
        //重新請求
        xxxxx
    
        //清除數據
        xxxxx
        xxxxx
    
        //音頻重置
        xxxx
        xxxx
    
        //視頻重置
        xxxxx
        xxxxx
    }
    

頁面規范

  • 推薦使用懶加載自動懶加載插件,具有高可閱讀性的優點
  • 復制以下代碼 在Xcode 中 添加一個用戶自定義代碼塊,支持快捷縮寫關聯出頁面腳手架
    • ViewController

      //生命周期
      #pragma mark - Lifecycle
      - (void)dealloc {
          NSLog(@"%@dealloc",self);
      }
      
      - (void)viewDidLoad {
          [super viewDidLoad];
          [self loadSubviews];
          // Do any additional setup after loading the view.
      }
      
      //視圖的初始化,層級結構都在這里
      #pragma mark - LoadSubViews
      - (void)loadSubviews {
      
      }
      
      //視圖的布局,刷新
      #pragma mark - LayoutSubViews
      - (void)layout {
      
      }
      
      //重寫父類放這里
      #pragma mark - Overwrite
      
      //公開方法
      #pragma mark - Public Method
      
      //隱私方法
      #pragma mark - Privacy Method
      
      //網絡請求
      #pragma mark - Request <#netRequest#>
      
      //通知方法
      #pragma mark - Notification Obverser Method
      
      //代理
      #pragma mark - Delegate
      
      //get set 懶加載
      #pragma mark - Get and Set
      
    • View

      //生命周期
      #pragma mark - Lifecycle
      - (void)dealloc {
          NSLog(@"%@dealloc",self);
      }
      
      //視圖的初始化,層級結構都在這里
      #pragma mark - LoadSubViews
      - (void)loadSubviews {
      
      }
      
      //視圖的布局,刷新
      #pragma mark - LayoutSubViews
      // tell UIKit that you are using AutoLayout
      + (BOOL)requiresConstraintBasedLayout {
          return YES;
      }
      
      // this is Apple's recommended place for adding/updating constraints
      - (void)updateConstraints {
          [self layout];
          [super updateConstraints];
      }
      
      //布局
      - (void)layout {
      
      }
      
      //重寫父類放這里
      #pragma mark - Overwrite
      
      //公開方法
      #pragma mark - Public Method
      
      //隱私方法
      #pragma mark - Privacy Method
      
      //網絡請求
      #pragma mark - Request <#netRequest#>
      
      //通知方法
      #pragma mark - Notification Obverser Method
      
      //代理
      #pragma mark - Delegate
      
      //get set 懶加載
      #pragma mark - Get and Set
      
    • Model

      //生命周期
      #pragma mark - Lifecycle
      
      //重寫父類放這里
      #pragma mark - Overwrite
      
      //公開方法
      #pragma mark - Public Method
      
      //隱私方法
      #pragma mark - Privacy Method
      
      //網絡請求
      #pragma mark - Request <#netRequest#>
      
      //代理
      #pragma mark - Delegate
      
      //get set 懶加載
      #pragma mark - Get and Set
      

屬性規范 (屬性方面,主要關注 .h 暴露出來的屬性。)

  • 寫代碼中最難的一個步驟之一可以說是命名,在給屬性命名時,在不是非常的長的情況下可以適當的做到盡量做到顧名思義,如需要給view 傳遞一個數據源,下面給出了三種命名:
array
dataArray
interfaceDataArray(**最優,能一眼就看出該數組的作用**)
  • 在.h文件的頭尾分別加上 NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END, 在這兩個宏之間的代碼,所有簡單指針對象都被假定為nonnull
  • "," ")" "@property" 之后需要加 空格 ; "*****"之前需要加 空格
@property空格(nonatomic,空格readwrite,空格strong)空格NSArray空格*interfaceDataArray;
  • 不可變的屬性使用Copy, 可變的屬性使用Strong(在需要保護封裝性的前提下)
  • 如果必須要賦值,要在注釋中使用 @required注明
  • 功能上不需要就不暴露,暴露了就需要足夠的信息或備注來說明,如下,interfaceDataArray 屬性可以說明
  • 盡量使用Fundation 框架的數據類型,如使用CGFloat 來替換float
    • 第一檔:只有一個簡單的nonatomic 和 strong 的來修飾

      @property (nonatomic, strong) NSArray *interfaceDataArray;
      
    • 第二檔:除了nonatomic 和 strong 之外 還多了一個readOnly字段,表明了該屬性為“只讀”屬性,你并不需要費勁心思的給他賦值

      @property (nonatomic, readOnly, strong) NSArray *interfaceDataArray; 
      
    • 第三檔:nullable類型的屬性可以說明支持 nil

      @property (nonatomic, readwrite, strong, nullable) NSArray *interfaceDataArray; 
      
    • 第四檔:容器屬性的 內部值類型,最好能一目了然,這樣寫就可以很直觀的看出該屬性是一個“包含了字符串數組的一個大數組”

      @property (nonatomic, readwrite, strong, nullable) NSArray<NSArray<NSString *> *> *interfaceDataArray; 
      
    • 第五檔:這里用copy替換掉了strong,被其他數組賦值時,會重新拷貝一份,不會和賦值的數組內存地址一致,從而保護了該屬性的封裝性。

      @property (nonatomic, readwrite, copy, nullable) NSArray<NSArray<NSString *> *> *interfaceDataArray; 
      
    • 第六檔:如果你有一個很詳細的注釋,那么你完全可以忘記之前的五種類型~

      /**
       @required
       外部傳入數據源
       在外部數據更新時需要對該屬性進行賦值刷新
       */
      @property (nonatomic, readwrite, copy, nullable) NSArray<NSArray<NSString *> *> *interfaceDataArray;
      

函數規范

  • 遵守駝峰命名
  • 一個文件里面統一使用一種括號模式
- (void)aaa {

}
  • 不使用 with、and 來連接各個參數
  • 盡量采用 instancetype 來替換 id
  • 提供初始化方法時,盡量考慮到使用方的便利,可以提供多個不同入參的方法 例如AF框架中的AFHTTPSessionManager 是這樣定義初始化函數的:
- (instancetype)init {
    return [self initWithBaseURL:nil];
}

- (instancetype)initWithBaseURL:(NSURL *)url {
    return [self initWithBaseURL:url sessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    return [self initWithBaseURL:nil sessionConfiguration:configuration];
}

- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration {
......
}

宏定義規范

  • NSUserDefault (BC_ + UDF_ + 命名),如下所示
#define BC_UDF_xxxxx @"BC_UDF_xxxxx"http://購買專輯成功
  • 通知 (BC_ + NOTI_ + 命名),如下所示
#define BC_NOTI_aaaaa @"BC_NOTI_aaaaa"http://節目列表頁面購買成功
  • 常數 (BC_ + K_ + 命名),如下所示 (包括字符串、數字等固定常量)
#define BC_K_bbbbb @"2"http://每日薦聽
  • 計算 存在計算的宏定義,需要對計算部分添加括號,否則在用該宏參與計算的時候會因為符號優先級不一定能得到想要的結果,如下所示
#define BC_K_GetSum(a,b) a + b
BC_K_GetSum(1,2) * 5 //11
    
//所以應該加上括號
#define BC_K_GetSum(a,b) (a + b)
BC_K_GetSum(1,2) * 5 //得到想要的15
  • 生產環境禁止Log (需要在xcode中添加DEBUG 配置,并復制以下代碼到項目對應的文件中)
#ifdef DEBUG
#define NSLog(...) NSLog(__VA_ARGS__)
#define debugMethod() NSLog(@"%s", __func__)
#else
#define NSLog(...)
#define debugMethod()
#endif
  • Weak / Strong
#define WeakObj(o) autoreleasepool{} __weak typeof(o) o##Weak = o;
#define StrongObj(o) autoreleasepool{} __strong typeof(o) o = o##Weak;

//例如
@WeakObj(self);
self.block =  ^{
    @StrongObj(self);
    [self xxxx];
}];

枚舉規范

  • NS_ENUM

    • NSInteger/NSUInteger 類型,直接賦值
    typedef NS_ENUM(NSInteger, BCHttpResponseType) {
            BCHttpResponseTypeA = 0, //這里是注釋
            BCHttpResponseTypeB = 1, //這里是注釋
            BCHttpResponseTypeC = -1, //這里是注釋
            BCHttpResponseTypeD = -2, //這里是注釋
            BCHttpResponseTypeE = -3, //這里是注釋
            BCHttpResponseTypeF = -4, //這里是注釋
            BCHttpResponseTypeG = -5, //這里是注釋
    };
    
  • NS_OPTIONS

    • 固定 NSUInteger 類型,一般用來進行二進制左右位移計算,當然也可以直接賦值.使用 NS_OPTIONS 的優點是可以通過 “|” 符號來鏈接多個枚舉,達到多選的效果.
    //顆粒化視頻遮罩層的組件,可用|自由組合不同層,高度可定制
    typedef NS_OPTIONS(NSUInteger, BCVideoMaskLayer) {
        BCVideoMaskLayerA = 0,//這里是注釋
        BCVideoMaskLayerB = 1 << 0,//這里是注釋
        BCVideoMaskLayerC = 1 << 1,//這里是注釋
        BCVideoMaskLayerD = 1 << 2,//這里是注釋
        BCVideoMaskLayerE = 1 << 3,//這里是注釋
        BCVideoMaskLayerF = 1 << 4,//這里是注釋
        BCVideoMaskLayerG = 1 << 5,//這里是注釋
        BCVideoMaskLayerH = 1 << 6,//這里是注釋
        BCVideoMaskLayerI = 1 << 8,//這里是注釋
        BCVideoMaskLayerJ = 1 << 9,//這里是注釋
        BCVideoMaskLayerK = 1 << 10,//這里是注釋
        BCVideoMaskLayerL = 1 << 11,//這里是注釋
        BCVideoMaskLayerM = 1 << 12,//這里是注釋
        BCVideoMaskLayerN = 1 << 13,//這里是注釋
        BCVideoMaskLayerO = 1 << 14,//這里是注釋
        BCVideoMaskLayerP = 1 << 15,//這里是注釋
        BCVideoMaskLayerQ = 1 << 16,//這里是注釋
        BCVideoMaskLayerR = 1 << 17,//這里是注釋
        BCVideoMaskLayerS = 1 << 18,//這里是注釋
        BCVideoMaskLayerT = 1 << 19,//這里是注釋
    };
    

內存規范

  • Dealloc 一定要執行
    頁面:在頁面的腳手架中的Lifecycle分類,需要實現dealloc 函數,并打印當前的類,在頁面功能開發完成之后,最好要測試一下頁面的dealloc 是否被調用.如果沒有被及時調用,那么就存在內存泄漏,需要對頁面的代碼進行排查.

  • 添加的監聽需要在合適的地方進行釋放

    • 如通知監聽,定時器等
  • block的循環引用.

    • 原因 在block中很容易因為當前類持有該block,而block中的代碼又引用當前類,產生循環引用,無法釋放.
    • 處理 在block外部定義弱引用(self),在block內部強引用(self)
  • delegate 沒有主動釋放

    • 原因 如果當前類持有了如友盟單例等代理,那么會因為單例一直存在,導致單例會一直對當前類進行持有,導致無法釋放.
    • 處理 需要在頁面退出時或者其他合適的時機,手動注銷delegate
    • (例子)正常點擊 A 的返回按鈕退出
      這里想要如期所愿的釋放 B、C,那么需要移除B、C 身上的所有引用,如上圖所示,B身上有兩個箭頭,代表兩個引用.那么當A釋放時,B身上只剩下一個引用,因為A釋放,D身上的唯一引用也被移除,D也隨之被釋放了,所以間接移除了D對B的引用,B在A釋放的同時可以正常的被釋放;
      那么問題來了,那么當A釋放時,按照上面的邏輯,D和A對C的引用也會被隨之移除,但是因為E還保持著對C的引用,所以C并沒有隨A的釋放而釋放.所以,想要讓C也跟隨A一起釋放,可以在A的dealloc 中手動解除E對C的持有即可.
    graph LR
      A(SuperVC-A) -->|持有|B((ChildVC-B))
      A -->|持有|C((ChildVC-C))
      A -->|持有|D((View-D))
      D -->|持有|B
      D -->|持有|C
      E(ShareInstance-E) -->|持有|C
    
  • 因系統或者其他情況導致的特殊情況.

    • (例子)在子頁面內點擊按鈕直接返回到RootVc,且切換到其他tab分欄,結構圖如下所示
      這里和上面點擊返回情況不同的是,因在pop到RootVc的同時切換到其他tab,導致系統無法及時對RootVc下的一些內存進行回收.導致在切回到RootVc所在分欄之前,D一直沒有被回收,也就是通常所見的野指針.那么這種情況下,如果還想讓B、C隨A一直釋放,可以在A釋放的同時,額外手動釋放D對的B、C的持有即可
    graph LR
      A(SuperVC-A) -->|持有|B((ChildVC-B))
      A -->|持有|C((ChildVC-C))
      A -->|持有|D((View-D))
      D -->|持有|B
      D -->|持有|C
      E(ShareInstance-E) -->|持有|C
      H(野指針) -->|在點擊按鈕直接退出到root且切換tab的情況下,導致的沒有被釋放|D
    
  • 當以上的一些方法都找不出問題時,可以通過profile的instruments的來進行調試定位到具體的代碼.

布局規范

  • RunLoop相關參考

  • 布局相關參考

  • RunLoop 中 蘋果注冊了一個 Observer 監聽 BeforeWaiting(即將進入休眠) 和 Exit (即將退出Loop) 事件,回調去執行一個很長的函數,_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個函數里會遍歷所有待處理的 UIView/CAlayer 以執行實際的繪制和調整,并更新 UI 界面,而約束布局的視圖一般和這幾個函數相關,從而進行約束、布局、繪制 (具體)
    [圖片上傳失敗...(image-6a68ff-1560592762925)]

  • 優先使用自動布局(masonry)

    • ViewController : 可以在viewDidLoad 生命周期中/或者接口返回后手動調用layout
    - (void)viewDidLoad {
     [super viewDidLoad];
     [self layout];
    }
        
    - (void)layout {
    //此處為布局代碼
    }
    
    • View : 使用上面提到的腳手架 ,其中重寫了 requiresConstraintBasedLayout 和 updateConstraints 方法,并在layout 函數中 進行代碼自動布局,在需要更新布局時,執行[self setNeedsUpdateConstraints] 即可,無需在其他地方主動執行 layout函數
    #pragma mark - LayoutSubViews
    // tell UIKit that you are using AutoLayout
    + (BOOL)requiresConstraintBasedLayout {
        return YES;
    }
    
    // this is Apple's recommended place for adding/updating constraints
    - (void)updateConstraints {
        [self layout];
        [super updateConstraints];
    }
    
    //布局
    - (void)layout {
    //此處為布局代碼
    }
    
    //在需要更新布局時,調用setNeedsUpdateConstraints 即可
    - (void)xxx {
            [self setNeedsUpdateConstraints];
    }
    
  • 在某些場景可以使用frame布局,如下

    • layer層參與布局 (因為layer 不支持自動布局)
    • 動畫和自動布局沖突時,如下,這樣使用就會發生一些奇怪的問題
    [UIView animateWithDuration:.5 animations:^{
        [xxx mas_updateConstraints:^(MASConstraintMaker *make) {
           xxxx
        }];
        //[xxx layoutIfNeeded];
     }];
    

資源規范

  • Assets 中應該建有和模塊相應的文件夾,存放該模塊中需要的icon
  • icon名字需要統一改為英文名字,并在文件名結尾標明 @2x,@3x ,系統能自動識別兩種資源.獲取對應的資源
  • gif、plist 等其他文件,因為不能存放在Assets中,需要在對應的下模塊新建Resource文件夾存放
  • 放入資源之前,需要提前檢索是否已經有同名的資源
  • 不需要的資源,需要定期刪除(可以在該版本結束時,進行刪除,包括不需要的代碼)

第三方庫規范

  • 使用cocoaPods進行管理
  • 在cocoaPods 的 ".gitignore" 屏蔽文件中,需要加上 Pods/ 、Podfile.lock (pod只在本地管理,有其他同事新增時可以直接pod udpate 新的)
  • pods中的每個三方庫都需要注明作用,如下
# Uncomment the next line to define a global platform for your project
platform :ios, '8.0'
target 'xxxx' do
    # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
    # use_frameworks!
    # Pods for xxx
    pod 'AFNetworking', '3.2.0'         #網絡
    pod 'MBProgressHUD', '1.1.0'        #提示框
    ....
    target 'xxxTest' do
    inherit! :search_paths
    # Pods for testing
    end

    target 'xxxUITests' do
    inherit! :search_paths
    # Pods for testing
    end
end

MVVM規范

  • 文件夾


    m_4752a2380b3175fcbb6d92de2dd9fbbd_r.jpg
  • 基本規則

    • view 或 vc 持有 viewModel 、viewModel 持有 model
    • viewModel 不持有view,且最好不引入UIKit框架
    • view 不持有 model
graph LR
A(View/ViewController)-->|持有|B(ViewModel)
B-->|持有|C(Model)
B---|不引入|D(UIKit框架)

解析規范

  • 盡量使用model 來承載數據,使用 ParseToModel 工具類來解析數據(如下代碼所示)
- (xxxxActivityModel *)xxxxModelWithResp:(NSDictionary *)response {
    NSDictionary *activityInfo = [response objectForKey:@"bbbb"];
    xxxxActivityModel *activityModel = [ParseToModel parseDictionary:activityInfo byClassName:@"xxxxActivityModel"];
    activityModel.aaa = [[response objectForKey:@"aaaa"] longLongValue];
    return activityModel;
}
  • 解析出的數據可能是null,在需要使用時,需要進行判空處理(如下代碼所示)
NSString *xxxx = [hotBook bc_jsonString:@"xxxx"]?[hotBook bc_jsonString:@"xxxx"]:@"";
NSString *aaaa = [hotBook bc_jsonString:@"aaaa"]?[hotBook bc_jsonString:@"aaaa"]:@"";
return @{@"xxxx":xxxx, @"aaaa":aaaa, @"cccc": @"c"};

跳轉規范

  • 在路由中有配置的跳轉使用路由跳轉
  • 如果路由中沒有配置,那么放在對應的vc中,盡量別放在view/viewModel/model 中

ps:喜歡的點個贊哈,或者贊賞支持~

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