Effective Objective-C 2.0 读书笔记——协议和分类

Effective Objective-C 2.0 读书笔记------协议和分类

文章目录

  • [Effective Objective-C 2.0 读书笔记------协议和分类](#Effective Objective-C 2.0 读书笔记——协议和分类)

在分类中添加属性

尽管从技术上说,分类里也可以声明属性,但这 种做法还是要尽量避免。原因在于,除了class-continuation分类之外,其 他分类都无法向类中新增实例变量,因此,它们无法把实现属性所需的实例变量合成出来。

由于我们在编写功能较多的类时,一般都会给每一个代码开辟一个分类,让他负责一部分的功能,但是我们在编写分类时,尽量不要在分类之中添加对应属性,所有的属性都应该在主接口(main interface)

如果我们尝试在分类之中添加属性

objc 复制代码
@interface MyClass (MyCategory)
@property (nonatomic, strong) NSString *categoryProperty;
@end

上述代码 不会 生成 categoryProperty 的实例变量或存储实现。如果你尝试 self.categoryProperty = @"test";,会导致编译错误。

如果真的想要将在分类之中添加属性,那么这就需要用到我们的关联对象

objc 复制代码
#import <objc/runtime.h>

@interface MyClass (MyCategory)
@property (nonatomic, strong) NSString *categoryProperty;
@end

@implementation MyClass (MyCategory)

static const void *CategoryPropertyKey = &CategoryPropertyKey; // 关联对象的 key

// getter 方法
- (NSString *)categoryProperty {
    return objc_getAssociatedObject(self, CategoryPropertyKey);
}

// setter 方法
- (void)setCategoryProperty:(NSString *)categoryProperty {
    objc_setAssociatedObject(self, CategoryPropertyKey, categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

书中的原话:

这样做可行,但不太理想。要把相似的代码写很多遍,而且在内存管理问题上容易出错, 因为我们在为属性实现存取方法时,经常会忘记遵从其内存管理语义。比方说,你可能通过属性特质 (attribute ) 修改了某个属性的内存管理语义 。 而此时还要记得,在设置方法中也得修改设置关联对象时所用的内存管理语义才行。所以说,尽管这个做法不坏,但笔者并不推荐。

虽然我们不建议在分类中直接使用属性,但是我们可以视情况而定,选择使用只读的属性

假设我们要为 NSCalendar 类创建一个分类,用以返回一个包含各个月份名称的字符串数组。初始做法是利用只读属性来实现:

objc 复制代码
@interface NSCalendar (EOC_Additions)
@property (nonatomic, strong, readonly) NSArray *eoc_allMonths;
@end

@implementation NSCalendar (EOC_Additions)
- (NSArray *)eoc_allMonths {
    if ([self.calendarIdentifier isEqualToString:NSGregorianCalendar]) {
        return @[@"January", @"February", @"March", @"April",
                 @"May", @"June",
                 @"July", @"August",
                 @"September", @"October", @"November", @"December"];
    } else if (/* other calendar identifiers */) {
        /* return months for other calendars */
    }
    return nil;
}
@end

使用 "class-continuation分类" 隐藏实现细节

class-continuation其实就是不命名的分类,没有名字的分类自然没办法在其他地方被应用,这就引申出我们使用它的场景------我们在编程的过程之中只需要对外公布的那部分内容公开,那么不公开的就会被放在class-continuation分类之中,这个class-continuation有什么具体作用呢?

隐藏实现细节:当我们希望将某些属性,方法或者协议名字设为私有,只对类内部使用时,使用类扩展可以将这些声明放在 .m 文件中,避免在头文件中暴露。

读写属性与只读属性的分离 :我们前面的读书笔记有写过,在公开接口中将属性声明为只读(readonly),而在类扩展中重新声明为读写(readwrite),从而允许内部修改

  1. 在 .m 文件中声明类扩展

    在实现文件开头的 @interface 部分添加匿名类别,用于声明私有属性和方法:

    objc 复制代码
    // MyClass.m
    
    #import "MyClass.h"
    
    @interface MyClass ()
    @property (nonatomic, strong) NSString *privateProperty; // 私有属性
    - (void)privateMethod; // 私有方法
    @end
  2. 在 @implementation 中实现私有属性和方法

    objc 复制代码
    @implementation MyClass
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            // 可以初始化私有属性
            _privateProperty = @"初始值";
        }
        return self;
    }
    
    - (void)privateMethod {
        NSLog(@"调用了私有方法,privateProperty = %@", self.privateProperty);
    }
    
    // 公开方法可能会调用私有方法
    - (void)publicMethod {
        [self privateMethod];
    }
    
    @end
  • 私有属性 :上例中的 privateProperty 在 MyClass 的头文件中没有声明,因此外部无法访问或修改它。
  • 私有方法privateMethod 仅在实现文件中可见,确保了内部实现的封装性。

通过协议提供匿名对象

协议定义了一系列方法,遵从此协议的对象应该实现它们,我们可以用协议把自己所写的API 之中的实现细节隐藏起来,将返回的对象设计为遵从此协议的纯i d类型。这样的话,想要隐藏的类名就不会出现在API 之中了。若是接又背后有多个不同的实现类,而你又不想指明具体使用哪个类,那么可以考虑用这个办法------因为有时候这些类可能会变,有时候它们又无法容纳于标准的类继承体系中,因而不能以某个公共基类来统一表示。

就是说,一个方法返回的对象只暴露其所遵循的协议(接口),而隐藏了具体的实现类。调用者只知道它能响应协议中定义的方法,而不必关心其背后的具体类型。

书中给出的例子:首先定义一个协议,该协议描述了需要实现的接口。例如,书中给出的数据库连接的例子中,可以定义一个协议 EOCDatabaseConnection

objc 复制代码
@protocol EOCDatabaseConnection <NSObject>
- (void)connect;
- (void)disconnect;
- (BOOL)isConnected;
- (NSArray *)performQuery:(NSString *)query;
@end

这个协议规定了所有数据库连接对象必须实现的方法,不论它们的内部实现如何。

在需要返回数据库连接的类中,我们不返回具体的类,而是返回一个遵循该协议的对象。比如,一个数据库管理器的接口可以这样设计:

objc 复制代码
@interface EOCDatabaseManager : NSObject
+ (instancetype)sharedInstance;
- (id<EOCDatabaseConnection>)connectionWithIdentifier:(NSString *)identifier;
@end

其中,方法 connectionWithIdentifier: 返回的类型是 id<EOCDatabaseConnection>,这表明返回的对象必须遵循 EOCDatabaseConnection 协议,但调用者不必知道它具体是什么类。

假设有一个具体类 MyDatabaseConnection 实现了该协议:

objc 复制代码
@interface MyDatabaseConnection : NSObject <EOCDatabaseConnection>
@property (nonatomic, assign) BOOL connected;
@end

@implementation MyDatabaseConnection

- (void)connect {
    NSLog(@"MyDatabaseConnection: 正在连接数据库...");
    self.connected = YES;
}

- (void)disconnect {
    NSLog(@"MyDatabaseConnection: 断开数据库连接...");
    self.connected = NO;
}

- (BOOL)isConnected {
    return self.connected;
}

- (NSArray *)performQuery:(NSString *)query {
    NSLog(@"MyDatabaseConnection: 执行查询: %@", query);
    // 这里只返回模拟的数据
    return @[@"结果1", @"结果2", @"结果3"];
}

@end

数据库管理器的实现中可以根据标识符返回合适的连接对象,而调用者只关心它是否符合 EOCDatabaseConnection 协议:

objc 复制代码
@implementation EOCDatabaseManager

+ (instancetype)sharedInstance {
    static EOCDatabaseManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[EOCDatabaseManager alloc] init];
    });
    return instance;
}

- (id<EOCDatabaseConnection>)connectionWithIdentifier:(NSString *)identifier {
    // 根据 identifier 可以决定返回哪种具体实现
    MyDatabaseConnection *connection = [[MyDatabaseConnection alloc] init];
    [connection connect]; // 自动建立连接
    return connection;
}

@end

调用者代码则只需依赖协议接口:

objc 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        EOCDatabaseManager *dbManager = [EOCDatabaseManager sharedInstance];
        id<EOCDatabaseConnection> connection = [dbManager connectionWithIdentifier:@"MainDB"];
        
        if ([connection isConnected]) {
            NSLog(@"数据库已连接");
            NSArray *results = [connection performQuery:@"SELECT * FROM users"];
            NSLog(@"查询结果: %@", results);
            [connection disconnect];
        } else {
            NSLog(@"数据库连接失败");
        }
    }
    return 0;
}

在这里,调用者只知道 connection 符合 EOCDatabaseConnection 协议,不需要关心它内部是 MyDatabaseConnection 或其他类型。

相关推荐
带娃的IT创业者38 分钟前
机器学习实战(8):降维技术——主成分分析(PCA)
人工智能·机器学习·分类·聚类
调皮的芋头1 小时前
iOS各个证书生成细节
人工智能·ios·app·aigc
coooliang3 小时前
【iOS】SwiftUI状态管理
ios·swiftui·swift
胖虎18 小时前
深入解析 iOS 视频录制(三):完整录制流程的实现与整合
ios·音视频·cocoa·ios视频录制·自定视频录制
coooliang8 小时前
【iOS】包大小和性能稳定性优化
ios
又知道了小老虎9 小时前
iOS开发基础-通过C++快速掌握Objective-C语言(基础)
ios
关键帧Keyframe10 小时前
音视频面试题集锦第 19 期 | 读取纹理数据
ios·图像识别·音视频开发
安和昂13 小时前
effective-Objective-C第六章阅读笔记
开发语言·笔记·objective-c
UWA16 小时前
iOS进程增加内存上限的接口
ios·memory·terrain
白-胖-子1 天前
DeepSeek系统架构的逐层分类拆解分析,从底层基础设施到用户端分发全链路
人工智能·机器学习·分类·数据挖掘·系统架构·agi·deepseek