面试题答案
一键面试运用属性注入技术提高代码可维护性和扩展性的思路
- 明确依赖关系
- 在大型项目中,类与类之间通常存在复杂的依赖关系。通过属性注入,可以清晰地将一个类所依赖的其他对象以属性的形式暴露出来。例如,如果一个视图控制器需要一个网络服务对象来获取数据,那么可以在视图控制器中定义一个网络服务属性。
- 这样在创建视图控制器实例时,就明确知道它依赖于网络服务,便于理解和维护代码结构。
- 解耦组件
- 属性注入有助于将不同功能的组件解耦。以一个电商应用为例,商品详情页面的视图控制器可能依赖商品数据模型、图片加载器和评论服务。通过属性注入,视图控制器只关心如何使用这些依赖,而不关心它们的具体实现。如果图片加载器的实现方式从本地缓存加载改为云存储加载,只需要在注入图片加载器属性时提供新的实现类,而不需要修改视图控制器内部关于图片加载的业务逻辑。
- 提高代码复用性
- 对于一些通用的组件,如日志记录器、数据缓存等,可以通过属性注入到不同的类中。比如,多个视图控制器都需要记录操作日志,通过将日志记录器属性注入到这些视图控制器中,日志记录器的代码可以被复用,而不需要在每个视图控制器中重复实现日志记录逻辑。
- 便于测试
- 在单元测试中,属性注入使得可以轻松替换真实的依赖对象为模拟对象。例如,测试一个处理订单的类,该类依赖于支付网关服务。在测试时,可以通过属性注入一个模拟的支付网关服务对象,这样可以控制测试环境,确保测试的准确性和独立性。
可能遇到的问题及解决方案
- 循环依赖问题
- 问题描述:当两个或多个类相互依赖,形成循环时,会导致对象创建失败或内存泄漏。例如,类A依赖类B,而类B又依赖类A。
- 解决方案:
- 重新设计架构:分析循环依赖的原因,尝试重新划分模块或调整类的职责,打破循环。比如,可以将A和B共同依赖的部分提取出来,形成一个新的独立模块,A和B都依赖这个新模块,从而消除循环依赖。
- 使用弱引用:在Objective - C中,可以在属性声明时使用
weak
关键字来解决部分循环依赖问题。如果A类对B类的依赖关系不需要强引用(即不需要持有B类对象的生命周期),可以将A类中B类的属性声明为weak
。例如:@property (nonatomic, weak) ClassB *bObject;
- 空指针异常问题
- 问题描述:如果在使用依赖对象前没有进行属性注入,或者注入的属性为
nil
,在访问该属性时会导致空指针异常,程序崩溃。 - 解决方案:
- 初始化时注入:在对象初始化方法中进行属性注入,确保对象在使用前依赖属性已被正确赋值。例如,在视图控制器的
init
方法中注入网络服务对象:
- 初始化时注入:在对象初始化方法中进行属性注入,确保对象在使用前依赖属性已被正确赋值。例如,在视图控制器的
- 问题描述:如果在使用依赖对象前没有进行属性注入,或者注入的属性为
- (instancetype)initWithNetworkService:(NetworkService *)networkService {
self = [super init];
if (self) {
_networkService = networkService;
}
return self;
}
- **使用断言**:在代码中对关键的依赖属性进行断言检查,确保属性不为`nil`。例如:
- (void)fetchData {
NSAssert(_networkService!= nil, @"Network service is not injected");
[_networkService fetchDataWithCompletion:^(NSData *data, NSError *error) {
// 处理数据
}];
}
- 属性注入的性能问题
- 问题描述:频繁地进行属性注入,特别是在循环或高频率调用的代码段中,可能会带来一定的性能开销,尤其是涉及到对象的创建和赋值操作。
- 解决方案:
- 缓存依赖对象:对于一些频繁使用且创建开销较大的依赖对象,可以进行缓存。例如,如果一个视图控制器频繁需要获取用户信息服务对象,可以在视图控制器类中设置一个静态变量来缓存该对象,避免每次都重新注入。
static UserInfoService *userInfoService;
+ (UserInfoService *)sharedUserInfoService {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
userInfoService = [[UserInfoService alloc] init];
});
return userInfoService;
}
然后在需要注入的地方使用这个共享的服务对象。
- 延迟注入:对于一些在对象初始化时不需要立即使用的依赖属性,可以采用延迟注入的方式。即在真正需要使用该属性时再进行注入。例如,可以在属性的getter
方法中进行注入:
- (SomeService *)someService {
if (!_someService) {
_someService = [[SomeService alloc] init];
}
return _someService;
}