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

Objective-C 代码规范与最佳实践

2022-03-024.5k 阅读

命名规范

  1. 类名 类名通常采用驼峰命名法(Camel Case),并且首字母大写。类名应该能够清晰地描述该类的功能或所代表的事物。例如,一个处理用户信息的类可以命名为UserInfo,一个用于显示图片的视图类可以命名为ImageDisplayView
// 定义一个名为UserInfo的类
@interface UserInfo : NSObject
// 类的属性和方法声明
@end
  1. 方法名 方法名同样采用驼峰命名法,首字母小写。方法名应该详细地描述该方法的功能,且参数的命名也要具有描述性。例如,一个根据用户名获取用户信息的方法可以命名为fetchUserInfoWithUsername:
@interface UserInfoManager : NSObject
- (UserInfo *)fetchUserInfoWithUsername:(NSString *)username;
@end
  1. 变量名 变量名采用驼峰命名法,首字母小写。局部变量的命名应简洁且能反映其用途,实例变量通常以_下划线开头。例如,一个用于存储用户年龄的局部变量可以命名为userAge,而对应的实例变量可以命名为_userAge
@implementation UserInfo
{
    NSInteger _userAge;
}
- (void)setUserAge:(NSInteger)userAge
{
    _userAge = userAge;
}
@end
  1. 常量名 常量名一般使用全大写字母,单词之间用下划线分隔。例如,定义一个表示最大用户数量的常量可以命名为MAX_USER_COUNT
// 在类的实现文件中定义常量
static const NSInteger MAX_USER_COUNT = 100;

代码结构

  1. 类的结构 一个类的接口(.h文件)应该简洁明了,只公开必要的属性和方法。属性通常放在接口部分的开头,方法声明紧跟其后。在实现文件(.m文件)中,首先实现属性的存取方法(如果需要自定义),然后按照功能模块组织方法的实现。
// UserInfo.h
@interface UserInfo : NSObject
@property (nonatomic, copy) NSString *username;
@property (nonatomic, assign) NSInteger age;
- (void)printUserInfo;
@end

// UserInfo.m
@implementation UserInfo
- (void)printUserInfo
{
    NSLog(@"Username: %@, Age: %ld", self.username, (long)self.age);
}
@end
  1. 文件结构 一个项目中的文件应该按照功能模块进行组织。例如,所有与用户相关的类可以放在User目录下,与网络请求相关的类可以放在Network目录下。每个文件应该有清晰的注释,说明其用途。
ProjectRoot
│
├── User
│   ├── UserInfo.h
│   └── UserInfo.m
├── Network
│   ├── NetworkRequest.h
│   └── NetworkRequest.m
└── AppDelegate.h
└── AppDelegate.m
  1. 方法结构 方法的实现应该逻辑清晰,避免过长和过于复杂。如果一个方法包含多个功能,可以将其拆分成多个小的方法。例如,一个处理用户登录的方法可以拆分成验证用户名密码、发送网络请求、处理响应等多个方法。
@implementation UserLoginManager
- (BOOL)validateUsername:(NSString *)username password:(NSString *)password
{
    // 用户名和密码验证逻辑
    return YES;
}
- (void)sendLoginRequestWithUsername:(NSString *)username password:(NSString *)password
{
    // 网络请求逻辑
}
- (void)handleLoginResponse:(NSData *)responseData
{
    // 响应处理逻辑
}
- (void)loginWithUsername:(NSString *)username password:(NSString *)password
{
    if ([self validateUsername:username password:password]) {
        [self sendLoginRequestWithUsername:username password:password];
    }
}
@end

内存管理

  1. ARC(自动引用计数) ARC是Objective - C在iOS 5.0引入的一项内存管理机制,它自动管理对象的生命周期,大大减轻了开发者手动管理内存的负担。在ARC环境下,不需要手动调用retainreleaseautorelease方法。
// ARC环境下创建和使用对象
NSString *str = @"Hello, World!";
// 不需要手动释放str,ARC会自动管理其内存
  1. 手动内存管理(MRC,非ARC环境) 在非ARC环境下,需要手动管理对象的引用计数。当创建一个对象时,其引用计数为1,通过retain方法可以增加引用计数,通过release方法可以减少引用计数,当引用计数为0时,对象会被销毁。
// MRC环境下创建和管理对象
NSString *str = [[NSString alloc] initWithString:@"Hello, World!"];
[str retain];
// 使用str
[str release];
  1. 避免循环引用 循环引用是内存管理中常见的问题,会导致对象无法被释放,造成内存泄漏。例如,两个对象相互持有对方的引用就会形成循环引用。可以通过使用weakunsafe_unretained修饰符来打破循环引用。
@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *classB;
@end
@interface ClassB : NSObject
@property (nonatomic, weak) ClassA *classA; // 使用weak避免循环引用
@end

代码风格

  1. 缩进和空格 代码应该使用4个空格进行缩进,而不是制表符。在操作符(如+-*/等)两边加上空格,使代码更易读。
// 良好的缩进和空格风格
NSInteger result = number1 + number2;
  1. 注释 注释应该清晰明了,解释代码的功能和用途。对于复杂的算法或难以理解的代码块,应该添加详细的注释。单行注释使用//,多行注释使用/* */
// 计算两个数的和
NSInteger sum = number1 + number2;
/*
 下面的代码块用于处理
 当和大于100的情况
 */
if (sum > 100) {
    // 处理逻辑
}
  1. 代码格式化 Xcode等开发工具提供了代码格式化功能,可以通过快捷键(如Command + Shift + F)对代码进行格式化,使其风格统一。同时,也可以通过配置文件(如.clang-format)来定义自定义的代码格式化规则。

面向对象设计原则

  1. 单一职责原则(SRP) 一个类应该只有一个引起它变化的原因,即一个类应该只负责一项职责。例如,一个UserInfo类只应该负责管理用户信息,而不应该同时负责网络请求等其他职责。
// 符合单一职责原则的UserInfo类
@interface UserInfo : NSObject
@property (nonatomic, copy) NSString *username;
@property (nonatomic, assign) NSInteger age;
- (void)updateUserInfo;
@end
  1. 开闭原则(OCP) 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。例如,可以通过继承和多态来实现功能的扩展,而不需要修改原有类的代码。
@interface Shape : NSObject
- (CGFloat)area;
@end
@interface Circle : Shape
@property (nonatomic, assign) CGFloat radius;
- (CGFloat)area;
@end
@interface Rectangle : Shape
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat height;
- (CGFloat)area;
@end
  1. 里氏替换原则(LSP) 所有引用基类的地方必须能透明地使用其子类的对象。这意味着子类对象可以替代父类对象,而程序的行为不会发生改变。
Shape *shape = [[Circle alloc] init];
shape.radius = 5.0;
CGFloat area = [shape area]; // 这里可以用Circle对象替代Shape对象
  1. 依赖倒置原则(DIP) 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。例如,在网络请求中,可以定义一个抽象的网络请求协议,具体的网络请求类实现该协议,这样高层模块只依赖于该协议,而不依赖具体的网络请求实现类。
@protocol NetworkRequestProtocol <NSObject>
- (void)sendRequestWithURL:(NSURL *)url completion:(void (^)(NSData *data, NSError *error))completion;
@end
@interface AFNetworkRequest : NSObject <NetworkRequestProtocol>
- (void)sendRequestWithURL:(NSURL *)url completion:(void (^)(NSData *data, NSError *error))completion;
@end
@interface UserManager : NSObject
@property (nonatomic, strong) id<NetworkRequestProtocol> networkRequest;
- (void)fetchUserInfo;
@end
@implementation UserManager
- (void)fetchUserInfo
{
    NSURL *url = [NSURL URLWithString:@"http://example.com/userinfo"];
    [self.networkRequest sendRequestWithURL:url completion:^(NSData *data, NSError *error) {
        // 处理响应
    }];
}
@end
  1. 接口隔离原则(ISP) 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。例如,将一个大的接口拆分成多个小的接口,让不同的类只实现它们需要的接口。
@protocol DrawingProtocol <NSObject>
- (void)draw;
@end
@protocol MovingProtocol <NSObject>
- (void)move;
@end
@interface Ball : NSObject <DrawingProtocol, MovingProtocol>
- (void)draw;
- (void)move;
@end
@interface StaticShape : NSObject <DrawingProtocol>
- (void)draw;
@end

性能优化

  1. 懒加载 懒加载是指在需要使用某个对象时才进行创建,而不是在类初始化时就创建。这样可以提高程序的启动性能,减少不必要的内存占用。例如,对于一个不经常使用的视图,可以采用懒加载的方式。
@implementation ViewController
@property (nonatomic, strong) UIView *customView;
- (UIView *)customView
{
    if (!_customView) {
        _customView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
        // 初始化customView的其他设置
    }
    return _customView;
}
@end
  1. 缓存 在处理频繁获取的数据时,可以使用缓存来提高性能。例如,在网络请求中,可以将请求的结果缓存起来,下次请求相同数据时直接从缓存中获取,而不需要再次发起网络请求。
@interface NetworkCache : NSObject
@property (nonatomic, strong) NSMutableDictionary *cacheDictionary;
- (id)cachedObjectForKey:(NSString *)key;
- (void)cacheObject:(id)object forKey:(NSString *)key;
@end
@implementation NetworkCache
- (id)cachedObjectForKey:(NSString *)key
{
    return self.cacheDictionary[key];
}
- (void)cacheObject:(id)object forKey:(NSString *)key
{
    if (!self.cacheDictionary) {
        self.cacheDictionary = [NSMutableDictionary dictionary];
    }
    self.cacheDictionary[key] = object;
}
@end
  1. 优化循环 在使用循环时,尽量减少循环体内的计算和对象创建。例如,可以将循环条件中不变的计算提取到循环外部,避免在每次循环时重复计算。
// 优化前
for (NSInteger i = 0; i < [array count]; i++) {
    NSObject *obj = array[i];
    // 处理obj
}
// 优化后
NSUInteger count = [array count];
for (NSInteger i = 0; i < count; i++) {
    NSObject *obj = array[i];
    // 处理obj
}

错误处理

  1. NSError的使用 在方法调用可能出现错误的情况下,应该通过NSError **参数返回错误信息。调用方可以根据返回的NSError对象来处理错误。
- (BOOL)readFileAtPath:(NSString *)path error:(NSError **)error
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:path]) {
        if (error) {
            *error = [NSError errorWithDomain:@"FileErrorDomain" code:1 userInfo:nil];
        }
        return NO;
    }
    // 读取文件逻辑
    return YES;
}
  1. 异常处理 虽然Objective - C支持异常处理,但在iOS开发中,通常不建议使用异常来处理程序中的错误,因为异常可能导致程序崩溃,并且在ARC环境下,异常可能会导致内存泄漏。如果必须使用异常,应该在@try块中进行可能抛出异常的操作,在@catch块中捕获并处理异常。
@try {
    // 可能抛出异常的代码
    NSArray *array = @[@1, @2];
    NSNumber *number = array[10]; // 访问越界会抛出异常
} @catch (NSException *exception) {
    NSLog(@"Caught exception: %@", exception);
}

与其他框架的集成

  1. 与UIKit的集成 Objective - C在iOS开发中广泛使用UIKit框架来构建用户界面。在使用UIKit时,要遵循其设计模式和最佳实践。例如,在创建视图控制器时,要正确实现其生命周期方法,如viewDidLoadviewWillAppear:等。
@interface MyViewController : UIViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    [button setTitle:@"Click me" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}
- (void)buttonTapped
{
    NSLog(@"Button tapped");
}
@end
  1. 与Core Data的集成 Core Data是用于在iOS和macOS应用中进行数据持久化的框架。在与Core Data集成时,要正确设计数据模型,并且合理使用NSManagedObjectContextNSPersistentStoreCoordinator等核心对象。
// 获取应用的持久化容器
NSPersistentContainer *persistentContainer = [(AppDelegate *)[UIApplication sharedApplication].delegate persistentContainer];
NSManagedObjectContext *context = persistentContainer.viewContext;
// 创建一个新的实体对象
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:context];
NSManagedObject *user = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
[user setValue:@"John" forKey:@"username"];
// 保存上下文
NSError *error = nil;
if (![context save:&error]) {
    NSLog(@"Error saving context: %@", error);
}
  1. 与第三方框架的集成 在集成第三方框架时,要仔细阅读其文档,了解其使用方法和注意事项。例如,在集成AFNetworking进行网络请求时,要按照其提供的API来设置请求参数、处理响应等。
#import "AFNetworking.h"
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *parameters = @{@"key": @"value"};
[manager POST:@"http://example.com/api" parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSLog(@"Success: %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"Failure: %@", error);
}];

测试

  1. 单元测试 可以使用Xcode自带的XCTest框架进行单元测试。单元测试主要测试类的单个方法的功能是否正确。例如,对于一个计算两个数之和的方法,可以编写如下单元测试。
#import <XCTest/XCTest.h>
@interface Calculator : NSObject
- (NSInteger)addNumber1:(NSInteger)number1 number2:(NSInteger)number2;
@end
@implementation Calculator
- (NSInteger)addNumber1:(NSInteger)number1 number2:(NSInteger)number2
{
    return number1 + number2;
}
@end
@interface CalculatorTests : XCTestCase
@end
@implementation CalculatorTests
- (void)testAddition
{
    Calculator *calculator = [[Calculator alloc] init];
    NSInteger result = [calculator addNumber1:2 number2:3];
    XCTAssertEqual(result, 5, @"The result of addition is incorrect");
}
@end
  1. 集成测试 集成测试用于测试多个组件之间的交互是否正确。例如,在测试网络请求与数据解析的集成时,可以模拟网络响应,测试数据解析是否正确。
#import <XCTest/XCTest.h>
#import "NetworkRequest.h"
#import "DataParser.h"
@interface NetworkIntegrationTests : XCTestCase
@end
@implementation NetworkIntegrationTests
- (void)testNetworkAndParser
{
    NetworkRequest *request = [[NetworkRequest alloc] init];
    NSData *mockResponseData = [@"{\"key\":\"value\"}" dataUsingEncoding:NSUTF8StringEncoding];
    id result = [request parseResponseData:mockResponseData];
    XCTAssertNotNil(result, @"Data parsing failed");
}
@end
  1. 性能测试 性能测试可以使用工具如Instruments来分析应用的性能瓶颈。例如,可以通过Instruments的Time Profiler工具来查看哪些方法消耗的时间最多,从而进行针对性的优化。在代码中,可以使用CFAbsoluteTimeGetCurrent()函数来测量某个代码块的执行时间。
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
// 要测试的代码块
for (NSInteger i = 0; i < 1000000; i++) {
    // 一些计算
}
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
CFTimeInterval executionTime = endTime - startTime;
NSLog(@"Execution time: %f seconds", executionTime);