MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的代码重构与设计模式应用

2022-01-212.5k 阅读

代码重构在Objective-C中的重要性

在Objective-C项目开发过程中,随着功能的不断增加和代码库的逐步扩大,代码质量可能会逐渐下降。初始开发时为了快速实现功能,代码可能存在结构混乱、重复代码多等问题。代码重构就成为解决这些问题,提升代码质量的关键手段。

代码重构的目标

  1. 提高代码可读性:当开发团队成员需要理解和维护代码时,清晰的代码结构和命名能大大减少理解成本。例如,将复杂的逻辑拆分成多个小的、功能明确的方法,使用有意义的变量和方法名。在Objective-C中,如果有一段处理用户登录逻辑的代码,像下面这样:
// 初始混乱代码
-(BOOL)loginUser:(NSString *)username password:(NSString *)password {
    // 一系列复杂逻辑,包含网络请求、验证等
    NSURL *url = [NSURL URLWithString:@"http://example.com/api/login"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    NSString *parameters = [NSString stringWithFormat:@"username=%@&password=%@", username, password];
    [request setHTTPBody:[parameters dataUsingEncoding:NSUTF8StringEncoding]];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (!error && data) {
            NSError *jsonError;
            NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
            if (!jsonError && [json[@"status"] isEqualToString:@"success"]) {
                // 登录成功处理
                return YES;
            }
        }
        return NO;
    }];
    [task resume];
    return NO; // 这里返回值逻辑存在问题
}

重构后:

-(BOOL)loginUser:(NSString *)username password:(NSString *)password {
    NSURL *loginURL = [self loginURL];
    NSMutableURLRequest *request = [self createLoginRequestWithURL:loginURL username:username password:password];
    return [self sendLoginRequest:request];
}

-(NSURL *)loginURL {
    return [NSURL URLWithString:@"http://example.com/api/login"];
}

-(NSMutableURLRequest *)createLoginRequestWithURL:(NSURL *)url username:(NSString *)username password:(NSString *)password {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    NSString *parameters = [NSString stringWithFormat:@"username=%@&password=%@", username, password];
    [request setHTTPBody:[parameters dataUsingEncoding:NSUTF8StringEncoding]];
    return request;
}

-(BOOL)sendLoginRequest:(NSMutableURLRequest *)request {
    __block BOOL success = NO;
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (!error && data) {
            NSError *jsonError;
            NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
            if (!jsonError && [json[@"status"] isEqualToString:@"success"]) {
                success = YES;
            }
        }
    }];
    [task resume];
    return success;
}

重构后的代码将登录逻辑拆分成多个方法,每个方法职责清晰,大大提高了可读性。

  1. 增强代码可维护性:当需求发生变化时,易于修改的代码结构能节省大量时间和精力。例如,在一个iOS应用中,如果初始代码将所有的界面布局代码都写在一个ViewController的viewDidLoad方法中,当需要修改某个界面元素的布局时,可能需要在大量代码中查找相关部分。通过重构,将布局代码按照功能模块或者界面区域进行拆分,分别放在不同的方法中,修改时就能快速定位和修改。

  2. 减少代码重复:重复代码不仅增加了代码量,还增加了维护成本。一旦需要修改某个功能,所有重复的代码都需要修改,容易出现遗漏。例如,在多个视图控制器中都有获取当前用户信息并显示的逻辑,如果每个视图控制器都单独实现这部分代码,当用户信息获取方式发生变化时,就需要在多个地方进行修改。可以将这部分逻辑封装成一个工具方法,在需要的地方调用,从而减少重复代码。

常见的代码重构手法在Objective-C中的应用

提取方法(Extract Method)

  1. 定义与作用:提取方法是指将一段代码从一个方法中提取出来,形成一个新的独立方法。这样做可以使原方法的逻辑更加清晰,同时新方法可以被其他地方复用。例如,在一个处理订单的类中,如果有一段代码用于计算订单总价,在calculateOrderTotal方法中,初始代码可能如下:
-(float)calculateOrderTotal {
    float total = 0;
    for (OrderItem *item in self.orderItems) {
        float itemPrice = item.price * item.quantity;
        if (item.discount > 0) {
            itemPrice = itemPrice * (1 - item.discount);
        }
        total += itemPrice;
    }
    if (self.shippingFee > 0) {
        total += self.shippingFee;
    }
    return total;
}

可以将计算单个商品价格的逻辑提取出来:

-(float)calculateItemPrice:(OrderItem *)item {
    float itemPrice = item.price * item.quantity;
    if (item.discount > 0) {
        itemPrice = itemPrice * (1 - item.discount);
    }
    return itemPrice;
}

-(float)calculateOrderTotal {
    float total = 0;
    for (OrderItem *item in self.orderItems) {
        total += [self calculateItemPrice:item];
    }
    if (self.shippingFee > 0) {
        total += self.shippingFee;
    }
    return total;
}
  1. 注意事项:在提取方法时,要确保新提取的方法有明确的职责,并且不会造成过多的参数传递。如果新方法需要访问原方法中的大量局部变量,可能需要重新考虑提取的合理性,或者可以将相关数据封装成一个对象传递给新方法。

合并重复代码(Consolidate Duplicate Code)

  1. 场景与做法:在Objective-C项目中,经常会出现多个类或者方法中有重复代码的情况。例如,在一个电商应用中,ProductListViewControllerCartViewController都有代码用于格式化价格显示:
// ProductListViewController.m
-(NSString *)formatPrice:(float)price {
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    NSNumber *number = [NSNumber numberWithFloat:price];
    return [formatter stringFromNumber:number];
}

// CartViewController.m
-(NSString *)formatCartPrice:(float)price {
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    NSNumber *number = [NSNumber numberWithFloat:price];
    return [formatter stringFromNumber:number];
}

可以将这部分重复代码提取到一个工具类中:

// PriceFormatter.m
#import <Foundation/Foundation.h>

@interface PriceFormatter : NSObject
+(NSString *)formatPrice:(float)price;
@end

@implementation PriceFormatter
+(NSString *)formatPrice:(float)price {
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    NSNumber *number = [NSNumber numberWithFloat:price];
    return [formatter stringFromNumber:number];
}
@end

// ProductListViewController.m
#import "PriceFormatter.h"
-(NSString *)formatPrice:(float)price {
    return [PriceFormatter formatPrice:price];
}

// CartViewController.m
#import "PriceFormatter.h"
-(NSString *)formatCartPrice:(float)price {
    return [PriceFormatter formatPrice:price];
}
  1. 优化效果:通过合并重复代码,减少了代码冗余,提高了代码的一致性和可维护性。如果价格格式化的规则发生变化,只需要在PriceFormatter类中修改一次即可。

重命名(Rename)

  1. 重要性:在Objective-C代码中,使用有意义的名称对于代码的可读性和可维护性至关重要。例如,一个表示用户年龄的变量命名为a,很难让人理解其含义。将其重命名为userAge就清晰多了。同样,对于方法名,如果一个方法用于获取用户信息,但命名为doSomething,也会给阅读代码的人带来困惑。将其重命名为fetchUserInfo就明确了方法的功能。
  2. 工具辅助:在Xcode中,可以使用重构功能方便地进行重命名。选中要重命名的变量、方法或类,然后通过快捷键(如Cmd + Shift + R)调出重命名对话框,输入新的名称后,Xcode会自动在整个项目中更新相关的引用,确保一致性。

设计模式在Objective-C中的应用

单例模式(Singleton Pattern)

  1. 定义与原理:单例模式确保一个类只有一个实例,并提供一个全局访问点。在Objective-C中,实现单例模式通常使用dispatch_once函数,它能保证代码块只被执行一次。例如,创建一个用于管理应用配置的单例类AppConfiguration
#import <Foundation/Foundation.h>

@interface AppConfiguration : NSObject
@property (nonatomic, strong) NSString *serverURL;
@property (nonatomic, assign) BOOL isDebugMode;
+(instancetype)sharedConfiguration;
@end

@implementation AppConfiguration
static AppConfiguration *sharedInstance = nil;
+(instancetype)sharedConfiguration {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
@end

在其他地方可以通过[AppConfiguration sharedConfiguration]来获取唯一的实例。例如:

AppConfiguration *config = [AppConfiguration sharedConfiguration];
config.serverURL = @"http://example.com/api";
config.isDebugMode = YES;
  1. 应用场景:单例模式常用于管理应用全局的资源,如数据库连接管理、网络请求队列管理等。在iOS开发中,UIApplication类本质上就是一个单例,通过[UIApplication sharedApplication]可以获取应用的实例,从而访问应用的各种属性和方法,如设置应用的状态栏样式、获取当前活动的视图控制器等。

代理模式(Proxy Pattern)

  1. 概念与实现:代理模式为其他对象提供一种代理以控制对这个对象的访问。在Objective-C中,代理模式广泛应用于委托机制。例如,在一个自定义的表格视图CustomTableView中,可能需要将一些事件(如单元格点击事件)委托给其他对象处理。首先定义代理协议:
@protocol CustomTableViewDelegate <NSObject>
-(void)customTableView:(CustomTableView *)tableView didSelectRowAtIndex:(NSInteger)index;
@end

@interface CustomTableView : UIView
@property (nonatomic, weak) id<CustomTableViewDelegate> delegate;
// 其他表格视图相关代码
@end

@implementation CustomTableView
// 假设在处理单元格点击事件时调用代理方法
-(void)handleCellTap:(UITapGestureRecognizer *)tap {
    NSInteger index = [self indexForTap:tap];
    if ([self.delegate respondsToSelector:@selector(customTableView:didSelectRowAtIndex:)]) {
        [self.delegate customTableView:self didSelectRowAtIndex:index];
    }
}
@end

然后在使用CustomTableView的视图控制器中实现代理方法:

@interface ViewController : UIViewController <CustomTableViewDelegate>
@property (nonatomic, strong) CustomTableView *customTableView;
@end

@implementation ViewController
-(void)viewDidLoad {
    [super viewDidLoad];
    self.customTableView = [[CustomTableView alloc] initWithFrame:self.view.bounds];
    self.customTableView.delegate = self;
    [self.view addSubview:self.customTableView];
}

-(void)customTableView:(CustomTableView *)tableView didSelectRowAtIndex:(NSInteger)index {
    NSLog(@"Selected row at index %ld", (long)index);
}
@end
  1. 优势与用途:代理模式使得对象之间的耦合度降低,提高了代码的可扩展性和维护性。在iOS开发中,UITableViewdelegatedataSource代理机制允许开发者自定义表格视图的行为,如设置单元格的样式、处理单元格的点击事件等,而不需要修改UITableView的内部实现。

观察者模式(Observer Pattern)

  1. 原理与Objective-C实现:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象。在Objective-C中,可以使用NSNotificationCenter来实现观察者模式。例如,在一个音乐播放应用中,当歌曲播放状态发生变化时,需要通知多个视图进行更新。首先,在歌曲播放管理类中发布通知:
#import <Foundation/Foundation.h>

@interface MusicPlayer : NSObject
-(void)playSong:(NSString *)songName;
@end

@implementation MusicPlayer
-(void)playSong:(NSString *)songName {
    // 播放歌曲逻辑
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MusicPlayerPlayStateChanged" object:self userInfo:@{@"songName": songName, @"state": @"playing"}];
}
@end

然后,在需要监听通知的视图控制器中注册观察者:

@interface MusicViewController : UIViewController
@end

@implementation MusicViewController
-(void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateUIForPlayStateChange:) name:@"MusicPlayerPlayStateChanged" object:nil];
}

-(void)updateUIForPlayStateChange:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    NSString *songName = userInfo[@"songName"];
    NSString *state = userInfo[@"state"];
    // 根据歌曲播放状态更新UI
    NSLog(@"Song %@ is in state %@, updating UI", songName, state);
}

-(void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"MusicPlayerPlayStateChanged" object:nil];
}
@end
  1. 应用场景:观察者模式常用于实现应用中的事件驱动机制,如UI界面的更新、数据模型变化的通知等。在iOS开发中,当键盘出现或消失时,系统会发布相应的通知,视图控制器可以注册为观察者,根据通知来调整界面布局。

结合代码重构与设计模式优化Objective-C项目

以一个复杂项目为例

假设我们正在开发一个社交类的iOS应用,包含用户资料展示、好友列表、聊天等功能。在项目初期,为了快速实现功能,代码可能存在很多问题。例如,在用户资料展示模块,获取用户资料的逻辑在多个视图控制器中重复出现,并且代码结构混乱。

  1. 代码重构步骤
    • 提取方法:将获取用户资料的逻辑提取成一个单独的方法,例如在UserService类中创建-(User *)fetchUserProfile方法。
    • 合并重复代码:将用户资料展示中一些相同的UI布局和样式设置代码提取到一个UserProfileUIHelper类中,避免在多个视图控制器中重复编写。
    • 重命名:对一些含义不明确的变量和方法进行重命名,如将getInfo方法重命名为fetchUserProfile,使代码更易读。
  2. 设计模式应用
    • 单例模式:可以创建一个AppDataManager单例类,用于管理应用全局的数据,如当前登录用户信息、应用配置等。这样在整个应用中可以方便地获取和修改这些数据,并且保证数据的一致性。
    • 代理模式:在聊天模块中,当收到新消息时,聊天视图可能需要将消息处理的一些逻辑委托给其他对象,如消息存储对象、消息提醒对象等。通过定义代理协议,实现对象之间的解耦。
    • 观察者模式:当用户的好友状态发生变化(如上线、下线)时,需要通知相关的视图进行更新。可以使用NSNotificationCenter来实现观察者模式,当好友状态变化时发布通知,相关视图注册为观察者并根据通知更新UI。
  3. 优化效果:经过代码重构和设计模式的应用,项目的代码结构更加清晰,可维护性和可扩展性大大提高。例如,当需要修改用户资料获取的接口时,只需要在UserService类的fetchUserProfile方法中修改;当需要添加新的好友状态变化处理逻辑时,只需要在相关的观察者方法中添加代码,而不会影响其他模块的功能。

持续优化的策略

  1. 定期代码审查:团队成员定期对代码进行审查,发现潜在的代码异味(如重复代码、过长的方法等),及时进行重构。在审查过程中,关注代码是否符合设计模式的最佳实践,是否有可以进一步优化的地方。
  2. 引入自动化工具:使用工具辅助代码重构,如Xcode的重构功能。同时,可以使用静态分析工具来检测代码中的潜在问题,如内存泄漏、未使用的变量等。例如,Clang静态分析器可以在编译时检测出很多常见的代码错误,帮助开发者提前发现和解决问题。
  3. 学习与实践新的技术和模式:随着技术的不断发展,新的设计模式和编程技巧不断涌现。开发团队应该持续学习,关注行业动态,将新的理念应用到项目中,不断优化代码质量。例如,随着iOS开发中响应式编程的兴起,可以考虑在项目中引入ReactiveCocoa等框架,通过响应式编程的方式来处理用户界面交互和数据绑定,提高代码的简洁性和可维护性。

通过不断地进行代码重构和合理应用设计模式,Objective-C项目能够保持良好的代码质量,适应不断变化的需求,为用户提供更加稳定和高效的应用体验。在实际开发过程中,开发者需要根据项目的具体情况,灵活运用这些技术,不断优化代码,打造出高质量的Objective-C应用。同时,要注重团队成员之间的沟通和协作,共同推动项目代码质量的提升。无论是小型的iOS应用还是大型的企业级项目,代码重构和设计模式的应用都是确保项目长期成功的关键因素。在日常开发中,养成良好的编程习惯,及时对代码进行优化,将为项目的维护和扩展带来极大的便利。随着项目的演进,不断地审视代码结构,适时地进行重构和引入新的设计模式,能使项目在面对复杂需求和变化时保持强大的生命力。例如,在项目功能迭代过程中,如果发现原有的模块之间耦合度较高,影响了新功能的添加,可以通过应用设计模式(如中介者模式)来降低耦合度,同时对相关代码进行重构,使新功能的集成更加顺畅。总之,代码重构与设计模式应用是Objective-C开发者提升代码质量、打造优秀应用的有力武器。