你的目录组织方式是这样:
先按照页面分,然后再按照 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 上都有最优实践的讨论。还有问题的话,可以微博上私信我。