美文网首页
iOS基础 3.0

iOS基础 3.0

作者: 脚踏实地的小C | 来源:发表于2023-11-19 14:56 被阅读0次

    前言

    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可以限制变量、函数或常量的作用域,使其在当前文件中可见。

    相关文章

      网友评论

          本文标题:iOS基础 3.0

          本文链接:https://www.haomeiwen.com/subject/kacmwdtx.html