前言
6、什么是KVC、KVO?
7、通知和协议的不同之处?
8、单例是什么?优缺点是什么?
9、什么是懒加载?优缺点是什么?
10、static什么时候使用?有什么作用?
6、什么是KVC、KVO?
KVC(Key-Value Coding)
KVC
是一种通过键值
访问对象的属性
的机制,允许通过键(属性名
)来访问
和修改
对象的属性值。这使得我们可以通过字符串
形式访问对象的属性,而不需要直接调用相应的方法。
在项目中,以下一些情况下你可能会使用KVC
:
1、设置和获取对象的属性
KVC提供了一种方便的方式来设置和获取对象的属性,尤其是当属性名以字符串形式
存在时。这对于在运行时动态地访问或修改对象的属性
非常有用。
// 设置对象属性
[object setValue:@"John" forKey:@"name"];
// 获取对象属性
NSString *name = [object valueForKey:@"name"];
2、字典和模型的转换
当从服务器或其他数据源获得数据时,通常以字典
的形式提供。使用KVC,你可以方便将字典中的键值对映射到对象的属性
,实现字典和模型对象之间的转换
。
NSDictionary *data = @{@"name": @"John", @"age": @25};
// 使用KVC将字典转换为对象
Person *person = [[Person alloc] init];
[person setValuesForKeysWithDictionary:data];
3、动态地访问属性
当需要动态地访问或操作对象的属性
时,KVC提供了一种方便的机制。这对于通用的数据操作
、表单处理
等场景非常有用。
NSString *propertyName = @"name";
NSString *propertyValue = [object valueForKey:propertyName];
4、集合操作
KVC提供了一套用于集合操作
的方法,例如对数组中的每个元素执行某个操作
、计算数组中的最大或最小值
等。这对于处理集合数据非常方便。
NSArray *numbers = @[ @10, @20, @30 ];
// 计算数组中的总和
NSNumber *sum = [numbers valueForKeyPath:@"@sum.self"];
5、在自定义控制台输出中使用
在调试时,通过覆盖description
方法并使用KVC
来生成自定义控制台输出,可以方便地查看对象的内部状态。
- (NSString *)description {
return [NSString stringWithFormat:@"Person - Name: %@, Age: %@", [self valueForKey:@"name"], [self valueForKey:@"age"]];
}
优点:
-
灵活性
:提供了一种灵活的方式来访问和修改对象的属性,特别是在处理动态和未知属性的情况下。 -
简化代码
:通过KVC,可以减少访问和修改对象属性的代码量,使代码更加简洁。
//正常情况下某个类的创建
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
//使用KVC
NSDictionary *data = @{@"name": @"John", @"age": @25};
// 使用 KVC 将字典转换为对象
Person *person = [[Person alloc] init];
[person setValuesForKeysWithDictionary:data];
-
便于集合操作
:提供了一套用于集合操作的方法,例如对数组中的每个元素执行某个操作、计算数组中的最大或最小值等。
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
@end
// 创建包含 Person 对象的数组
NSArray *people = @[ [[Person alloc] initWithName:@"John"],
[[Person alloc] initWithName:@"Jane"],
[[Person alloc] initWithName:@"Bob"] ];
// 使用 KVC 获取所有人的姓名组成的新数组
NSArray *names = [people valueForKeyPath:@"@each.name"];
NSLog(@"Names: %@", names);
-
实现通用方法
:使得能够实现通用的数据 操作,例如字典和模型之间的转换。
缺点
-
运行时错误
:由于KVC使用字符串形式来访问属性,编译器无法提供错误检查,因此在运行时可能会发生拼写错误或传递了不存在的属性名,导致崩溃。 -
性能开销
:相对于直接方法调用,KVC的性能开销较大。因此,对性能有严格要求的场景下,可能不适合大规模使用KVC。 -
不支持编译器优化
:不容易被编译器优化,因为它是在运行时动态解析的,这可能导致一些性能上的损失。
KVO(Key-Value Observing)
KVO
允许一个对象监听
另一个对象特定属性值的变化
。当被观察的对象的属性发生改变时,观察者会收到通知
。这提供了一种在对象之间保持同步
的机制。
在项目中,以下一些情况下你可能会使用KVO
:
1、UI更新
当数据模型的属性变化时,需要更新用户界面。
// 添加 KVO 观察者
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// 实现 KVO 观察者方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
// 在这里执行刷新 UI 的操作
[self updateUIWithNewName:change[NSKeyValueChangeNewKey]];
}
}
2、数据同步
当多个对象
之间需要保持数据同步
时,可以使用KVO。一个对象的属性变化
将会通知到观察者
,观察者可以做出相应的响应,保持数据的一致性
。
3、观察属性变化执行特定操作
在属性变化时,需要执行一些特点的操作,而不是仅仅更新UI。
4、监控网络请求状态
在进行网络请求时,有时需要监控网络请求的状态变化,以便在请求完成时执行一些操作。
5、自定义观察者模式
KVO提供了一种机制来实现观察者模式,用于建立对象之间的通信。这样可以减少对象之间的直接耦合。
ps:使用KVO时应该遵循一些最佳实践,如正确
地注册
和移除
观察者,处理观察者通知时的线程安全
等。
优点
-
松耦合
:KVO实现了观察者模式,使得观察者(监听者)和被观察者之间的耦合度较低。对象不需要直接调用观察者的方法,而是通过通知的方式进行通信。 -
动态性
:KVO允许在运行时动态地注册和取消观察者,以及动态地选择观察的属性。这种动态性使得在不同场景下适应变化更加容易。
// 被观察的对象
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
@end
// 观察者
@interface Observer : NSObject
@end
@implementation Observer
- (instancetype)init {
self = [super init];
if (self) {
// 创建被观察对象
Person *person = [[Person alloc] init];
// 添加观察者
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// 模拟属性变化,触发 KVO
person.name = @"John";
// 移除观察者
[person removeObserver:self forKeyPath:@"name"];
}
return self;
}
// 实现 KVO 观察者方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"Name changed to: %@", change[NSKeyValueChangeNewKey]);
}
}
@end
-
简化代码
:使用KVO可以简化代码,特别是在需要响应属性变化并执行相应操作
的情况下。这有助于避免在代码中插入大量的回调方法
。
缺点
-
难以调试
:由于KVO是在运行时
进行动态注册
和移除
的,因此在调试时
可能会导致一些难以追踪的问题。观察者的添加和移除时机需要特别注意,否则可能会导致崩溃
或不符合预期的行为
。 -
性能开销
:KVO的实现涉及到动态方法解析
和消息转发
,相对于直接调用方法来说,会有一些性能开销。在性能要求较高的场景中,可能需要谨慎使用KVO
。 -
不支持对基本数据类型的直接观察
:KVO主要适用于对象,不直接支持对基本数据类型(例如NSInteger、CGFloat等)的观察。
7、通知和协议的不同之处?
通知
和协议
时iOS中两种不同的机制,用于实现对象之间的通信和协作
。
通知
-
发布/订阅模式
:采用发布/订阅模式,允许一个对象发布通知,而其他对象则可以注册成为观察者并在通知发生时得到通知。 -
中心化管理
:通知的管理由通知中心(NSNotificationCenter
)来完成,对象通过通知中心注册和接收通知。 -
一对多关系
:一个通知可以有多个观察者,观察者可以订阅多个通知。 -
松散耦合
:发送通知的对象和接收通知的对象之间的关系相对松散,它们不需要直接知道对方的存在。 -
异步
:通知是异步的,通知的发送和接收不需要在同一时间和线程上。
// 发送通知的代码
[[NSNotificationCenter defaultCenter] postNotificationName:@"SomeNotification" object:nil];
// 接收通知的代码
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"SomeNotification" object:nil];
// 通知处理方法
- (void)handleNotification:(NSNotification *)notification {
NSLog(@"Received notification: %@", notification.name);
}
协议
-
定义接口
:协议定义了一组方法
,表示一种接口,对象通过采用协议
来表明它们实现了这个接口。 -
实现
:一个类可以采用一个或多个协议
,表示它们具有
协议定义的方法。 -
强制执行
:采用协议的类必须
实现协议中定义的所有方法,否则会在编译时报错
。 -
一对一关系
:协议通常用于实现一对一的关系,一个类实现一个协议。 -
编译时检查
:编译器会检查是否实现了协议中的所有方法,确保符合协议的要求
// 定义一个协议
@protocol MyProtocol
- (void)doSomething;
@end
// 类采用协议
@interface MyClass : NSObject <MyProtocol>
@end
@implementation MyClass
- (void)doSomething {
NSLog(@"Doing something");
}
@end
如果通信涉及到多个对象
,且这些对象之间的关系比较松散
,通知是一个不错的选择
。
如果通信是一对一
的关系,并且需要确保
采用协议的对象实现
了一组特定的方法,协议是更适合的选择
。
8、单例是什么?优缺点是什么?
单例
是一种设计模式
,它确保一个类
只有一个实例
,并提供一个全局访问点来获取该实例。单例模式通常使用静态方法或类方法
(类似于sharedInstance
)来返回唯一的实例。
优点
-
全局访问
:可以在整个应用程序中通过单一
的访问点获取相同的实例,方便数据共享
和操作
。 -
避免重复创建
:由于单例
只有一个实例,可以避免重复创建相同类型的对象,减少资源占用
。 -
共享状态
:单例可以用于共享状态
,例如应用配置信息
、登录状态
等。
缺点
-
全局状态
:单例引入了全局状态,可能会导致代码的耦合性增加,使得代码难以测试和维护。 -
隐藏依赖
:在某些情况下,单例可能会隐藏类之间的依赖关系,增加代码的复杂性。 -
生命周期管理
:单例的生命周期通常贯穿整个应用程序,可能会导致对象持有时间过长,无法及时释放。
// MySingleton.h
@interface MySingleton : NSObject
@property (nonatomic, strong) NSString *data;
+ (instancetype)sharedInstance;
@end
// MySingleton.m
@implementation MySingleton
// 静态变量用于保存唯一实例
static MySingleton *_sharedInstance = nil;
+ (instancetype)sharedInstance {
// 使用GCD确保线程安全创建单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[super alloc] init];
});
return _sharedInstance;
}
- (instancetype)init {
// 防止通过init方法创建新的实例
NSAssert(!_sharedInstance, @"Use sharedInstance method to get the instance.");
self = [super init];
if (self) {
// 初始化
}
return self;
}
@end
9、什么是懒加载?优缺点是什么?
懒加载
是一种延迟加载对象或执行操作
的技术,它在需要的时候
才进行加载
或执行
,而不是在应用启动时就立即加载或执行。懒加载通常用于延迟加载视图、数据或其他资源
,以提高
应用程序的性能
和资源利用率
。
优点
-
性能优化
:懒加载可以减少
应用程序启动时
的资源消耗
,因为只有在需要的时候
才会加载相应的对象或执行相关
的操作。这有助于提高
应用程序的启动速度
和响应性能
。 -
节省资源
:对于一些不一定
会被使用的对象
或数据
,懒加载可以避免
不必要的资源占用
,从而节省内存
和其他系统资源
。 -
更快的启动时间
:通过延迟加载,应用程序启动时
需要加载的资源较少
,从而加快
启动时间。
缺点
-
复杂性增加
:在代码中使用懒加载可能会增加代码的复杂性,因为你需要管理对象的状态并确保在需要时正确地进行加载。这可能导致一些潜在的错误,如内存泄露
或逻辑错误
。 -
可能引起延迟
:如果懒加载的时机选择不当,可能导致在需要对象或数据时出现短暂的延迟,因为此时才进行加载。 -
可能影响代码可读性
:过度使用懒加载可能会导致代码难以理解,特别是对于新加入的开发人员。适度使用,并在必要时添加注释以解释懒加载的原因和时机。
10、static什么时候使用?有什么作用?
static
关键字用于不同的上下文,其作用取决于它所修饰的内容。
1、静态局部变量
:在方法调用之间保留其值
,而不像普通局部变量那样在每次调用时重新初始化。这可以用于实现在方法之间保持状态
的需求。
- (void)someMethod {
static NSInteger counter = 0;
counter++;
NSLog(@"Counter: %ld", counter);
}
2、静态全局变量
:在文件范围内
可见,但是其作用域
限制在当前文件
中。这可以用于在整个文件中共享状态或数据
。
// 在文件的顶部或实现文件的全局作用域内
static NSInteger globalCounter = 0;
- (void)someMethod {
globalCounter++;
NSLog(@"Global Counter: %ld", globalCounter);
}
3、静态函数
:作用域限制在当前文件中,不可被其他文件访问。这可以用于实现文件内部的私有函数,不暴露给其他文件使用。
// 在文件的顶部或实现文件的全局作用域内
static NSInteger multiplyByTwo(NSInteger number) {
return number * 2;
}
- (void)someMethod {
NSInteger result = multiplyByTwo(5);
NSLog(@"Result: %ld", result);
}
4、静态常量
:用于定义在整个文件内都可见的常量,避免魔法数字的使用。常量通常以static const
的形式定义,确保其在编译时被优化。
static const NSInteger MaxItemCount = 10;
- (void)someMethod {
NSLog(@"Max Item Count: %ld", MaxItemCount);
}
-
static
的主要作用是限制变量或函数的作用域
,使其仅在定义它们的文件内可见。 -
在
方法内部
使用static
关键字可以使局部变量在方法调用之间保留其值
。 -
在
全局范围内
使用static
可以限制
变量、函数或常量的作用域
,使其在当前文件中可见。
网友评论