本文主要探究类与对象的基础知识

方法

Objective-C 中有两种类型的方法:

@interface AClass : NSObject 
// 类方法
+ (void) classMethod;
// 实例方法
- (void) instanceMethod;
@end

类方法又称作静态方法,可以不用创建对象来调用;而实例方法必须通过实例对象调用;

Objective-C 中的方法只要在 @interface 中声明,都认为是公有的;它没有绝对私有的方法,只能将方法隐藏;

隐藏方法的方式:

  • 在 @implementation 中声明
  • Category
  • Extension

变量

苹果推荐使用 @property 来声明成员变量,作为类的属性;被声明的成员变量会在类的内部自动创建 gettersetter 方法,前者用于获取该属性,后者用于修改这个属性;

属性有两种修饰方式,一种是修饰 gettersetter 方法的原子性,另一种是设置读写属性;

原子性(默认值为 atomic)

  • atomic : 保证属性在读写操作的原子性,它修饰的属性在读写操作完成后一定还是一个完整的属性;也就是说,它能保证读写操作的线程安全,但不能保证整个类的线程安全;
  • nonatomic : 非原子性,因此在普遍情况下,用它修饰的属性在读写时会更快;

1.如果想要保证多线程下属性赋值的安全性,需要借助其他手段来实现;

2.Swift 中默认为 atomic,且无法修改;如果一个类在 OC 中定义,它的属性在 Swift 中运行时,会被修饰为 atomic

参考链接

https://www.jianshu.com/p/7288eacbb1a2 https://www.jianshu.com/p/66b77270e363 https://stackoverflow.com/questions/588866/whats-the-difference-between-the-atomic-and-nonatomic-attributes https://medium.com/@YogevSitton/atomic-vs-non-atomic-properties-crash-course-d11c23f4366c

读写(默认值为 readwrite assign)

  • readwrite : 可读写;
  • readonly : 只读,只会生成 getter
  • assign : 赋值;
  • retain : MRC 下的持有,setter 方法将传入参数先保留,再赋值,传入参数的 retain count 会+1; ARC 下也能用,但不建议;
  • strong : ARC 独有,强引用,除非引用它的对象被释放,否则被强引用的对象将不会被释放;
  • weak : ARC 独有,弱引用,被它修饰的属性遵循自身的释放流程,与引用它的对象释放与否无关;
  • copy : 拷贝,setter 方法将传入对象复制一份;

同时,我们还可以使用自己定义 accessor 的名字:

@property (getter=isFinished) BOOL finished;

这种情况下,编译器生成的 getter 方法名为 isFinished,而不是 finished。

@synthesize 和 @dynamic

  • 系统默认会将属性 synthesize,生成 setter 和 getter 方法;
  • 可读写(readwrite)属性实现了自己的 getter 和 setter;
  • 只读(readonly)属性实现了自己的 getter;
  • 使用 @dynamic,显式表示不希望编译器生成 getter 和 setter (例如 CoreData 中的对象);
  • Protocol 中定义的属性,编译器不会自动 synthesize,需要手动写;
  • 当重载父类中的属性时,也必须手动写 synthesize;

类的扩展

继承

创建子类,继承父类的属性、方法等;

Protocol

协议,类似 C++ 中的多重继承,可以让多个不同的类实现类似的接口;

Category

  • 可以不知晓某个类的源码、无需继承,来实现扩展其功能的效果;使用场景为将方法模块化,分别在不同的文件中实现;
  • 在使用 Category 时需要注意的一点是,如果有多个命名 Category 均实现了同一个方法(即出现了命名冲突),那么这些方法在运行时只有一个会被调用,具体哪个会被调用是不确定的。因此在给已有的类(特别是 Cocoa 类)添加 Category 时,推荐的函数命名方法是加上前缀
@interface NSString (ABCEmpty)

- (BOOL)abc_isEmpty;

@end

Extention

  • 可以认为是匿名的 Category;
  • 必须知晓类的源码;
  • Extension 声明的方法必须在类的主 @implementation 区间内实现,可以避免使用有名 Category 带来的多个不必要的 implementation 段;
  • Extension 可以在类中直接添加新的属性和实例变量,Category 需要使用其他方式才可以

给已有的类添加属性

Extension 可以给类添加属性,编译器会自动生成 getter,setter 和 ivar。 Category 并不支持这些。如果使用 Category 的话,类似下面这样:

@interface XYZPerson (UDID)
@property (readwrite) NSString *uniqueIdentifier;
@end

@implementation XYZPerson (UDID)
...
@end

尽管编译可以通过,但是当真正使用 uniqueIdentifier 时直接会导致程序崩溃。

如果我们手动去 synthesize 呢?像下面这样:

@implementation XYZPerson (UDID)
@synthesize uniqueIdentifier;
...
@end

然而这样做的话,代码直接报编译错误了:

@synthesize not allowed in a category's implementation

经过查阅网上资料,我们可以用 OC 的 Runtime 机制来实现这样的效果


@implementation XYZPerson (UDID)
@dynamic uniqueIdentifier;

- (void)setUniqueIdentifier:(NSString*)identifier {
     objc_setAssociatedObject(self, @selector(uniqueIdentifier), identifier, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString*)uniqueIdentifier {
    return objc_getAssociatedObject(self, @selector(uniqueIdentifier));
}
...
@end

类的导入

  • #include: 是C/C++导入头文件的关键字
  • #import: 是Objective-C导入头文件的关键字;头文件会自动只导入一次,不会重复导入;
  • @class: 告诉编译器需要知道某个类的声明,可以解决头文件的相互包含问题;使用时一般在 interface 中声明,需要在 .m 文件中引用该头文件;

类的初始化

在 OC 中绝大部分类都继承自 NSObject,它有两个非常特殊的类方法 loadinitilize,用于类的初始化

+load

load 是在被添加到 runtime 时开始执行,父类最先执行,然后是子类,最后是 Category。又因为是直接获取函数指针来执行,不会像 objc_msgSend 一样会有方法查找的过程。

+initilize

initialize 最终是通过 objc_msgSend 来执行的,objc_msgSend 会执行一系列方法查找,并且 Category 的方法会覆盖类中的方法。

  +load +initialize
调用时机 被添加到 runtime 时 收到第一条消息前,可能永远不调用
调用顺序 父类->子类->分类 父类->子类
调用次数 1次 多次
是否需要显式调用父类实现
是否沿用父类的实现
分类中的实现 类和分类都执行 覆盖类中的方法,只执行分类的实现

参考链接

https://www.jianshu.com/p/872447c6dc3f