你的目錄組織方式是這樣:
先按照頁面分,然後再按照 MVC 來細分。
往往業界有兩種做法:
-
先按業務劃分,再按照 MVC 來劃分
-
先按 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 上都有最優實踐的討論。還有問題的話,可以微博上私信我。