知识要点汇总模式: 提出问题,给出详解(Q&A)
PS:会以扩展方式延伸介绍一些知识点
问题列表
Q1. Objective-C特点以及和C++的区别?
Q2. 属性的特点,和实例变量的区别,使用注意事项?
Q3. 类的继承,协议和分类的概念和使用,以及需要注意的问题?
Objective-C(OC)
是C的超集,基于C语言加入了面向对象特性和消息转发机制的动态语言(继承了smalltalk语言面向对象的经典思想)。除编译器外还需要用运行时(runtime)系统来动态创建类和对象进行消息发送和转发
,而runtime系统是一个用C语言编写动态链接库(libobjc.A.dylib), 该库提供了OC语言所需的各种动态特性的支持。
OC采用消息传递(Messaging)即向对象传递消息的方式而非方法调用。如: [receiver message],编译器并不马上执行对象的message方法,而是向receiver对象发送一条message消息,编译器将其转化为 obj_msgSend (runtime和对象模型详细实现参见Foundation框架要点归纳)及其相关函数调用,在程序运行时会根据obj_msgSend 查找具体方法实现。
简单的说,即OC程序在编译时并未具体确定方法实现,而是在运行时借助runtime系统实现方法的动态绑定,通过obj_msgSend实现查找方法的类别,进而调用方法实现,通过runtime系统实现面向对象的多态特性(不同的类有相同的方法名)。
C++ 也是一种对C实现面向对象特性的扩展,可以从面向对象的三个特性(封装、继承/派生、多态)角度简单分析C++和OC的不同
封装: OC和C++都有自己风格的类结构语法定义方式。OC成员变量默认为protected,属性是OC的一个特性,用于封装对象中的数据。C++默认为private。 OC可以通过分类对原始类进行扩充,在不破坏类封装特性的基础上对类进行方法添加,但不能增加成员变量(属性可以)。
继承: C++可以多继承,即同时继承多个父类。 OC只能单继承,但通过协议有效增加了继承的灵活性和多样性,弥补了单继承的缺点。
多态: 即相同类具有同名函数(方法),但可以通过类别来区分实现对应的函数(方法)。C++和OC不同的是其对象采用函数调用方式,且在编译阶段实现类和函数的绑定(虚函数的动态绑定机制除外),这点和OC具有明显的区别,OC是在运行时通过runtime系统来查找具体类别方法并实现。
而且,C++可以实现函数重载功能,OC没有该功能。
属性(property)
是OC一个特性,用于封装对象中的数据。OC通过定义实例变量来存储对象所需的数据,并通过存取方法(access method,setter,getter)来访问。上述概念的成型且经由“属性”这一特性而成为OC 2.0 一部分。
使用“属性”取代定义传统的实例变量和存取方法让编译器自动合成存取方法有利于提高程序性能(可以通过@dynamic关键字告诉编译器不要实现属性所用的实例变量和存取方法)。而引入了点语法(dot syntax)更提高程序可读性(”.”语法实际上编译成消息传递模型[receiver message])。
property有一些具有特殊用途的关键字(特质),一般分为三类:原子性,存取器控制,内存管理
(1)原子性
atomic(默认):只允许一个线程访问实例变量,线程安全效率低下
nonatomic: 可以被多线程访问,效率高
(2) 读写权限
readwrite(默认):当属性通过@synthesize实现时,编译器自动生成setter和getter方法
readonly :只有属性由@synthesize实现时,编译器才会合成获取方法,只有 getter没有setter
(3)内存管理
显示内存管理策略
assign(默认):用于值类型,如 int,float,NSInteger 等表示单纯的复制
retain: 在setter方法中,需要对传入对象进行引用计数+1的操作,即对对象具有所有权,该对象不会被释放
strong:和retain意思相同,并产生相同代码,但语意上更能体现对象拥有
weak:setter方法中对传入对象不进行引用计数+1的操作,即对传入的对象没有所有权,当对象引用计数为0时,对象被释放,声明实例变量指向nil
unsafe_unretained: 和assign相同,但是它适用于“对象类型”,非拥有关系,当目标对象被清除时,该属性之不被清空(nil, 和weak有区别), 访问会造成崩溃。
copy: 和strong类似,但区别在于对对象副本拥有所有权而非对象本身,常用于NS String * 类型,用于保护其封装性。
概念解释:
浅复制: 对于对象中的每一层(对象成员中包含的对象)复制都是指针复制(引用计数角度,每层对象引用计数+1)
深复制:至少有一个对象复制是对象内容复制从引用计数角度出发,除了原对象,其他指针复制的对象引用计数都+1)
方法关联:
retain:始终采取浅复制,引用计数+1
copy: 对于不可变对象,copy 采用的是浅复制,引用计数+1(编译器进行的优化)
对于可变对象copy采用的是深复制,引用计数器不变
mutableCopy: 可变和不可变对象都采用深复制
属性修饰词关联:
retain , strong 都是对象引用计数+1
copy 是拷贝对象副本,引用计数+1
对于常量类型 assign
NSString类 copy
id对象 strong
关联对象 weak
非系统内存管理 unsafed_retained
// ------AClass.h------
#import <Foundation/Foundation.h>
@interface AClass : NSObject
// property
@property (nonatomic, strong) NSString *aName;
// print
- (void)print;
// self-defined getter & setter
// 主要和 自定义getter方法作比较,通过获取的限制条件
- (NSString *)aName;
@end
// ------AClass.m------
@implementation AClass
//@synthesize aName = _aName; // 系统隐藏
//@synthesize aName;
// print
- (void)print
{
// 调用实例变量
NSLog(@"内部Print方法直接调用实例变量%@", _aName);
// 调用自定义getter函数
NSLog(@"内部Print方法通过存取方法获取属性%@", self.aName);
NSLog(@"内部Print方法通过存取方法获取属性%@", [self aName]);
}
// getter
- (NSString *)aName
{
if ([_aName isEqualToString:@"King"]) { // self.aName 会进入死循环
return _aName;
}
else {
return @"Not King~";
}
}
@end
// ------main.m------
int main(int argc, char const *argv[])
{
@autoreleasepool {
AClass *a = [[AClass alloc] init];
a.aName = @"King";
NSLog(@"通过点运算符实现属性访问: %@", a.aName);
// 调用存取方法的setter
[a setAName:@"King2"];
NSLog(@"通过存取方法实现属性访问: %@", [a aName]);
NSLog(@"----------------------------------------");
[a print];
return 0;
}
}
OutPut:
通过点运算符实现属性访问: King
通过存取方法实现属性访问: Not King~ ----------------------------------------
内部Print方法直接调用实例变量King2
内部Print方法通过存取方法获取属性Not King~
内部Print方法通过存取方法获取属性Not King~`
(Maybe you need it or learn something important)
[继承] OC是面向对象语言,继承方式和C++有明显区别即单继承,而没有C++的多继承。但多协议弥补了不能多继承的缺陷。可以通过子类继承的方式
a.增加新的方法/或实例变量,
b.类的特别接口封装,
c. 覆写 一个或者多个方法改变类的默认行为
[多态,动态类型和动态绑定]
多态: 不同的类对象定义相同名称
动态类型id类型: 直到运行时才确定所属对象的类,通用对象类型,定义为:
typedef struct objc_object *id;
(id 变量不能使用点运算符)
动态绑定:运行时才确定实际要调用的对象方法
[分类 - Category]
提供一种简单方式将类定义模块化到相关方法的组或分类中。一般,如果主类为B,则分类头文件和实现文件名称分别为:B+subClass.h, B+subClass.m
从调用角度,分类就是对原始类的一种扩展
从程序角度,分类可读性更强
分类文件格式
#import “SuperClassName.h”
@interface Fraction (MathOp)
@end
类扩展:
如果创建一种未命名的分类,则称为类的扩展,在有命名的分类中时不允许的。和有命名分类不同,未命名分类主要在主实现区实现,而非分离实现区域。未命名分类扩展的方法和属性或者实例变量只能由该类本身私有。
Category 使用场景:
a. 已经定义的类需要添加新的方法功能
b. 一个类中包括许多种不同类型方法,需要不同团队实现,有利于任务分配
注意问题:
a. Category可以访问原始类方法,但不能添加变量,添加变量可以通过创建子类(继承)方式来实现
b. Category可以重载原始方法,会导致不能访问原来的方法,创建子类实现 重载 覆写
c. 和普通接口有所区别,分类实现文件中可以不必实现所有声明的方法
一系列不属于任何类的方法列表,其中声明的方法可以被任何类实现。这种模式称为代理模式。在不同场景中实现不同模式。Apple采用了大量的代理模式来实现 MVC中 View和 Controller 的解耦。
最常用的是委托代理模式, Cocoa框架中大量采用这种模式实现 数据和UI的分离: UIView产生的所有事件 都是通过委托的方式 交给 Controller 完成
框架中后缀为Delegate都是 Protocol
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
(可以放在单独的.h头文件中定义,也可以放在相关类的h文件中)
使用:
@interface AddressBook : NSObject< NSCopying, NSCoding >
没必要在接口部分声明协议的方法,但要在实现部分定义这些方法
2. 可以通过 如下代码来检查一个对象是否遵循某项协议
id currentObject;
if ([currentObject conformsToProtocol:@protocol(Drawing)] == YES) {
}
3. 也可以使用 respondsToSelector:@selector() 来检查是否实现了可选的方法
代理(delegation)
-(BOOL)respondsToSelector:selector 被广泛用于实现委托方法定义
4. id currentObject; 借助编译器来检查变量一致性
5. 和类名一样,协议名必须唯一
6. 协议是可以继承的,具有继承的属性,如果协议B继承了协议A,则实现协议B 需要实现A和B的所有方法
//******测试例子: (涵盖之前一些内容,算是整合一下思路)
//******MTProtocol.h 文件,为定义的协议
@protocol Printing
// 默认
@required
- (void)printProtocol;
@optional
- (void)printProtocolOptional;
@end
//******A.h 文件,包含A类定义,采用Printing协议
#import <Foundation/Foundation.h>
#import "MTProtocol.h"
// 接口处定义实例变量, 采用协议
@interface A : NSObject <Printing>
{
int x;
}
@property (nonatomic, assign) int y;
- (void)initXY;
- (void)printXY;
@end
//******A.m 文件,包含A类实现,注意实例变量和属性初始化的问题(这里没有重载init方法,所以不需要模版 self = [super init] 。。。)
#import "A.h"
@implementation A
- (void)initXY
{
x = 1;
self.y = 2;
}
- (void)printXY
{
NSLog(@"This is Class A : %i, %i", x, self.y);
}
- (void)printProtocol
{
NSLog(@"I am printProtocol Method form Printing!");
}
- (void)printProtocolOptional
{
NSLog(@"I am printProtocolOptional Method form Printing!");
}
@end
//******A+Op.h A类的分类接口,用于扩展(感觉就是扩展,一方面通过子类,一方面在原类中进行分类扩展,或者 合成类 这个比较奇葩)
#import "A.h"
@interface A (Op)
- (void)printCategory;
@end
//******A+Op.m A类的分类实现
#import "A+Op.h"
@implementation A (Op)
- (void)printCategory
{
NSLog(@"Op Category for Class A!");
}
@end
//******B.h B类接口,A 类子类,在B类中我们尝试 未命名分类,即私有方法扩展
#import "A.h"
@interface B : A
- (void)initXY;
- (void)printXY;
@end
//******B.m B类实现,注意未命名分类
#import "B.h"
@interface B ()
- (void)printNanNameCategory;
@end
@implementation B
- (void)initXY
{
x = 10; // A类中接口处定义了实例变量,可以被继承
super.y = 20; // 属性或者实现部分声明的变量为私有实例变量,通过合成取值方法获取
}
- (void)printXY
{
NSLog(@"This is Class B : %i, %i", x, self.y);
}
- (void)printNanNameCategory
{
NSLog(@"I am Nan Name Category for B!");
}
@end
//******main.m 实现,注意未命名分类
#import "A+Op.h"
#import "B.h"
// ---------- main ----------
int main(int argc, const char * argv[]) {
@autoreleasepool {
A *a = [[A alloc] init];
B *b = [[B alloc] init];
[a initXY];
[b initXY];
// 定义动态类型 id
id tmp = a;
[tmp printXY];
[tmp printCategory];
[tmp printProtocol];
[tmp printProtocolOptional];
tmp = b;
[tmp printXY];
[tmp printProtocolOptional];
// 测试 tmp 是否是A类别
//[tmp isKindOfClass:[A class]] ? NSLog(@"Yes") : NSLog(@"No");
// 测试A 类是否包含printXY方法
//[A instancesRespondToSelector:@selector(printXY)] ? NSLog(@"Yes") : NSLog(@"No");
// 测试对象a 是否包含init方法
//[a respondsToSelector:@selector(init)] ? NSLog(@"Yes") : NSLog(@"No");
}
}
结果分析
This is Class A : 1, 2
Op Category for Class A!
I am printProtocol Method form Printing!
I am printProtocolOptional Method form Printing!
This is Class B : 10, 20
I am printProtocolOptional Method form Printing!