一.类的组成
Objective-C是一门动态的高级编程语言,它的代码基本组织形式分为接口文件.h和实现文件.m。
接口文件(.h)
@interface Controller : NSObject
@property (nonatomic, copy) NSString *name;
+ (void)thisIsAStaticMethod; // 静态方法
– (void)thisIsAnInstanceMethod; // 实例方法
@end
实现文件(.m)
@implementation Controller()
+ (void)thisIsAStaticMethod{
}
– (void)thisIsAnInstanceMethod{
}
@end
在OC中存在与实现文件中的代码外部是无法看到的,只有存在接口文件的代码外部才能看到。实际上OC仅仅是对其进行了代码隐藏并没有在Java,C++中那种绝对的私有及保护成员的方法。
类(Class)就是一组方法列表和属性的集合抽象。
类的方法
在Objective-C中有两种类方法:实例方法和类方法。
+类方法:是对类本身执行某些操作的方法,通过类名直接访问,类方法可以独立于对象执行。
-实例方法:通过类实例化对象以后才能访问。
结合对RunTime实现的理解,就可以知道在底层实现它们的区别在与类方法只有在对象被实例化(分配内存)后才能调用,而类方法则是编译完后的一组方法列表集合可以不用实例化就调用。
类的属性
在OC中推荐使用@property来声明一个成员变量,它本质是一种语法糖,它会自动帮这个成员变量生成getter和setter方法。
@property
的属性按功能可以分为四大类:
1.属性名称,getter=xxx,setter=yyy。
2.内存管理,assin/retain/copy/weak/strong/unsafe_unretained
3.读写权限,readwrite,readonly
4.原子性,atomic/nonatomic。
assign
表明 setter 仅仅是一个简单的赋值操作,通常用于基本的数值类型,例如CGFloat
和NSInteger
。strong
表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行retain
,旧值进行release
,然后进行赋值操作。weak
表明属性定义了一个非拥有者关系。当给属性设定一个新值的时候,这个值不会进行retain
,旧值也不会release
, 而是进行类似assign
的操作。不过当属性指向的对象被销毁时,该属性会被置为nil。unsafe_unretained
的语义和assign
类似,不过是用于对象类型的,表示一个非拥有(unretained)的,同时也不会在对象被销毁时置为nil的(unsafe)关系。copy
类似于strong
,不过在赋值时进行copy
操作而不是retain
操作。通常在需要保留某个不可变对象(NSString最常见),并且防止它被意外改变时使用。意味着这是一份拷贝不能进行操作。readwrite
是可读可写特性,需要生成 getter 方法和 setter 方法。readonly
是只读特性,只会生成 getter 方法 不会生成 setter 方法,不希望属性在类外改变时使用。retain
表示持有特性,setter 方法将传入参数先保留,再赋值,传入参数的 retain count 会+1。nonatomic
和atomic
,决定编译器生成的 setter getter是否是原子操作。 atomic 表示使用原子操作,可以在一定程度上保证线程安全。一般推荐使用 nonatomic ,因为 nonatomic 编译出的代码更快。
默认的@property
是 readwrite,assign,atomic。
@synthesize
和@dynamic
使用@property
的过程实际上是编译器隐式使用@synthesize
会生成getter/setter方法并将ivar(成员变量)进行绑定。
假如显式调用@synthesize,则必须自定义getter/setter。
@synthesize propertyName = _propertyName //一般情况下编译器会自动生成
然而在一些情况下编译器是不会自动使用@synthesize
(现在Xcode会自动补充这部分代码):
- 可读写(readwrite)属性实现了自己的 getter 和 setter。
- 只读(readonly)属性实现了自己的 getter。
- 如果手动使用
@dynamic
,则显式表示不希望编译器生成 getter 和 setter。 - protocol 中定义的属性,编译器不会自动
@synthesize
,需要手动写。 - 当重载父类中的属性时,也必须手动写
@synthesize
。
二.类的导入
在iOS中有三种导入类的三个关键词#include
#import
@class
,其区别如下:
#include
是C语言的头文件包含方法。#import
是Obejective-C的文件方法,头文件会自动只导入一次,不会重复导入,相当于#include
+#pragma onc
@class
是类的预声明,它可以解决头文件的相互包含问题。
@class B
@interface A : NSObject
@property (nonatomic, strong) B objectB;
@end
@interface B : NSObject
@end
三.类的初始化
Objective-C 是建立在 Runtime 基础上的语言,OC 中类是初始化也是动态的。在 OC 中类都继自NSObject
,它有两个非常特殊的类方法load
和initilize
用于类的初始化。
+load
在main函数之前调用,目的是将类/分类加入到Objective-C的Runtime中。重写这个方法可以实现让我们在类加载之前就执行一些类相关的行为。load方法是不会自动继承的且相互独立的,在父类/子类/分类中分别重写load的话,都会被调用。
+initilize
在main中目的是初始化一个类的相关信息,它会发生在收到类的第一条消息之前,包括实例方法和类方法的调用。也就是说 +initialize 方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize 方法是永远不会被调用的。
它们的区别:
+load | +initialize | |
---|---|---|
调用时机 | 被添加到 runtime 时 | 收到第一条消息前,可能永远不调用 |
调用次数 | 1次 | 多次 |
是否需要显式调用父类实现 | 否 | 否 |
是否沿用父类的实现 | 否 | 是 |
分类中的实现 | 父类,子类和分类都执行 | 从父类->子类->分类覆盖调用 |
用途 | 用于method swizzle(方法混淆) | 初始化全局变量和静态变量 |
四.类的扩展——Protocal,Category以及Extension
@protocal
:协议,用于实现Objective-C中的多重继承特征,它一般配合委托(delegate)设计模式来使用。
@protocol
类似 Java 中的 interface,本质就是一个方法列表。- 这个方法列表中的方法可以使用
@required
,@optional
标注,以表示该方法是否是客户类必须要实现的方法。 - 一个 protocol 可以继承其他的 protocol 。
- 假如在协议中使用
@property
则表示使用该协议的类想要实现这个@property
的setter/getter方法。
@protocal AProtocal <NSObject> //必须继承自NSObject的协议才是一个正确的协议。
@required
- (void)didGetSomething:(NSString *)name;
@optional
@property (nonatic, strong) NSString *name;
@end
@interface A : NSObject
@property (nonatomic, weak) id<AProtocal> delegate; //协议配合委托方法来使用
@end
@intertface B : NSObject <AProtocal> //表示B这个类会实现A协议所定下的方法列表
@property (nonatomic, strong) NSString *name; //使用@property的话需要再次声明一次。还要进行手动@synthesize:
@end
category
:分类,是一种简化版的协议。
- 它可以不用知道类的源码就给这个类扩展方法,也无需继承。
- 它用于扩展一个方法列表,默认情况下是无法使用拓展的
@property
的,需要配合runtime技术才能够使用一个拓展属性。 - 它提供一种简单的方式来实现类相关方法的模块化,即将不同的类方法分配到不同的文件中去。
"c+hello.h"
#import "c.h"
@interface C(Hello) //表示为C类添加一个名为hello的分类方法列表
- (void)hello();
@end
"c+hello.m"
@implementation C(Hello)
- (void)hello(){
}
@end
extension
:扩展,是一种强化版的匿名分类。与category
的区别在于:
- 它必须要知道类的源码才能进行对该类的扩展。
- 它应该在实现文件中使用。
- 它可以为类添加新的属性和实例变量,
category
不行。
extension
的经典用法是可以对外声明一个只读成员变量但对内是可读写的。
@interface TestObject : NSObject
@property (nonatomic, readnoly) NSString *name;
@end
@interface TestObject() //使用了匿名分类Extension
@property (nonatomic, readwrite) NSString *name;//改变了name的读写属性
@end
@implementation TestObject
@end