iOS Objective-C 类扩展
1. 类扩展简介
类扩展是我们在开发中经常忽略的一个知识点。就我个人来说,一直认为类扩展就是类中的一部分,我们主要在其中声明私有属性,其实不是,因为类扩展是单独存在的,我们新建一个类的时候并不会主动创建类扩展。但也是,因为类扩展在类编译的时候一起编译。
类扩展的定义:
A class extension bears some similarity to a category, but it can only be added to a class for which you have the source code at compile time (the class is compiled at the same time as the class extension). The methods declared by a class extension are implemented in the @implementation block for the original class so you can’t, for example, declare a class extension on a framework class, such as a Cocoa or Cocoa Touch class like NSString.
译:类扩展与分类有一些相似之处,但它只能添加到在编译时拥有源代码的类中(类与类扩展同时编译)。类扩展声明的方法是在原始类的@implementation块中实现的,所以你不能在框架类上声明类扩展,比如Cocoa或Cocoa Touch类,比如NSString。
类扩展的声明语法:
@interface ClassName ()
@end
新建一个类扩展:
我们在Xcode
中创建Objective
类型的文件的时候可以选择空文件、分类、协议以及类扩展。
虽然Xcode
给我们提供了新建类扩展的选项,但是一般我们不这样用,我们一般都是在.m
文件中声明一下当前的类扩展,其实协议的声明我们大多数也是这样做的。
2. 类拓展的应用
2.1 添加属性
@interface XYZPerson ()
@property NSObject *extraProperty;
@end
2.2 添加实例变量
添加自定义实例变量。需要在类扩展接口的大括号中声明。
@interface XYZPerson () {
id _someCustomInstanceVariable;
}
@end
2.3 隐藏私有信息
在类中声明一个只读的属性uniqueIdentifier
如下,这样我们暴露给外界的时候就是只读的。
@interface XYZPerson : NSObject
...
@property (readonly) NSString *uniqueIdentifier;
- (void)assignUniqueIdentifier;
@end
但是我们想在类内部进行更改就可以通过类扩展来实现,如下
@interface XYZPerson ()
@property (readwrite) NSString *uniqueIdentifier;
@end
@implementation XYZPerson
...
@end
3. 验证类扩展的确定时机是编译时
我们新建一个LGPerson
类,并新建一个LGPerson+Extension
的类扩展。代码如下:
LGPerson 代码:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
#import "LGPerson.h"
#import "LGPerson+Extension.h"
@interface LGPerson ()
@property (nonatomic, copy) NSString *mName;
- (void)extM_method;
@end
@implementation LGPerson
+ (void)load{
NSLog(@"%s",__func__);
}
- (void)extM_method{
NSLog(@"%s",__func__);
}
- (void)extH_method{
NSLog(@"%s",__func__);
}
@end
LGPerson+Extension 代码:
#import "LGPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, copy) NSString *ext_subject;
- (void)extH_method;
@end
NS_ASSUME_NONNULL_END
main.m 代码:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "LGPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
LGPerson *p = [LGPerson alloc];
NSLog(@"%@ - %p", p, p);
}
return 0;
}
先导知识,类中由编译器确定的实例方法以及属性存储在类的bits
->data
->ro
中,我们只要验证在ro
中存在我们在上面代码中定义的方法和属性以及属性的setter
和getter
方法就可以说明类扩展的确定时机是类编译的时候。先导知识传送门:
iOS Objective-C 类原理
iOS Objective-C 方法的本质
通过上面这幅图,我们已经在ro
的baseProperties
中找到了我们类中已经类扩展中的所有属性,以及在baseMethodList
找到了我们类中和类扩展中的方法已经属性的getter
和setter
方法,至此我们已经验证了类扩展的确定时机是在编译期。
但是有一点值得注意,如果我们没有在类的头文件或者源文件中引入单独的类拓展头文件,那么这个单独的类拓展的头文件里面的属性和方法将不会被加载到类上面来。会认为你不用,优化掉了。
4. 类扩展和分类的区别
按照官方的文档的说法,类扩展与分类很相似,也可以说是匿名的分类但是它们之间也有很多的区别。iOS Objective-C 分类的加载
操作对象 | 是否有implementation | 加载时机 | 操作对象 | 能否通过@property声明属性生成 getter 和 setter |
---|---|---|---|---|
类扩展 | 无 | 编译时 | ro | 可以 |
分类 | 有 | 运行时/编译时 | ro/rw | 不可以,需要借助关联对象 |