#import @class 的区别

关键词:#import、#class、class-continuation 分类、点语法(直接访问还是通过属性访问)、协议等

文章是参考书籍与博客的总结,自己写下来也算是自我总结,加深印象~

一、前言

在 OOP 编程中有两个技术用于描述类或对象与对象之间的关系:一个是继承;另一个是复合。在 Object-C 中 ,当一个类需要引用另一个类,即建立复合关系时,需要在类的头文件 (h) 中,通过 #import 修饰符来建立被引用的指针。

通常情况下我们都使用 "#import" 修饰符引用类,但从代码质量与安全角度来看,使用 "#import" 建立复合关系时,也暴露了所引用类的实体变量与方法。很多时候我们并不需要知道关于这个类的更多信息,那么只需要了解它是通过指针引用即可,同时也减少了依赖关系,也减少了重新编译所产生的影响。

二者在编译效率上也存在很大差异。对于已经产生的引用,若被引用的头文件有变化时,那么引用它的类都需要重新编译,这将耗费大量的时间,而使用 @class 则不会。

对于初学者,使用 #import 还容易犯 "类循环依赖" 错误。即两个类互相引用对方,使用 @class 互相引用虽然不会出现编译错误,但还是尽量避免这种 "类循环依赖" ,因为容易造成高耦合多依赖,不便于维护。

二、何时使用 #import 何时使用 @class

既然 #import 有很多不足之处,但是很多情况下不得不用 #import,如在一个头文件 (.h) 中包含多个类的声明定义时,要与该头文件声明的多个类建立复合关系,即所引用的类所处的文件有多个类或者多个其他的定义,使用 #import 比较好。如下:

#import 

//B 类
@interface BClass : NSObject

@end

//C 类
@interface CClass : NSObject

@end

//D 类
@interface DClass : NSObject

@end

一般来说,使用 @class 只是为了在头文件中引用这个类,把之当做类型来用。同时,在实现类 .m 中 ,如果需要引用这个类的实体变量或方法等,还需要通过 #import 把在 @class 中声明的类引用进来。下面举例说明一下:

#import 

@interface ClassA : NSObject

@property (nonatomic, copy) NSString *title;

@end
@import UIKit.UIViewController;

@class ClassA;
@interface ViewController : UIViewController

@property (nonatomic, strong) ClassA *aClass;

@end
#import "ViewController.h"
#import "ClassA.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSLog(@"%@", self.aClass.title);
    
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (ClassA *)aClass {
    if (!_aClass) {
        _aClass = [[ClassA alloc] init];
    }
    return _aClass;
}

@end
四、class-continuation 分类

综上,在设计类的时候,要分清何时使用 #import 何时使用 @class,是否有必要引入头文件等。若因为要实现属性、实例变量或者要遵循协议而必须引入头文件,则应尽量将其移至“class-continuation 分类”中,这样不仅可以缩减编译时间,而且还能降低彼此依赖程度。下面就讲讲 “class-continuation 分类”。

类中经常会包含一些无需对外公布的方法以及实例变量(当然也可以对外公布,并写明其为私有)。然后,我们最好还是只把确实需要对外公布的部分公开。那么不公开的怎么写呢,其实就是用“class-continuation 分类”。

“class-continuation 分类” 和普通分类不同,它必须定义在其类的实现文件中,与其他分类不同,“class-continuation 分类”没有名字。比如有个类叫 KPerson,其“class-continuation 分类” 写法如下:

@interface KPerson()

@end

看到这里,可能突然明白,哦,原来这就是“class-continuation 分类”~
我们平时也一直这么用...

@interface ViewController ()

@end

在分类其中可以定义方法和实例变量,放在里面的相当于隐藏起来,仅供本类使用。
当然的确可以写在头文件中,哪怕是将其标注为 private,也还是会泄露实现细节。别人知道有个视图宽度的属性了。或者你可以将这个值改为强类型 id,但是 id 类型不够友好,在实现类中无法获得编译器的帮助,即辅助检查功能,且自己也难以理解。

#import 

@interface ClassA : NSObject{
@private
    NSInteger viewWidth;
}
@end

编写 C++ 代码时“class-continuation 分类” 尤为有用。由于游戏大多用 C++ 来写,若把 C++ 文件的引用放在头文件中(实现文件必须是 .mm 扩展名表示编译器应该将此文件按 Object-C++ 编译)。后果就是所有引入了这个引入了 C++ 文件的类,都要改为 .mm,这可能导致整个应用程序都改为 .mm 了(互相引用是有可能的)。使用“class-continuation 分类”即将 C++ 的引入放到了其实现文件中,头文件没有 C++ 代码了,使用这个头文件的人甚至不知道其内部混有 C++ 代码,对外暴露的是一套纯 Object-C 接口。

“class-continuation 分类”还有一种合理用法,就是将 public 接口中标为只读的属性扩展成可读写,以便在内部设置其值。通过触发键值观测通知,其他对象有可能正监听此事件。只需要像下面的几行代码就行。实现类既可以随意调用 setFirstName: 或 setLastName: 这两个设置方法,也可以用点语法来设置属性,这样做既可以让外界无法修改对象,又能在其内部按照需要管理其数据。

.h 文件
#import 

@interface ClassA : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;

@property (nonatomic, copy, readonly) NSString *lastName;

@end
.m文件
#import "ClassA.h"

@interface ClassA()

@property (nonatomic, copy, readwrite) NSString *firstName;

@property (nonatomic, copy, readwrite) NSString *lastName;

@end

@implementation ClassA

@end
五、关于降低类与类的耦合度

在我看来,提高效率倒是其次,毕竟硬件发展这么快,这种优化提升不大(话说回来,精益求精还是需要的)。其实不管采取哪种方式都可以,首要目的还是为了降低耦合度,降低文件代码之间的粘合性,依赖关系过于复杂会失去代码重用性,给维护增加难度。

如果建立的复合关系过于复杂时(无论使用 #import 或 @class),这时可以考虑 2 种方式来解决:

1.模块方式(这个我也不是很明白,有待了解)
2.协议:后续文章讲这个

你可能感兴趣的:(#import @class 的区别)