Objective-C 編碼 Tips

weixin_34146805發表於2017-07-25

[toc]

一般性原則

避免命名歧義

Not Preferred

`displayName` /// Does it display a name or return the receiver’s title in the user interface?

一致性

整個工程的命名風格要保持一致性,最好和Apple.Inc SDK的程式碼保持統一。

Not Preferred

@interface MNMMovieCommentList : MTBModel

@property (nonatomic, strong) NSMutableArray *commentsList;
@property (nonatomic, strong) NSNumber *totalCount; //影評總條數(過濾後),用於分頁計算

- (NSUInteger)dataCount;

@end

常量、變數、資料型別

基本型別

  • 結合數值和幣種的money類
  • 由一個起始值和一個結束值組成的range類

Not Preferred

@property (nonatomic, assign) CGFloat totalRate;
@property (nonatomic, assign) CGFloat musicRate;
@property (nonatomic, assign) CGFloat pictureRate;
@property (nonatomic, assign) CGFloat directorRate;
@property (nonatomic, assign) CGFloat storyRate;
@property (nonatomic, assign) CGFloat playRate;
@property (nonatomic, assign) CGFloat impressionRate;
NSDictionary *infoDic = @{
                @"video" : @{
                    @"count" : self.movieDetailData.video.count ?  : @(0),
                    @"url" : self.movieDetailData.video.img ?  : @""
                },
                @"photo" : @{
                    @"count" : self.movieDetailData.stageImg.count ?  : @(0),
                    @"url" : ((MovieImageEntity *) [self.movieDetailData.stageImg.list mt_objectAtIndex:0]).imageUrl ?  : @""
                }
            };

常量

  • 常量應該用static宣告為靜態常量,而不要用#define。實際上,如果一個變數既宣告為static,又宣告為const,那麼編譯器根本不會建立符號,而是會像#define預處理指令一樣做替換。
  • 若常量侷限於某編譯單元,也就是實現檔案之內,則在前面加字母k;若常量在類之外可見,則通常以類名為字首。

Preferred

static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4

Not Preferred

- (MNEMemberProgress)p_memberProgress:(CGFloat)floatEmpirical
{
    floatEmpirical = floatEmpirical > 0 ? floatEmpirical : 0;

    if (floatEmpirical >= 8000)
    {
        return MNEMemberProgress_Diamond;
    }
    else if (floatEmpirical >= 3000)
    {
        return MNEMemberProgress_Platinum;
    }
    else if (floatEmpirical >= 1000)
    {
        return MNEMemberProgress_Gloden;
    }
    else if (floatEmpirical >= 200)
    {
        return MNEMemberProgress_Silver;
    }
    else
    {
        return MNEMemberProgress_Normol;
    }
}

布林值

  • Objective-C使用YESNO
  • truefalseCoreFoundationCC++程式碼使用。
  • BOOLObjective-C 中被定義為signed char,能被設定為8位。但YES被定義為1

Not Preferred

self.baseTableView.showsHorizontalScrollIndicator = FALSE;
if (self.arraySectionBtns == nil)
{
    self.arraySectionBtns = [NSMutableArray array];
}
if (self.isSubscribe == YES)
{
    return;
}

語法糖

應該使用可讀性更好的語法糖來構造NSArrayNSDictionary等資料結構,避免使用冗長的alloc,init方法。

運算子、表示式

三目運算子

一個條件語句的所有的變數應該是已經被求值了的。否則計算多個條件子句通常會讓語句更加難以理解。

當三元運算子的第二個引數返回和條件語句中已經檢查的物件一樣的物件的時候,可以省略。

Preferred

result = object ? : [self createObject];

if巢狀

不要巢狀 if 語句。使用多個 return 可以避免增加迴圈的複雜度,並提高程式碼的可讀性。因為方法的重要部分沒有巢狀在分支裡面,可以很清楚地找到相關的程式碼。

Not Preferred

- (BOOL)handleSchemeOpenURL:(NSURL *)aUrl
{
    NSString *stringScheme = aUrl.scheme;
    NSString *stringHost = aUrl.host;

    /// 檢查Scheme和Host是否合法
    if ([stringScheme mt_compareCaseInsensitive:mnkScheme_mtime])
    {
        if ([stringHost mt_compareCaseInsensitive:mnkScheme_host])
        {
            return [self handleAppLinkUrl:aUrl animation:NO];
        }
    }
    return NO;
}

括號指定計算優先順序

Not Preferred

if(*p++)

複雜的條件表示式

  • 當有一個複雜的if子句的時候,應該把它們提取出來賦給一個 BOOL變數,這樣可以讓邏輯更清楚,而且讓每個子句的意義體現出來。
  • 以查詢取代臨時變數更適合,畢竟臨時變數只在它所處的那個函式中才有意義,侷限性較大,而函式則可以在物件的整個生命中都有用,並且可被其他物件使用。

Not Preferred

if (!badgeValue || [badgeValue isEqualToString:@""] || ([badgeValue isEqualToString:@"0"] && self.shouldHideBadgeAtZero))
    {
        //        [self removeBadge];
        self.badge.hidden = YES;
    }

控制語句

Switch

可以運用物件導向的多型概念來避免Switch語句。

Preferred

[MNCTempletTableViewCell templetTableViewCellWithTableView:tableView indexPath:indexPath Type:model.templetType BeUsedIn:MNETempletBeUsedIn_IndexSelected]
[(MNCTempletTableViewCell *) cell mt_setObject:model]

函式

過長的函式

每當感覺需要以註釋來說明點什麼的時候,我們就把需要說明的東西寫進一個獨立函式中,並以其用途命名。每個函式的粒度都很小,那麼函式被複用的機會就更大,函式的覆寫也會更容易。
粒度小的函式使得高層函式讀起來就像一系列註釋。

過長的引數列

使用引數物件解決問題。

Preferred

- (void)updateContent:(MNMTempletBottomView *)modleTempletBottomView

私有方法

私有方法使用字首表明。

Preferred

- (void)p_privateFunction{}

將查詢函式和修改函式分離

某個函式既返回物件狀態值,又修改物件狀態。則應該將查詢函式和修改函式分離。任何有返回值的函式,都不應該有看得到的副作用。

重複程式碼

  • 同一個類的兩個函式含有相同的表示式
  • 兩個互為兄弟的子類內含相同表示式
  • 毫不相干的類出現相同的表示式

封裝向下轉型

某個函式返回的物件,需要由函式呼叫者執行向下轉型。這種情況下你不應該要求使用者承擔向下轉型的責任,應該儘量為他們提供準確的型別。

Not Preferred

- (NSNumber *)obtainPayEndTime
{
    return _modelOrderDetail.payEndTime;
}
    self.requestReSendSMS.stringSubOrderID = [[self obtainSubOrderId] stringValue];

過大的類

Objective-C

列舉型別

使用NS_ENUM,以獲得強型別檢查。

Not Preferred

typedef enum : NSUInteger {
    /// 等級禮包
    MNE_Member_PopType_Level = 1,
    /// 生日禮包
    MNE_Member_PopType_Birthday = 2
} MNE_Member_PopType;

Preferred

typedef NS_OPTIONS(NSUInteger, NSWindowMask) {
    NSBorderlessWindowMask      = 0,
    NSTitledWindowMask          = 1 << 0,
    NSClosableWindowMask        = 1 << 1,
    NSMiniaturizableWindowMask  = 1 << 2,
    NSResizableWindowMask       = 1 << 3
};

屬性

  • 描述BOOL屬性的詞如果是形容詞,那麼setter不應該帶is字首,但它對應的getter訪問器應該帶上這個字首。
  • 應該總是使用點語法來訪問和修改屬性。

Preferred

@property(nonatomic,getter=isOn) BOOL on;

Not Preferred

if ([objet isKindOfClass:[NSArray class]])
{
    return [(NSArray *) objet count];
}
/// 表示只有一組
return self.arrayDataSourceItems.count;

new()

  • 不要嘗試呼叫NSObject類的new()方法,也不要在它的子類中複寫這個方法。
  • alloc負責建立物件,這個過程包括分配足夠的記憶體來儲存物件,寫入isa指標,初始化引用計數,以及重置所有例項變數。
  • init負責初始化物件,這意味著使物件處於可用狀態。這通常意味著為物件的例項變數賦予合理有用的值。

instancetype

使用instancetype確保編譯器正確地推斷結果型別。

Preferred

+ (instancetype)personWithName:(NSString *)name;

Lazy Loading

Preferred

- (NSDateFormatter *)dateFormatter {
  if (!_dateFormatter) {
    _dateFormatter = [[NSDateFormatter alloc] init];
        NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        [_dateFormatter setLocale:enUSPOSIXLocale];
        [_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];
  }
  return _dateFormatter;
}

錯誤處理

Important: Success or failure is indicated by the return value of the method. Although Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO, you should always check that the return value is nil or NO before attempting to do anything with the NSError object.

當一個方法通過引用返回一個錯誤引數,應該檢測返回值的狀態,而不是錯誤引數的狀態。

Preferred

 NSError *anyError;
    BOOL success = [receivedData writeToURL:someLocalFileURL
                                    options:0
                                      error:&anyError];
    if (!success) {
        NSLog(@"Write failed with error: %@", anyError);
        // present error to user
    }

Not Preferred

    NSError *error = nil;
    BOOL isSucess = [[NSFileManager defaultManager] removeItemAtPath:self.stringFilePathCaches
                                                               error:&error];
    if (error)
    {
        if (mtkString_Is_Valid(error.localizedDescription))
        {
            // 如果解析失敗,上傳解析失敗原因
            NSString *stringErrorLog = [NSString stringWithFormat:@"清除“MIT_Caches”檔案 失敗,\n原因:%@",
                                                                  error.localizedDescription];
            stringErrorLog = stringErrorLog;
            LOG_Foundation_Info_(@"%@", stringErrorLog);
        }
    }

    return isSucess;

ARC下函式命名

ARC通過命名約定將記憶體管理規則標準化。若方法名以下列詞語開頭,則其返回的物件歸呼叫者所有。若方法名不以上述四個詞語開頭,則標示其返回的物件並不歸呼叫者所有,在這種情況下,返回的物件會自動釋放。

  • alloc
  • new
  • copy
  • mutableCopy

Not Preferred

- (void)newShareContent:(ShareViewContent *)aModelContent
       statisticsParams:(NSDictionary *)aDicStatistics;

CGRect

Your application can standardize a rectangle—that is, ensure that the height and width are stored as positive values—by calling the CGRectStandardize function. All functions described in this reference that take
CGRect
data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the
CGRect
data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.

CGGeometry

Preferred

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

NS_DESIGNATED_INITIALIZER

  • 如果建立類例項的方式不止一種,那麼這個類就會有多個初始化方法,不過仍然要選定其中一個作為全能初始化方法,令其他初始化方法都來呼叫它。這樣當底層資料儲存機制改變時,只需修改此方法的程式碼就好,無須改動其他初始化方法。如果子類的全能初始化方法與超類方法的名稱不同,那麼總應覆寫超類的全能初始化方法,必
    要時甚至在其中丟擲異常。
  • 每個子類的全能初始化方法都應該呼叫其超類的對應方法,並逐層向上。
  • 使用NS_DESIGNATED_INITIALIZER表明全能初始化方法。

isEqual:

  • 當實現相等性的時候記住這個約定:需要同時實現isEqualhash方法。如果兩個物件是被isEqual認為相等的,它們的 hash方法需要返回一樣的值。但是如果hash返回一樣的值,並不能確保他們相等。
  • 一定要注意hash方法不能返回一個常量。這是一個典型的錯誤並且會導致嚴重的問題,因為實際上hash方法的返回值會作為物件在hash雜湊表中的key,這會導致hash100%的碰撞。

Not Preferred

- (NSUInteger)hash
{
    return 1;
}

Notification

[ 觸發通知的類名 ] + [Did | Will] + [ 動作 ] + Notification

Preferred

NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

Clang

忽略沒用使用變數的編譯警告

Preferred

- (NSInteger)giveMeFive
{
    NSString *foo;
    #pragma unused (foo)
    return 5;
}

標註、警告、錯誤

Preferred

/// MARK:a
/// TODO:b
/// FIXME:c
/// !!!: d
/// ???:e
#error Whoa, buddy, you need to check for zero here!

#warning Dude, don't compare floating point numbers like this!

引入斷言

註釋

self-documenting

其他

封裝集合

我們常常會在一個類中使用集合來儲存一組例項。集合的處理方式應該和其他種類的資料不同。取值函式不該返回集合自身,因為這會讓用得以修改集合內容而集合擁有者確一無所悉。

Not Preferred

@interface MNMMovieCommentList : MTBModel
@property (nonatomic, strong) NSMutableArray *commentsList;
- (void)replaceByNewMovieCommentListEntity:(MNMMovieCommentList *)newMovieCommentListEntity;
- (void)addObjectsFromNewMovieCommentListEntity:(MNMMovieCommentList *)newMovieCommentListEntity;
@end

Preferred

- (void)addElement:(elementType)anObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;

縮略詞

縮略詞

參考資料

相關文章