我有一个很长的UIViewController,有很多部分和行。尽管对于这些部分中的每一部分都有单独的方法,但仍然变得麻烦地跳到不同的方法。有没有最好的方法来设计这样的课程?我想为这些部分中的每个部分设一个类别?这是一个好主意吗?
答案 0 :(得分:1)
我发现与委托方法相关的代码成为我的视图控制器的很大一部分。 this article at objc.io中描述的一种策略是将数据源委托方法移动到它们自己的类中。
答案 1 :(得分:1)
在去年,我开始从视图控制器中移出尽可能多的代码,尝试创建“更轻的视图控制器”。因为你可以使用实现一个 - 而且只有一个 - 视图控制器将具有的功能的某些方面的对象。你可以称之为Sub Controller,但是我和其他人使用名称«Intentions»来表达这个事实,即每个对象都有一个意图。
我尝试了不同种类,从目标/动作到基于块,我最终发现它是最有用的。
真实代码的一个例子:
我是一个收银机应用程序,需要通过wifi与热敏打印机进行通信
要添加打印机,我的AddPrinterViewController
包含用于IP地址,端口和名称/位置的文本字段。
而不是将视图控制器实现为我在类上创建的所有文本字段的委托,该类将作为一个文本字段的委托,并具有基于块的验证。
@interface TextfieldDelegateIntention : NSObject
@property(nonatomic, weak, readonly) UITextField *textField;
@property (nonatomic, copy) BOOL (^validationBlock)(UITextField *textField);
-(instancetype)initWithTextField:(UITextField *)textField
validationBlock: (BOOL (^)(UITextField *textField)) validationBlock;
-(BOOL)validate;
@end
#import "TextfieldDelegateIntention.h"
@interface TextfieldDelegateIntention ()<UITextFieldDelegate>
@property(nonatomic, weak) UITextField *textField;
@end
@implementation TextfieldDelegateIntention
-(instancetype)initWithTextField:(UITextField *)textField
validationBlock: (BOOL (^)(UITextField *textField)) validationBlock
{
self = [super init];
if(self){
self.validationBlock = validationBlock;
self.textField = textField;
self.textField.delegate = self;
}
return self;
}
-(BOOL)validate
{
if (self.validationBlock) {
return self.validationBlock(self.textField);
}
return NO;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
[textField resignFirstResponder];
}
@end
我使用故事板中的三个文本字段设置视图控制器。
IP(仅限第4版)地址'文本字段可能如下所示:
TextfieldDelegateIntention *ipAddresIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.ipTextField validationBlock:^BOOL(UITextField *textField) {
NSArray *components = [textField.text componentsSeparatedByString:@"."];
if ([components count] == 4) {
__block BOOL compsAreValidNumbers = YES;
[components enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj integerValue] > -1 && [obj integerValue] < 256 ) {
} else {
compsAreValidNumbers = NO;
*stop = YES;
}
}];
return compsAreValidNumbers;
}
return NO;
}];
过于简单的验证,但有助于用户正确输入。
完整视图控制器的代码如下所示
#import <UIKit/UIKit.h>
#import "BaseModalViewController.h"
@class NamendPOSNetworkPrinter;
@class PrinterProvider;
@interface AddPrinterViewController : BaseModalViewController
@property (nonatomic, strong) PrinterProvider *printerProvider;
@property (nonatomic, copy) void(^printerPreferecesEntered)(NSDictionary *printerDict);
@end
#import "AddPrinterViewController.h"
#import "TextfieldDelegateIntention.h"
#import "ButtonIntention.h"
#import "PrinterProvider.h"
@interface AddPrinterViewController ()
@property (weak, nonatomic) IBOutlet UITextField *nameTextField;
@property (weak, nonatomic) IBOutlet UITextField *ipTextField;
@property (weak, nonatomic) IBOutlet UITextField *portTextField;
@property (nonatomic, strong) NSArray *textFieldIntentions;
@property (nonatomic, strong) ButtonIntention *okIntention;
@property (weak, nonatomic) IBOutlet UIButton *okButton;
@end
@implementation AddPrinterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
TextfieldDelegateIntention *nameIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.nameTextField validationBlock:^BOOL(UITextField *textField) {
if ([textField.text length] > 0) {
return YES;
}
return NO;
}];
TextfieldDelegateIntention *ipAddresIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.ipTextField validationBlock:^BOOL(UITextField *textField) {
NSArray *components = [textField.text componentsSeparatedByString:@"."];
if ([components count] == 4) {
__block BOOL compsAreValidNumbers = YES;
[components enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj integerValue] > -1 && [obj integerValue] < 256 ) {
} else {
compsAreValidNumbers = NO;
*stop = YES;
}
}];
return compsAreValidNumbers;
}
return NO;
}];
TextfieldDelegateIntention *portIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.portTextField validationBlock:^BOOL(UITextField *textField) {
if ([textField.text integerValue] > 1023 && [textField.text integerValue]< 65536) {
return YES;
}
return NO;
}];
self.textFieldIntentions = @[nameIntention, ipAddresIntention, portIntention];
__block typeof(self) weakSelf = self;
ButtonIntention *okIntention = [[ButtonIntention alloc] initWithButton:self.okButton actionBlock:^(UIButton *button) {
typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
__block BOOL formIsValid = YES;
[strongSelf.textFieldIntentions enumerateObjectsUsingBlock:^(TextfieldDelegateIntention *intention, NSUInteger idx, BOOL *stop) {
BOOL isValid = [intention validate];
if (!isValid) {
intention.textField.backgroundColor = [UIColor redColor];
formIsValid = NO;
} else {
intention.textField.backgroundColor = [UIColor greenColor];
}
}];
if (formIsValid) {
if (self.printerPreferecesEntered) {
self.printerPreferecesEntered(@{
@"name": strongSelf.nameTextField.text,
@"ipAddress": strongSelf.ipTextField.text,
@"port": @([strongSelf.portTextField.text integerValue])
});
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf dismissViewControllerAnimated:YES completion:NULL];
});
}
}
}];
self.okIntention = okIntention;
}
@end
如您所见,我可以使用简单的块为三个文本字段添加不同的行为,只覆盖一个方法 - 没有其他方法添加。
我的博客验证电子邮件的另一个例子:Lighter ViewControllers with Block-based Intentions
tableview的数据源也很容易实现为Intention。
#import <UIKit/UIKit.h>
@class PrinterProvider;
@interface PrinterDataSource : NSObject <UITableViewDataSource>
@property (nonatomic, strong) PrinterProvider *printerProvider;
@end
#import "PrinterDataSource.h"
#import "VSPOSNetworkPrinter.h"
#import "PrinterProvider.h"
@interface PrinterDataSource ()
@end
@implementation PrinterDataSource
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[self.printerProvider allPrinters] count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PrinterCell" forIndexPath:indexPath];
cell.textLabel.text = [[self.printerProvider allPrinters][indexPath.row] ipAddress];
cell.detailTextLabel.text = [[self.printerProvider allPrinters][indexPath.row] name];
return cell;
}
@end
将其用作
#import "PrinterViewController.h"
#import "AddPrinterViewController.h"
#import "ButtonIntention.h"
#import "BarButtomItemIntention.h"
#import "NamendPOSNetworkPrinter.h"
#import "PrinterProvider.h"
#import "PrinterDataSource.h"
@interface PrinterViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *addButton;
@property (nonatomic, strong) BarButtomItemIntention *addIntention;
@property (nonatomic, strong) AddPrinterViewController *addPrinterViewController;
@property (strong, nonatomic) IBOutlet PrinterDataSource *printerDataSource;
@property (nonatomic, strong) PrinterProvider *printerProvider;
@end
@implementation PrinterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.printerDataSource.printerProvider = self.printerProvider;
self.addPrinterViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"AddPrinterViewController"];
typeof(self) weakSelf = self;
[self.addPrinterViewController setPrinterPreferecesEntered:^(NSDictionary *printerDict) {
NamendPOSNetworkPrinter *printer = [[NamendPOSNetworkPrinter alloc] initWithName:printerDict[@"name"]
ipAddress:printerDict[@"ipAddress"]
port:printerDict[@"port"]];
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.printerProvider addPrinter:printer];
[strongSelf.tableView reloadData];
}
}];
self.addPrinterViewController.printerProvider = self.printerProvider;
self.addIntention = [[BarButtomItemIntention alloc] initWithButtonItem:self.addButton
actionBlock:^(UIBarButtonItem *buttonItem)
{
typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf){
[strongSelf presentViewController:strongSelf.addPrinterViewController
animated:YES completion:^{
}];
}
}];
}
@end
这种方法的魅力在于您可以独立创建和子类视图控制器和意图,并且我可以根据需要轻松地重新排列它们。实际上,苹果在最近的wwdc视频«Advanced User Interfaces with Collection Views»
中开始宣传类似的做法答案 2 :(得分:0)
我建议您使用#pragma mark [divider name]
将您的班级分成几个部分,这样您就可以轻松地对方法进行分组。否则,创建处理包含功能的子类是唯一的选择。我已经看过8,000多个线路视图控制器类,所以有时候如果你太深入而无法将它全部分开,这是不可避免的。
答案 3 :(得分:0)
这更像是一种编程哲学。 Objective-C有冗长的方法(名称和声明),因此简化代码的最佳方法是将任何重复模式委托给外部类。
例如,我创建了一个仅保留返回数值运算的类,例如对NSNumbers,字符串等的算术运算。另一个只在输入上返回布尔值的类。 例如,如果您验证数字的有效性并将其递增,或者将其设置为1,即使只是代码中的两个位置,而不是像这样的事情:
if (variableName != nil && variableName.intValue > 0)
{
variableName2 = [NSNumber numberWithInt:(variableName.intValue + 1)];
}
else
{
variableName2 = [NSNumber numberWithInt:1];
}
在外面使用此功能会更容易(例如在“Arithmetics”类中),并将其称为如下:
variableName2 = [Arithmetics incrementNumber:variableName];
如果你像这样替换所有代码,通过具有良好命名的类和易于理解的方法(名称),你的代码将更具逻辑可读性和易于遵循,这是Objective-C的哲学(你也得到了能够在所有代码中重用这些方法的好处,在任何地方保持相同的逻辑。)