话谈 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 上都有最优实践的讨论。还有问题的话,可以微博上私信我。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值