不知不覺團隊已經有了4個iOS開發,大家的代碼風格完全不一樣,所以每次改起別人的代碼就頭疼,理解起來不是那麼順暢,如鯁在喉。所以,就開了場分享會,把一些基本調用方法和代碼風格統一了一下。 前言 主要參考了: "view層的組織和調用方案" "更輕量的View Controllers" "整潔的Tab ...
不知不覺團隊已經有了4個iOS開發,大家的代碼風格完全不一樣,所以每次改起別人的代碼就頭疼,理解起來不是那麼順暢,如鯁在喉。所以,就開了場分享會,把一些基本調用方法和代碼風格統一了一下。
前言
主要參考了:
view層的組織和調用方案
更輕量的View Controllers
整潔的Table View代碼
因為每個人的風格不一樣,有些地方很難定義哪個好那個壞,但是同樣的風格很重要,對團隊有很大的好處。這些博客都詳細介紹了這樣做的原因,我這裡就把他們的精髓吸取了,加了些自己的想法,就把格式直接定下來了。
ViewController代碼結構
- 所有的屬性都使用Lazy Init,並且放在最後。這樣既美觀,對於數組之類的屬性也避免了崩潰
- viewDidLoad:addSubview,configData,這樣會很美觀
- viewWillAppear:佈局,佈局這個時候設好處很多,比如我們iPad版類似qq空間,一個VC容器里放兩個,frame在WillAppear時在確定,這樣復用到iPhone版本就不用修改什麼。
設置Nav,TabBar是否隱藏,Status顏色。在WillDisAppear在設回原來的狀態,這樣就不會影響別人的VC。
- ViewDidAppear:添加Notification監聽,在DidDisappear里remove掉。
- 每一個delegate都把對應的protocol名字帶上,delegate方法不要到處亂寫,寫到一塊區域裡面去
- event response專門開一個代碼區域,所有button、gestureRecognizer的響應事件都放在這個區域裡面,不要到處亂放
- private/public methods,private methods儘量不要寫,可能以後別的地方會用到,做一個模塊或者category。
view的佈局和寫法
在一個VC或者View里,要麼全用Masonry,要麼全用frame。這個要統一,看起來很美觀。
storyboard絕對不用,主要是純代碼結合xib。
有些人說storyboard是未來,是apple力推的。但是它不僅效率低,conflict還多。我們曾經分成很多很多小的storyboard減少conflict,但是最後做iPad版本時,整個佈局變掉了,類似QQ空間的風格,它的復用性真的差,最後索性全部純代碼寫,然後重做iOS版,幾天就搞定了。所以只後就徹底拋棄了storyboard。
一些通用的邏輯或者頁面是否使用繼承來實現?
儘量不通過繼承,這也是設計模式中最常說的多用組合少用繼承。
很多情況可以使用category或者delegate來實現。
還有就是AOP,它需要一個攔截器,Mehtod Swizzling是個很好的手段。Aspects是個開源的庫,利用Mehtod Swizzling實現攔截的功能。
這樣很多功能可以統一處理,代碼的侵入性很小。比如打點,自定義導航欄,導航欄回退按鈕,cell的箭頭的統一的設置等。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// 如果 swizzling 的是類方法, 採用如下的方式:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(swizzling_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
#pragma mark - Method Swizzling
- (void)swizzling_viewWillAppear:(BOOL)animated {
[self swizzling_viewWillAppear:animated];
if (self.navigationController.viewControllers.count > 1) {
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
backButton.frame = CGRectMake(0, 0, 44, 44);
[backButton setTitle:@"" forState:UIControlStateNormal];
[backButton setImage:[UIImage imageNamed:@"back_black_icon"] forState:UIControlStateNormal];
[backButton setImageEdgeInsets:UIEdgeInsetsMake(0, -22, 0, 0)];
[backButton addTarget:self action:@selector(backEvent) forControlEvents:UIControlEventTouchUpInside];
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
[leftView addSubview:backButton];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:leftView];
}
}
MVC,MVVM,胖Model,瘦Model
所有的這些選擇,其實就是為了給ViewController減負。難點就是怎麼去拆分。通俗點講就是ViewController代碼行數很少,拆分出來的部分能復用,並且邏輯清晰。
viewController的作用就是數據請求,處理數據,顯示在View上。
數據請求
數據請求是指從服務端或者本地文件,資料庫取數據,VC不需要知道從哪裡取,只需要數據,我們的做法統一是:
ViewController.m
- (void)configData {
[CTPlanDataManager configPlanJsonDataWithPlanId:planId success:^(NSDictionary *dict) {
} failure:^(NSError *error) {
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self configData];
}
CTPlanDataManager.m
- (void)configPlanJsonDataWithPlanId:(NSUInteger) planId
success:(RequestOSSSuccessDictBlock) success
failure:(RequestOSSFailureBlock) failure {
if ([self planJsonFileExistsWithPlanId:planId]) { //判斷本地有沒有
NSDictionary *dict = [self readPlanJsonFromFileWithPlanId:planId];
if (success) {
success(dict);
}
}
else {
[self downloadPlanJsonFileWithPlanId:planId progress:nil success:^(NSDictionary *dict) { //從阿裡雲上取
if (success) {
success(dict);
}
} failure:^(NSError *error) {
if (failure) {
failure(error);
}
}];
}
}
處理數據
處理數據的邏輯全部放在model里,通過model直接獲取需要展現的數據。
model.h
@property (nonatomic, strong) NSArray<NSString *> *serviceArray; //從服務端獲取的
@property (nonatomic, strong) NSArray< NSString *> *handleArray; //model處理過的
model.m
- (void)setServiceArray:(NSArray *) serviceArray {
_serviceArray = serviceArray;
NSMutableArray< NSString *> *handleArray = [[NSMutableArray alloc] init];
for(NSString *value in _serviceArray) {
//一些邏輯處理
handleValue = [value doSomething];
[handleArray addObject:handleValue];
}
_handleArray = handleArray;
}
數據顯示
把處理後的數據顯示在View上,這個比較容易,主要就是自定義View,只留出初始化方法和賦值方法。
主要需要註意的地方賦值的時候要分離model和view,可以用category來實現賦值函數。
@implementation CTHeaderView (ConfigureForInfor)
- (void)configureForInfor:(CTInfor *) myInfor
{
self.nameTitleLabel.text = myInfor.name;
NSString* date = [self.dateFormatter stringFromDate: myInfor.birthday];
self.dateLabel.text = date;
......
}
@end
UITableview,UICollectionView
這兩個View是最常用的比較重的View。比較複雜的UI一般都用到他們。這個時候cell比較多,viewController比較臃腫,所以必須規範。
- dataSource,delegate,UICollectionViewLayout等必須分離出去寫
- 在cell內部控制cell的狀態。
//點擊的反饋
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
.....
self.selectedBackgroundView = self.selectView;
}
//高亮狀態的行為
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
......
} else {
......
}
}
- 控制多個Cell類型的寫法風格
typedef NS_ENUM(NSUInteger, ProgressCellTag) {
ProgressDateCellTag = kMinTag,
ProgressBlankCellTag,
ProgressTrainNoticeCellTag,
ProgressTimeNoticeCellTag,
ProgressActionCellTag,
};
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (self.dataSource[indexPath.row].integerValue) {
case ProgressActionCellTag:
return [self tableView:tableView actionCellForRowAtIndexPath:indexPath];
break;
case ProgressDateCellTag:
return [self tableView:tableView dateCellForRowAtIndexPath:indexPath];
break;
case ProgressTimeNoticeCellTag:
return [self tableView:tableView timeNoticeCellForRowAtIndexPath:indexPath];
break;
case ProgressTrainNoticeCellTag:
return [self tableView:tableView trainNoticeCellForRowAtIndexPath:indexPath];
break;
case ProgressBlankCellTag:
return [self tableView:tableView blankCellForRowAtIndexPath:indexPath];
break;
default:
break;
}
return nil;
}
#pragma mark - Cell Getter
- (UITableViewCell *)tableView:(UITableView *)tableView actionCellForRowAtIndexPath:(NSIndexPath *)indexPath {
//
}
- (UITableViewCell *)tableView:(UITableView *)tableView dateCellForRowAtIndexPath:(NSIndexPath *)indexPath {
//
}
- (UITableViewCell *)tableView:(UITableView *)tableView timeNoticeCellForRowAtIndexPath:(NSIndexPath *)indexPath {
//
}
- (UITableViewCell *)tableView:(UITableView *)tableView trainNoticeCellForRowAtIndexPath:(NSIndexPath *)indexPath {
//
}
- (UITableViewCell *)tableView:(UITableView *)tableView blankCellForRowAtIndexPath:(NSIndexPath *)indexPath {
//
}
總結
統一的風格和方式,使我們的邏輯更加清晰。尤其是改別人的代碼時,定位問題非常快,只需要理解他的處理邏輯,基本上就是改自己的代碼。