首页 > 代码库 > Objective-C 2.0 基础要点归纳

Objective-C 2.0 基础要点归纳

本文的阅读基本条件:

  • 具备C/C++基础知识,了解面向对象特征
  • 阅读过《Objective-C 2.0 程序设计(第二版)》、《Objective-C 程序设计 第6版》或相关基础OC书籍

知识要点汇总模式: 提出问题。给出详细解释(Q&A)
PS:会以扩展方式延伸介绍一些知识点

问题列表
Q1. Objective-C特点以及和C++的差别?
Q2. 属性的特点。和实例变量的差别,使用注意事项?
Q3. 类的继承。协议和分类的概念和使用,以及须要注意的问题?

Q1. Objective-C特点以及和C++的差别?

Objective-C特点

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++ 和 OC的差别

C++ 也是一种对C实现面向对象特性的扩展,能够从面向对象的三个特性(封装、继承/派生、多态)角度简单分析C++和OC的不同

封装: OC和C++都有自己风格的类结构语法定义方式。OC成员变量默觉得protected。属性是OC的一个特性,用于封装对象中的数据。C++默觉得private。 OC能够通过分类对原始类进行扩充,在不破坏类封装特性的基础上对类进行方法加入,但不能添加成员变量(属性能够)。

继承: C++能够多继承。即同一时候继承多个父类。 OC仅仅能单继承,但通过协议有效添加了继承的灵活性和多样性,弥补了单继承的缺点。

多态: 即同样类具有同名函数(方法)。但能够通过类别来区分实现相应的函数(方法)。C++和OC不同的是其对象採用函数调用方式,且在编译阶段实现类和函数的绑定(虚函数的动态绑定机制除外),这点和OC具有明显的差别,OC是在执行时通过runtime系统来查找详细类别方法并实现。

并且,C++能够实现函数重载功能,OC没有该功能。

Q2. 属性的特点,和实例变量的差别,使用要点?

属性的特点

属性(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 * 类型。用于保护其封装性。

property使用要点

  1. 在implementation中假设不用@synthesize, 则使用属性时能够通过 _name(编译器隐藏了 @synthesize name = _name;) 或者self.name , [self name]訪问,点运算符和调用默认getter方法效果一样。
  2. 但假设自己定义了getter或者setter方法,则在方法实现中不能够使用 self ,须要用_name对实例变量进行訪问。否则 会进入死循环, 以下的Demo代码中有体现
  3. 假设使用@synthesize,则能够直接使用变量 name

浅复制(浅拷贝)和深复制(深拷贝)

概念解释:
浅复制: 对于对象中的每一层(对象成员中包括的对象)复制都是指针复制(引用计数角度,每层对象引用计数+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~`

Q3. 类的继承,协议和分类的概念和使用。以及须要注意的问题?

知识点汇总

(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 >

PS:

  1. 不是必需在接口部分声明协议的方法。但要在实现部分定义这些方法
    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的全部方法

    • [代理] delegation
      协议是一种两个类之间的接口定义,定义了协议的类能够看作是将协议定义的方法代理给实现他们的类。

      (定义了协议的类称为 代理)

測试程序

//******測试样例: (涵盖之前一些内容,算是整合一下思路)
//******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!

參考资源

  • 《Effective Objective-C2.0》
  • 《Objective-C 2.0 程序设计(第二版)》/《Objective-C 程序设计 第6版》
  • Objective-C消息传递机制
  • 深入理解Objective-C的Runtime机制
  • Objective-C的对象模型与执行时
  • Objective-C 消息、Category和Protocol
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

Objective-C 2.0 基础要点归纳