01-iOS-OC常用语法-----简介与常规应用|Category、Extension、关联对象、Protocol

528 阅读13分钟

前言

最近在围绕iOS编程语言(OC&&Swift)的语法 探索iOS的底层原理实现,在即将探索到 OC 面向对象语法中的:CategoryExtension关联对象 之前,先简单对这三者的特点和使用场景有一个简单的回顾。

学过OOP (object-oriented programming)我们知道,面向对象的三大特性就是:封装、继承、多态,在iOS中也不例外有这三大特性。此外,iOS还提供了其它对OC类进行(功能/‘成员变量’)扩展的方式:

  • Category
  • Extension
  • Protocol
  • 关联对象

一、Category

Category比继承更为简洁 的方法来对Class进行扩展,无需创建子类就可以为现有的类动态添加方法。

  • 可以给项目内任何已经存在的类 添加 Category
  • 甚至可以是系统库/闭源库等只暴露了声明文件的类 添加 Category (看不到.m 文件的类)
  • 通过 Category 可以添加 实例方法类方法属性
    • 注意:通过 Category 可以添加属性,会声明setter、getter方法 ,但需要 开发者 自己 实现 setter、getter方法
    • 通过 Class 添加属性,会默认生成并实现 setter、getter方法
  • 通过 Category 可以 重新实现 在 Class中已存在的 方法
  • 通过 Category 可以 重新实现 在 其它 Category中已存在/已实现 的方法
  • 在iOS中,实例对象/类对象方法调用顺序严格依赖 源码文件的编译顺序,编译顺序的查看可以通过Xcode>Build Phases>Compile Sources查看:image.png
      1. 类与 各个分类 各自声明且实现各自的方法:没有方法的实现被‘覆盖’,分类 只是扩展了 类的 功能
      1. 类与 各个分类 存在 声明 且实现 了相同的 方法: 存在 方法的实现被‘覆盖’(实际上不是被覆盖,而是方法地址后挪,系统会找到同名方法在内存地址中位置较前的方法 实现 调用)
      • 分类 方法实现 的优先级 > 原来的类
      • 各个分类 中 被‘覆盖’的情况严格 依赖 源码 文件的编译顺序:
        • 先编译的 方法 会 先加入 方法列表「先入栈」
        • 后编译的 方法 会 后加入 方法列表「后入栈」
        • 系统在调用 方法 的实现的时候,通过 对象(实例对象、类对象等) 和 方法API 在底层发送消息,拿到方法 实现 的 实现 IMP指针 找到 方法的具体实现(实际上最终拿到的方法实现,是后编译的源码文件中的方法实现)

实践验证:
image.png

1.添加一个类

//
//  Car.h
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
// 
#import <Foundation/Foundation.h> 
NS_ASSUME_NONNULL_BEGIN  
@interface Car : NSObject{ 
@public

    int _year;

} 
-(void)run; 
@end  
NS_ASSUME_NONNULL_END


//
//  Car.m
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
// 
#import "Car.h" 
@implementation Car 
- (void)run{ 
    NSLog(@"%s",__func__); 
} 
@end

查看 run方法的调用情况: image.png 查看 是否符合 源码文件的编译顺序:
image.png 只有一个类的源码文件和main.m,符合预期

2.添加一个Category1

//
//  Car+Test.h
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//


#import "Car.h"
  
@interface Car (Test)
- (void)runFaster;
@end
 


//
//  Car+Test.m
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//

#import "Car+Test.h"

@implementation Car (Test)
- (void)run{
    NSLog(@"%s",__func__);
}
- (void)runFaster{
    NSLog(@"%s",__func__);
}
@end 

此时我们只是编写了一个分类Car+Test,但未引入到 源码调用中:
(该分类与原类有一个相同的方法) main.m文件中的源码情况与打印结果:
image.png 查看 是否符合 源码文件的编译顺序:
image.png 打印结果符合编译文件的先后顺序 我们调换一下分类和原类的编译顺序试试看: image.png 打印结果不符合编译文件的先后顺序: 分类 方法实现的优先级 > 原来的类 我们引入一个分类Car+Test探究一下:\

//
//  main.m
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//

#import <Foundation/Foundation.h>

#import "Car.h"
#import "Car+Test.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *car = [[Car alloc]init];
        [car run];
        [car runFaster];
    }
    return 0;
}

image.png 通过分类,原类增加了一个功能方法:- (void)runFaster;

3.添加一个Category2

//
//  Car+Test2.h
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//


#import "Car.h"

NS_ASSUME_NONNULL_BEGIN

@interface Car (Test2)

- (void)runFaster;
@end

NS_ASSUME_NONNULL_END

//
//  Car+Test2.m
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//

#import "Car+Test2.h"

@implementation Car (Test2) 
- (void)runFaster{
    NSLog(@"%s",__func__);
}
@end

Car+Test2.hCar+Test.h有一个相同的方法实现- (void)runFaster

此时尚未引入Car+Test2.h,只是完成了分类代码的编写!
image.png

image.png 程序的调用 受 源码文件的 编译顺序的影响:优先级:后编译源码实现 > 先编译源码实现

3.改变源码文件的编译顺序:

我们,改变两个分类的 源码文件的编译顺序试试看: image.png image.png

4.总结

相同方法(名称相同、类型相同、参数相同)实现的优先级:

  • Category 的实现 > 原来的类 的实现
  • 后编译的 Category 的实现 > 先编译的 Category 的实现
  • 后编译的 Category > 先编译的 Category >原来的类

二、Extension

延展(Extension)可以理解成是匿名的Category

  • 可以用来给类 添加 属性和方法 的声明,不作用在Subclass
  • 可以 通过 在.m文件 给其它类 或者 当前 类 添加 Extension
  • 也可以 通过 .h文件 给类 添加 Extension
  • 要对 Extension添加的 属性和方法进行实现

1. 编写一个.h文件声明的Extension

image.png

//
//  Car+DiyCar.h
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//


#import "Car.h"

NS_ASSUME_NONNULL_BEGIN

@interface Car ()
- (void)diyRun;
@end

NS_ASSUME_NONNULL_END

2. 编写一个Person类,并给其 添加 匿名扩展 且 实现 刚才 .h文件声明的Extension

//
//  Person.h
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//

#import <Foundation/Foundation.h>
 
#import "Car+DiyCar.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
//Desc:
@property (strong, nonatomic) Car *myCar;
- (void)tellMeWhoRU;
@end

NS_ASSUME_NONNULL_END


//
//  Person.m
//  分类、扩展
//
//  Created by VanZhang on 2022/5/11.
//

#import "Person.h"

@implementation Car (DiyCar)
- (void)diyRun{
    NSLog(@"%s",__func__);
}
- (void)run{
    NSLog(@"%s",__func__);
}

@end

@interface Person ()
//Desc:
@property (strong, nonatomic) NSString* name;
@end
@implementation Person
- (instancetype)init{
    if (self = [super init]) {
        _name = @"我是你大哥!!";
    }
    return  self;
}
- (Car *)myCar{
    if (!_myCar) {
        _myCar   =  [[Car alloc]init];
    }
    return _myCar;
}
- (void)tellMeWhoRU{
    NSLog(@"name:%@",self.name);
}
@end

image.png

3.总结

Extension的作用:

  • 可以用来给类 添加 属性和方法 的声明,不作用在Subclass
  • 可以 通过 在.m文件 给其它类 或者 当前 类 添加 Extension
    • 当前类添加 Extension 时,编译器会默认给 添加的属性 声明且实现 其setter&&getter方法
  • 也可以 通过 .h文件 给类 添加 Extension
  • 要对 Extension添加的 属性和方法进行实现
  • 若 对 Extension的实现中 ,重新实现 原来 类或其它分类中已有的方法,不会对原来的方法执行产生影响(因为没有自身的.m文件,不存在源码实现文件的编译的情况)

Extension的作用更多在于拆分结构复杂的类,比较清晰的暴露声明

三、关联对象

我们前面的篇幅中提到:通过 Category 添加属性,可以默认生成 setter、getter方法 ,但系统不会默认是实现这两个方法。
所以我们在开发过程中常常自己去实现 setter、getter 我们编写一个Car类和它的分类,添加一个属性实践一下:

//
//  Car.h
//  关联对象、Protocol
//
//  Created by VanZhang on 2022/5/11.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Car : NSObject{
@public
    int _year;
}

-(void)run;
@end

NS_ASSUME_NONNULL_END

//
//  Car.m
//  关联对象、Protocol
//
//  Created by VanZhang on 2022/5/11.
//

#import "Car.h"

@implementation Car
- (void)run{
    NSLog(@"%s",__func__);
}
@end 
//
//  Car+Category1.h
//  关联对象、Protocol
//
//  Created by VanZhang on 2022/5/11.
//


#import "Car.h"

NS_ASSUME_NONNULL_BEGIN

@interface Car (Category1)
//Desc:
@property (strong, nonatomic) NSString *nameplate;//车子的铭牌
@end

NS_ASSUME_NONNULL_END



//
//  Car+Category1.m
//  关联对象、Protocol
//
//  Created by VanZhang on 2022/5/11.
//

#import "Car+Category1.h"

@implementation Car (Category1)

@end

image.png 与原来的结论相符!!
接下来我们自己展开对 setter、getter的实现: image.png 如上图,我们不难得知,只需要把添加的属性,在setter方法中存起来,在getter方法中取出来返回即可

1.setter、getter的方法实现1-通过系统的对象缓存手段存取

  • NSUserDefault
  • 沙盒缓存
    • Dir
    • Temp
    • Library
    • Cache
  • Plist
  • CoreData
  • NSKeyedArchiver .... 诸上方法,可以解决程序开发的需要,但是对于属性的值的存取,基本是随着 类的销毁就销毁 的,没必要如此进行 数据缓存!!!若是这般做,是对硬盘资源的浪费!!

2.setter、getter的方法实现-通过第三方缓存工具手段存取

  • Sqlite(FMDB)
  • Realm
  • YYCache等 诸上方法,可以解决程序开发的需要,但是对于属性的值的存取,基本是随着 类的销毁就销毁 的,没必要如此进行 数据缓存!!!若是这般做,是对硬盘资源的浪费!!

3.setter、getter的方法实现-通过静态字典对象进行存取

因为一个分类可以给类添加若干个属性,所以可以通过属性名做键值对的key,对真实的值进行存取。但是添加静态字典,属于全局公用的!

  • 若这个类初始化了多个实例,那么将访问通过一个静态字典的内存,那么就存在数据存储隔离的缺点了
  • 并且 这样存储 还存在线程安全问题,需要进一步 编写 保障 存取数据 的 可靠性的代码
  • 最后,若添加了若干个属性,则诸上代码 需要重复 编写 若干遍,会导致代码文件肿大(若是整个项目都如此编写分类,那么会导致一定的性能降低)

4.setter、getter的方法实现4-通过系统runtimeAPI提供的关联对象进行存取

修改分类的实现代码如下:

//
//  Car+Category1.m
//  关联对象、Protocol
//
//  Created by VanZhang on 2022/5/11.
//

#import "Car+Category1.h"
#import <objc/runtime.h>
@implementation Car (Category1)
- (void)setNameplate:(NSString *)nameplate{
    objc_setAssociatedObject(self, @selector(nameplate), nameplate, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)nameplate{
    return objc_getAssociatedObject(self, _cmd);
}
@end 

程序的运行结果如下:

image.png 读写成功!!!

  • 值的存取都只通过一行代码,十分简介!!
  • 线程安全(通过底层分析得知,底层分析代码待下会分晓!)!!
  • 在程序结束的时候,随着实例的销毁,也清理了内存 image.png

5.总结

  • 通过Category可以给类添加属性,但需要开发者自己实现属性的 setter、getter方法(只需要把添加的属性,在setter方法中存起来,在getter方法中取出来返回即可)
  • 实现 setter、getter方法的最佳方案是 通过系统 的runtimeAPI 关联对象
    • 代码 简介 可靠
    • 线程安全
    • 需要引入头文件和API:
      #import <objc/runtime.h> 
      void  objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy) ;
      id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
      
    • objc_AssociationPolicy其实就是添加属性时候的 描述
      /* Associative References */
      
      /**
       * Policies related to associative references.
       * These are options to objc_setAssociatedObject()
       */
      typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
          OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
          OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                                  *   The association is not made atomically. */
          OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                                  *   The association is not made atomically. */
          OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                                  *   The association is made atomically. */
          OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                                  *   The association is made atomically. */
      };
      
      • OBJC_ASSOCIATION_ASSIGN = @property (assign)
      • OBJC_ASSOCIATION_RETAIN_NONATOMIC = @property (strong,nonatomic)
      • OBJC_ASSOCIATION_COPY_NONATOMIC = @property (copy,nonatomic)
      • OBJC_ASSOCIATION_RETAIN = @property (strong)
      • OBJC_ASSOCIATION_COPY = @property (copy)
    • void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy) ;
      • 参数介绍:
      • id: 为给谁关联对象,一般填self(当前实例对象)
      • const void * _Nonnull key:关联的key
      • id _Nullable value:关联的值
      • objc_AssociationPolicy policy:关联的策略
    • id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
      • 参数介绍:
      • id: 为给谁关联对象,一般填self(当前实例对象)
      • const void * _Nonnull key:关联的key
      • 返回值就是 前面一个API关联的值
  • 对于关联的key有很多种写法:
    • 通过属性名 字符串
    • 通过宏定义
    • 通过...
    • 最流行的写法是通过 getter方法的@selector对象
      • 能够保证其唯一性
      • 能够让 源码文件 不那么肿大(在添加若干属性的时候,需要编写大量的key相关的代码)
      • 标识度清晰(一看代码就知道是哪个key),统一编程习惯之后,直接不用看代码都知道内部的实现,进而不引入头文件的情况下可以不耦合获取存储的值

四、Protocol

1.Protocol的作用

  • 1、一个协议对应一个类:公开方法一般都放在.h文件中,如果想隐藏实现细节,可以把这些方法放到协议中,再让该类遵守此协议(有点类似与Extension)
  • 2、一个协议对应多个类:将一些公共方法抽取出来封装成协议,任何类想拥有这些方法,只需遵守此协议即可

问题:公共方法为什么不放在父类中?
答:下图中D类跟E类的公共方法可以放在父类B中,但D类跟G类的公共方法放在父类的父类A中显然是不合适的,这时协议就能很优雅的解决

2.基本使用

2.1 修饰符

  • @required:遵守此协议的类必须实现它修饰的方法(默认修饰符)
  • @optional:遵守此协议的类可以不实现它修饰的方法
@protocol PersonProtocol <NSObject>
@required
- (void)eat;
@optional
- (void)run;
@end

2.2 能声明属性,但必须在遵守此协议的类中调用@syntheszie才能正常使用

// PersonProtocol
@protocol PersonProtocol <NSObject>
@property (nonatomic, copy) NSString *name;
@end
 
// Person
@interface Person : NSObject <PersonProtocol>
@end
 
@implementation Person
@synthesize name; // 生成get/set方法的实现
@end
 
// 使用
Person *person = [Person new];
person.name = @"111";
NSLog(@"name---%@", person.name);
 
// 打印
name---111

2.3 不能声明成员变量

3. Protocol特点

  1. 协议只有方法的声明,没有方法的实现
  2. 遵守协议只能在类的声明@interface上,不能在类的实现@implementation
  3. 一个协议可以遵守多个其他协议
  4. 一个协议若遵守了其他协议,就拥有其他协议所有方法的声明
  5. 一个协议可以被多个类遵守,一个类可以遵守多个协议
  6. 一个类若遵守了某个协议,就必须实现协议中@required修饰的方法(不实现的话,编译器会发出警告,但依然能编译通过)
  7. 若父类遵守了某个协议,子类也就遵守了此协议

4. Protocol应用场景

4.1 不同的类使用统一入口传递数据(一个协议对应多个类)

// CustomViewProtocol
@protocol CustomViewProtocol <NSObject>
- (void)setData:(id)data;
@end
 
// CustomView
@interface CustomView : UIView <CustomViewProtocol>
@end
 
@implementation CustomView
- (void)setData:(id)data {
    NSLog(@"view---%@", data);
}
@end
 
// CustomTableView
@interface CustomTableView : UITableView <CustomViewProtocol>
@end
 
@implementation CustomTableView
- (void)setData:(id)data {
    NSLog(@"tableView---%@", data);
}
@end
 
// 使用
NSArray *views = @[[CustomView new], [CustomTableView new]];
NSArray *datas = @[@"111", @"222"];
[views enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {    if ([obj conformsToProtocol:@protocol(CustomViewProtocol)]) { // 是否遵守此协议
        [obj setData:datas[idx]];
    }
}];
 
// 打印
view---111
tableView---222

4.2.面向接口编程AOP:将接口(声明)和实现分离,对外只暴露接口(一个协议对应一个类)

  • 图解

面向接口编程

  • bridge:关联接口和实现
// .h文件
@interface ServerBridge : NSObject
+ (void)bindServer:(id)server andProtocol:(Protocol *)protocol;
+ (id)serverForProtocol:(Protocol *)protocol;
@end
 
// .m文件
@interface ServerBridge ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, id> *serverStore;
@end
 
@implementation ServerBridge
- (NSMutableDictionary<NSString *,id> *)serverStore {
    if (!_serverStore) {
        _serverStore = [NSMutableDictionary new];
    }
    return _serverStore;
}
+ (instancetype)shared {
    static id _bridge = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _bridge = [[self alloc] init];
    });
    return _bridge;
}
+ (void)bindServer:(id)server andProtocol:(Protocol *)protocol {
    if ([server conformsToProtocol:protocol]) {
        [[ServerBridge shared].serverStore setValue:server
                                             forKey:NSStringFromProtocol(protocol)];
    }
}
+ (id)serverForProtocol:(Protocol *)protocol {
    return [[ServerBridge shared].serverStore valueForKey:NSStringFromProtocol(protocol)];
}
@end
  • protocol:对外暴露的接口
@protocol ServerProtocol <NSObject>
@property (nonatomic, copy) NSString *provideData;
- (void)doSomething;
@end
  • server:接口的具体实现
@interface Server () <ServerProtocol>
@end
 
@implementation Server
@synthesize provideData;
+ (void)load {
    [ServerBridge bindServer:[self new]
                 andProtocol:@protocol(ServerProtocol)];
}
- (NSString *)provideData {
    return @"server provide data";
}
- (void)doSomething {
    NSLog(@"server do something");
}
@end
  • business:使用接口的业务
id<ServerProtocol> server = [ServerBridge serverForProtocol:@protocol(ServerProtocol)];
NSLog(@"%@", server.provideData);
[server doSomething];
 
// 打印
server provide data
server do something

3,控制链式编程的调用顺序

// .h文件
@class SQLTool;
@protocol Fromable;
@protocol Whereable;
 
typedef SQLTool<Fromable>*(^Select)(NSString *string);
typedef SQLTool<Whereable>*(^From)(NSString *string);
typedef SQLTool*(^Where)(NSString *string);
 
@protocol Selectable <NSObject>
@property (nonatomic, copy, readonly) Select select;
@end
 
@protocol Fromable <NSObject>
@property (nonatomic, copy, readonly) From from;
@end
 
@protocol Whereable <NSObject>
@property (nonatomic, copy, readonly) Where where;
@end
 
@interface SQLTool : NSObject
+ (NSString *)makeSQL:(void(^)(SQLTool<Selectable> *tool))block;
@end
 
// .m文件
@interface SQLTool() <Selectable, Fromable, Whereable>
@property (nonatomic, copy) NSString *sql;
@end
 
@implementation SQLTool
- (NSString *)sql {
    if (!_sql) {
        _sql = [NSString new];
    }
    return _sql;
}
+ (NSString *)makeSQL:(void(^)(SQLTool<Selectable> *tool))block {
    if (block) {
        SQLTool *tool = [SQLTool new];
        block(tool);
        return tool.sql;
    }
    return nil;
}
- (Select)select {
    return ^(NSString *string) {
        self.sql = [self.sql stringByAppendingString:string];
        return self;
    };
}
- (From)from {
    return ^(NSString *string) {
        self.sql = [self.sql stringByAppendingString:string];
        return self;
    };
}
- (Where)where {
    return ^(NSString *string) {
        self.sql = [self.sql stringByAppendingString:string];
        return self;
    };
}
@end
 
// 使用
NSString *sql = [SQLTool makeSQL:^(SQLTool<Selectable> *tool) {
    tool.select(@"111").from(@"222").where(@"333"); // 不能改变调用顺序
}];
NSLog(@"%@", sql);
 
// 打印
111222333

5. 系统协议

5.1 NSObject:根协议,其他协议都要遵守它,它提供了很多基本的方法,基类NSObject已经遵守此协议并实现了协议方法,所以我们可以直接使用这些方法

@protocol NSObject
- (id)performSelector:(SEL)aSelector;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)respondsToSelector:(SEL)aSelector;
...
@end

5.2 NSCopying:与对象拷贝相关的协议,如果想让某个自定义类具备拷贝功能,那么该类必须遵守此协议并实现协议方法

  • 声明
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
  • 使用
// Person
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@end
 
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
    Person *person = [[Person allocWithZone:zone] init];
    person.name = self.name;
    return person;
}
@end
 
// 使用
Person *person1 = [Person new];
person1.name = @"123";
Person *person2 = [person1 copy];
person1.name = @"456";
NSLog(@"%@", person1.name);
NSLog(@"%@", person2.name);
 
// 打印
456
123

5.3 NSMutableCopying:让自定义类具备可变拷贝功能

@protocol NSMutableCopying
- (id)mutableCopyWithZone:(NSZone *)zone;
@end

5.4 NSCoding:如果想用归档存储某个自定义类对象,那么该类必须遵守此协议并实现协议方法

  • 声明
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
@end
  • 使用
// Person
@interface Person : NSObject <NSCoding>
@property (nonatomic, copy) NSString *name;
@end
 
@implementation Person
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name forKey:@"name"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
    }
    return self;
}
@end
 
// 使用
- (void)viewDidLoad {
    [super viewDidLoad];
 
    [self save];
    [self read];
}
- (NSString *)filePath {
    NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    return [document stringByAppendingPathComponent:@"Person.data"];
}
- (void)save {
    Person *person = [Person new];
    person.name = @"111";
    [NSKeyedArchiver archiveRootObject:person toFile:self.filePath];
}
- (void)read {
    Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:self.filePath];
    NSLog(@"%@", person.name);
}
 
// 打印
111

5.5 NSSecureCoding:在NSCoding基础上增加了安全性

@protocol NSSecureCoding <NSCoding>
@property (class, readonly) BOOL supportsSecureCoding;
@end

总结

本文整个篇幅通过 理论 + 代码示例 验证的形式 介绍了iOS中除了 封装继承多态三大特性外,其它对OC类进行(功能/‘成员变量’)扩展的方式:

  • Category
  • Extension
  • Protocol
  • 关联对象等 为紧接着探索 Category、关联对象的底层原理 进行了一定的准备工作!!