話談 iOS 目錄結構的劃分

你的目錄組織方式是這樣:

先按照頁面分,然後再按照 MVC 來細分。

往往業界有兩種做法:

  1. 先按業務劃分,再按照 MVC 來劃分

  2. 先按 MVC 劃分,再按照業務劃分

第一種的好處是把相應業務的代碼放在一起,找特別好找,相應的 xib、model 和 ViewController 全在一個地方。在這些代碼之間跳轉特別方便。Telegram 非常大的代碼量,也是這麼組織的。個人覺得如果代碼量特別大,這種劃分方式其實更好,先按照業務劃分了,更容易分工合作。

第二種似乎更多人用。Yep 也是按照這樣的方式組織。

其實我覺得都可以,看個人習慣。

你的代碼裏把請求參數的 Model 和返回的Model 放在 Model 目錄裏,把 TableViewCell 放在 View 下面,把具體的 ViewController 放在 Controller 目錄下是沒有問題的,挺清晰的。

我不知道他們說的理解不深是什麼意思,你的 Model 層、網絡層、View 層、ViewController 層已經劃分得很清楚了。所以我猜他們的意思是不是說你的按頁面先劃分了,再 MVC 分目錄有點奇怪?但其實這還好,大到 Telegram 也是這麼弄的。

簡書上有個討論《iOS 項目的目錄結構能看出你的開發經驗》。引用其中的一個評論:

之前是先MVC然後各個模塊,後面發現新建模塊要跨越n多級文件結構就覺得這種不太好,等到後面模塊越來越多,真的發現先MVC完全沒好處,後面就 先模塊然後每個模塊再MVC,至於一些通用的MVC文件就直接單獨建在和模塊同級,現在實踐下來還不錯。

看,像你代碼的那樣,先按業務模塊劃分,再 MVC,挺好的!

下面說說其它問題:

Git Ignore

你提交的代碼裏沒有加 .gitignore,導致後續我想在你的代碼裏提交的時候,發現有很多不相關的代碼,也關聯進來了。這是我常用的一個 .gitignore

# Xcode.DS_Storebuild/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
!default.xcworkspacexcuserdata*.moved-aside
*.mobileprovisionDerivedData.idea/# Pods - for those of you who use CocoaPodsPodsPodfile.lock

GitHub 官方給了一個各語言的 .gitignore 大全。

另外 Pods 目錄推薦加入 .gitignore。因爲它可能特別大,影響別人 clone 的速度以及會影響 contribution 的統計。

Podfile.lock 也推薦 ignore 掉。因爲 pod install 的過程中會自動生成。另外 Podfile.lock 有 pod 的版本信息,如果不同的人使用了不同的版本,那會造成沒必要的衝突。

Stackoverflow上有更詳細的討論。用 CocoaPods 時 .gitignore 應包含什麼?

typedef block

+ (void)homeStatusesWithParam:(PartyParam *)param success:(void (^)(PartyResult *result))success failure:(void (^)(NSError *error))failure;

你的代碼中很多(void (^)(NSError *error))。我建議聲明這兩個 typedef ,來省略這部分的重複聲明,

typedef void (^PartyResultBlock)(PartyResult *result);
typedef void (^FailureBlock)(NSError *error);

所以就變成了

+ (void)homeStatusesWithParam:(PartyParam *)param success:(PartyResultBlock)success failure:(FailureBlock)failure;

還可以用上 Xcode 的自動補全功能,當你輸入 Fa 的時候, FailureBlock 的提示就出來了。用(void (^)(NSError *error)) 就沒有自動補全了,每次都要手工輸入一遍,很麻煩。

在 LeanCloud ,我們一般會把常見的類型都給定義一個 block:

typedef void (^AVBooleanResultBlock)(BOOL succeeded, NSError *error);
typedef void (^AVIntegerResultBlock)(NSInteger number, NSError *error);
typedef void (^AVArrayResultBlock)(NSArray *objects, NSError *error);
typedef void (^AVSetResultBlock)(NSSet *channels, NSError *error);
typedef void (^AVDataResultBlock)(NSData *data, NSError *error);
typedef void (^AVImageResultBlock)(UIImage * image, NSError *error);
typedef void (^AVDataStreamResultBlock)(NSInputStream *stream, NSError *error);
typedef void (^AVStringResultBlock)(NSString *string, NSError *error);
typedef void (^AVIdResultBlock)(id object, NSError *error);
typedef void (^AVProgressBlock)(NSInteger percentDone);
typedef void (^AVDictionaryResultBlock)(NSDictionary * dict, NSError *error);

這樣用起來就特別方便。

@impletation 定義私有成員 還是 用 Property

你的代碼:

@interface PartyViewController ()@property (weak, nonatomic) IBOutlet UITableView *partyTableView;@property (strong,nonatomic) NSMutableArray *partys;@end@implementation PartyViewController{    NSMutableArray *lodedIndex;
}

這裏同時用到了 @implementation 塊內定義私有成員,又用到了 property。該用哪種呢?

這個爭論唐巧老師甚至寫了一篇文章。

無論如何,應該一致,應該統一風格。建議這裏用 property 。因爲 @IBOutlet ,從 IB 拖出來的屬性用的就是 property ,所以統一用 property 較好。

你的代碼裏不統一的疑問就出來了,爲什麼lodedIndex 放在 @implementation 裏呢,而 partys 數組定義成 property 呢?新增一個私有成員,我應該怎麼定義呢?

所以代碼得特別注意,統一,consistent,始終如一。

用 int 還是 NSInteger

你的代碼:

@property (nonatomic, assign) int user_id;

可以注意到 apple 的 UIKit 等代碼一般都是用的 NSInteger。NSInteger 在 32位系統是 int ,64位系統是 long 。Apple 把它用在函數的參數處,和返回的地方。爲什麼?因爲函數是需要跟其它代碼或其它平臺的代碼交流互動的,所以是 int 還是 long 很重要。系統的代碼用的是 NSInteger 的話,你的用了 int 的話,可能不夠大而造成崩潰。

這裏可參考 Stack Overflow 的一個詳細討論。《什麼時候用 NSInteger 和 int ?》。

尺寸變量統一用 CGFloat

   int padding1 = 10;

應改爲

  CGFloat padding1 = 10;

一開始看到 padding1 後,不知道它是幹嘛的。因爲附近沒有它的代碼,看到後面才知道的。所以聲明類型爲 CGFloat ,更容易知道它是來定義尺寸距離的。

及時抽取函數,應對未來業務增長

- (void)setParty:(Party *)party{

    _party = party;

    [self.backgroundImageView sd_setImageWithURL:[NSURL URLWithString:self.party.img]];
    // 省略4行

    for (int i = 0; i<self.party.actor.count; i++) {
           UIButton *actorBtn = [[UIButton alloc]init];

          [self.actorsView addSubview:actorBtn];

          [actorBtn mas_makeConstraints:^(MASConstraintMaker *make) {

            make.centerY.mas_equalTo(self.actorsView.mas_centerY);

            make.left.equalTo(self.actorsView.mas_left).with.offset(padding1+i*55);

            //make.top.equalTo(self.actorsView.mas_top).with.offset(padding1);
            make.size.mas_equalTo(CGSizeMake(45, 45));
        }];
        [actorBtn setBackgroundColor:[UIColor whiteColor]];
        [actorBtn.layer setMasksToBounds:YES];
        [actorBtn.layer setCornerRadius:23];
        [actorBtn.layer setBorderWidth:1.0];
        [actorBtn.layer setBorderColor:[UIColor whiteColor].CGColor];
        Actor *actor = self.party.actor[i];
        [actorBtn sd_setBackgroundImageWithURL:[NSURL URLWithString:actor.avatar] forState:UIControlStateNormal placeholderImage:[UIImage imageNamed:@"人-佔位圖"]];

    }


}

這段函數比較長,未來業務增長了,又增加個瀏覽數的 label,點贊數的label 等等,這段函數就會非常長。這段 addActorButton 的邏輯相對獨立,應該抽取出來。改進之後,變成了這樣:

- (void)addActorButtonWithActor:(Actor *)actor index:(NSInteger)index {    // 省略10行代碼}

- (void)setParty:(Party *)party {  // ...
  for (NSInteger i = 0; i<self.party.actor.count; i++) {
        Actor *actor = self.party.actor[i];
        [self addActorButtonWithActor:actor index:i];
    }
}

選用正確的類型,讓編譯器能靜態檢查

代碼中有一段這樣的:

@interface PartyParam : YQX_BaseParam@property (nonatomic, strong) NSNumber *city_id;@property (nonatomic, strong) NSNumber *lat;@property (nonatomic, strong) NSNumber *lng;@property (nonatomic, strong) NSNumber *page;@property (nonatomic, strong) NSNumber *regionname;@property (nonatomic, strong) NSNumber *user_id;@end
PartyParam *param = [PartyParam param];    param.city_id = @52;    param.lat = @0;    param.lng = @0;    param.page = @0;    param.regionname = nil;    param.user_id = @2159;

全是 NSNumber 類型。改進之後:

@interface PartyParam : YQX_BaseParam@property (nonatomic, assign) NSInteger city_id;@property (nonatomic, assign) CLLocationDegrees lat;@property (nonatomic, assign) CLLocationDegrees lng;@property (nonatomic, assign) NSInteger page;@property (nonatomic, strong) NSString *regionname;@property (nonatomic, assign) NSInteger user_id;@end
    PartyParam *param = [PartyParam param];    param.city_id = 52;    param.lat = 0;    param.lng = 0;    param.page = 0;    param.regionname = nil;    param.user_id = 2159;

這樣能靜態檢查,提前爆紅,發現錯誤。減少代碼的出錯可能性。比如 city_id 明明只能是整形,定義了 NSNumber 之後,可能不小心有個地方傳入個小數,就會出現錯誤了。

注意,定義座標,用了 CLLocationDegrees ,其實是 double ,不過它的閱讀性更好一些。Stack Overflow 上也有專門的討論。《緯度和經度在 iOS 上最精確的類型是什麼》

最後

可以多 Stack Overflow 一下。當有疑問的時候,基本上 Stack Overflow 上都有最優實踐的討論。還有問題的話,可以微博上私信我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章