IOS简洁的试图控制器

//英文不好,好好学习


在IOS项目里,View controller经常是最大的文件,它们通常包含一些非必须的代码。View controller几乎总是代码中最少可以重用的。我们将关注一些技术使你的View controller瘦下来,使代码可以重用,把代码移动到更适当的地方。


分离数据源和其它特性       


一个使你的View controller瘦下来非常有效的手段是把UITableViewDataSource部分移动到你自己的类中。如果你不止一次的这样做,你将看到它的模式和可重用的类。

例如,在我们的示例项目中,类PhotosViewController中有以下的方法:

# pragma mark Pragma 


- (Photo*)photoAtIndexPath:(NSIndexPath*)indexPath {

    return photos[(NSUInteger)indexPath.row];

}


- (NSInteger)tableView:(UITableView*)tableView 

 numberOfRowsInSection:(NSInteger)section {

    return photos.count;

}


- (UITableViewCell*)tableView:(UITableView*)tableView 

        cellForRowAtIndexPath:(NSIndexPath*)indexPath {

    PhotoCell* cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier 

                                                      forIndexPath:indexPath];

    Photo* photo = [self photoAtIndexPath:indexPath];

    cell.label.text = photo.name;

    return cell;

}

很多的代码是关于数组的,还有一些只是关于photos由view controller管理。让我尝试着把array相关的代码移动到我们自己的class中。我们使用block去配置cell,但它也有可能是委托,只是根据你自己去选择的。

@implementationArrayDataSource


- (id)itemAtIndexPath:(NSIndexPath*)indexPath {

    return items[(NSUInteger)indexPath.row];

}


- (NSInteger)tableView:(UITableView*)tableView 

 numberOfRowsInSection:(NSInteger)section {

    return items.count;

}


- (UITableViewCell*)tableView:(UITableView*)tableView 

        cellForRowAtIndexPath:(NSIndexPath*)indexPath {

    id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier

                                              forIndexPath:indexPath];

    id item = [self itemAtIndexPath:indexPath];

    configureCellBlock(cell,item);

    return cell;

}


@end


这三个方法在你自己的view controller中可以运行,而相反你可以创建一个这个类的实例并且把它设置成为tableview的数据源。

void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {

   cell.label.text = photo.name;

};

photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos

                                                cellIdentifier:PhotoCellIdentifier

                                            configureCellBlock:configureCell];

self.tableView.dataSource = photosArrayDataSource;


现在,您不用去担心把index path映射到你数组中相应的位置,每当您想在tableview中展示array中的数据,您可以重用这段代码。您也可以实现额外的方法,例如tableView:commitEditingStyle:forRowAtIndexPath:共享这些代码在你的view controller中。


一个好的方面,我们可以单独测试这个类,再也不用担心再写一次,同样的规则你可以应用到其他的东西上不只是array。


一个应用我们让它在这一年工作,我们大量的使用Core Data,我们创建了一个相似的类,但不是依靠一个数组,它依赖一个读取数据的控制器。它实现了所有的动画更新,做节头和删除数据的逻辑。你可以创建一个实例,给它一个获取的数据的请求再用一个block去配置cell。


此外,这种方法也可以扩展到其他的协议。最明显的是UICollectionViewDataSource。这给了你极大的灵活性。如果在开发中的某一刻你决定用UICollectionView替代UITableVIew,你几乎不用改变view controller中的任何东西,你只需要让你的数据支持这两种协议。


把逻辑域移动到Model


下面是一段实例代码,用于查找一个用户活动优先级的列表:

- (void)loadPriorities {

  NSDate* now = [NSDate date];

  NSString* formatString = @"startDate <= %@ AND endDate >= %@";

  NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];

  NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate];

  self.priorities = [priorities allObjects];

}

然而,把他移动到我们类的类别中它将变得更简洁,然后我们将在这个view controller中看到:

- (void)loadPriorities {

  self.priorities = [user currentPriorities];

}

在 User+Extensions.m:

- (NSArray*)currentPriorities {

  NSDate* now = [NSDate date];

  NSString* formatString = @"startDate <= %@ AND endDate >= %@";

  NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];

  return [[self.priorities filteredSetUsingPredicate:predicate] allObjects];

}

一些代码移动到模型中不是那么的简单,但是它明显的跟modol中的代码有关联,因此,我们可以用Store:


创建一个Store类


在我们的第一个实例中,我们一些代码是从文件中加载数据并解析它。这些代码在我们的view controller中:

- (void)readArchive {

    NSBundle* bundle = [NSBundle bundleForClass:[selfclass]];

    NSURL *archiveURL = [bundle URLForResource:@"photodata"

                                 withExtension:@"bin"];

    NSAssert(archiveURL != nil, @"Unable to find archive in bundle.");

    NSData *data = [NSData dataWithContentsOfURL:archiveURL

                                         options:0

                                           error:NULL];

    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

    _users = [unarchiver decodeObjectOfClass:[NSArrayclass] forKey:@"users"];

    _photos = [unarchiver decodeObjectOfClass:[NSArrayclass] forKey:@"photos"];

    [unarchiver finishDecoding];

}

View controller不需要知道这些,我们创建一个Store来做这些。通过把它分离出去,我们可以重用这些代码,分别测试

,保持我们view controller简洁。Store可以关注数据加载、缓存、设置数据库堆栈。Store也经常会被服务层或存储调用。


移动网络服务到数据层


这和上面的主题非常相似:不要把网络服务逻辑放在你的view controller。相反,把它封装到不同的类。你的view controler可以调用这个类提供的方法附带一个回调(例如,一个finish block)。你可以把所有处理缓存和错误的放在这个类,这是一件很棒的事情。


移动View代码到View层


创建复杂结构层次的视图不应该放在view controller。你可以用interface builder 或者把views封装到UIView的子类。例如,如果你创建一个日期选择控制器,把它放到一个DatePickerView比直接把所有的东西都在view controller创建更有意义。此外,它可以增加代码的可重用性和使代码简洁。

如果你喜欢用Interface Builder,你可以在Interface Builder中做这些工作。有些人认为你只能把它给View controller用,但是你亦可以加载独立的nib文件与你自定义的视图,在我们的实例app,我们创建一个PhotoCell.Xib,它包含一个photo cell的布局:



正如我们看到的,我们在视图上创建属性,把他们连接到特定的子view,这个技术对于自定义子试图来说非常的方便。


讨论

一个view controller经常会和其它的view controller、数据、视图相互通讯。这正是一个controller应该做的,我们尽可能用最少的代码去实现。

有很多好的技术对于controller和model的通讯(比如KVO和读取结果控制器),然而,controller之间的通讯往往有点儿不大清楚。

我们经常遇到一个问题,一个cotroller有一些状态和其它很多的controller通讯,把它变成一个单独的实例在视图之间传递,所有的controller观察和修改该状态。这样的好处是所有的在一个地方,我们不会陷入一个代理回调。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值