import语句
和 C 一样,Objective-C 也使用头文件来包含结构体、符号常量和函数原型等元素的声明。C 中使用 #include
语句,在 Objective-C 中虽然有也可以使用 #include
,但是我们不会这么做,而是使用 #import
语句。#import
是 Xcode 编译器提供的,可保证头文件只被包含一次,无论此命令在该文件总出现了多少次。
在 C 中,通常使用基于
#ifdef
命令的方案来避免一个文件包含另一个文件而后者又包含前者的情况。
而在 Objective-C 中,使用#import
命令来实现这个功能。
布尔(Boolean)类型
布尔类型指的是可以存储真值和假值的变量类型。C 的布尔数据类型 bool
具有 true
和 faluse
两个值,Objective-C 的 BOOL
具有 YES
和 NO
两个值。
Objective-C 中的 BOOL 实际上一种对带符号的字符类型(signed char)的类型定义(typedef),它使用 8 位的存储空间。通过
#define
把YES
定义为 1,NO
定义为 0,。Objective-C 并不会将BOOL
作为仅能保存YES
或NO
值的真正布尔类型来处理。编译器仍将BOOL
认作 8 位二进制数。这样会引发一个小问题:如果不小心将一个大于 1 字节的整数值赋给了一个BOOL
变量,那么只有低位字节会用作BOOL
值。
面向对象编程
面向对象编程(Object-Oriented Programming)的首字母缩写为 OOP,这是以一种编程技术,是为了编写模拟程序而开发的。OOP 是一种编程架构,可构建由多个对象组成的软件。
OOP 的一个关键概念 -> 间接:在代码中通过指针间接获取某个值,而不是直接获取。间接是 OOP 的核心,OOP 使用间接来调用某个函数,而不是直接调用。
- 类(class):是一种表示对象类型的结构体。对象通过它的类来获取自身的各种信息,尤其是执行每个操作需要运行的代码。
- 对象(object):是一种包含值和指向其类的隐藏指针的结构体。
- 实例(instance):是对象的另一种称呼。
- 消息(message):是对象可以执行的操作,用于通知对象去做什么。
- 方法(method):是为了响应消息而运行的代码。
- 方法调度(method dispatcher):是 Objective-C 的一种机制,用于推测执行什么方法以响应某个特定的消息。
- 接口(interface):是类为对象提供的特性描述。
- 实现(implementation):是使接口能正常工作的代码。
继承
OOP 中的继承表明一个类从另一个类,它的父类或超类(superclass)中获取某个特性,是在两个类中建立关系的一种方式。
继承的类之间建立的关系为 "is a"(是一个)。
某些编程语言(例如 C++)具有多继承性,也就是一个类可以直接从两个或者多个类继承而来,但是 Objective-C 不支持多继承。
- 超类 & 父类:是继承的类。
- 子类 & 孩子类:是执行继承的类。
当代码发送消息时,Objective-C 的方法调度机制将在当前的类中搜索相应的方法,如果无法在接收消息的对象的类文件中找到相应的方法,它就会在该对象的父类中进行查找。
- self:是一个指向接收消息的对象的指针。
- super:既不是参数也不是实例变量,而是 Objective-C 编译器提供的一种功能,当你向
super
发送消息时,实际上是在向该类的父类发送消息,如果父类中没有定义该消息,那么就会继续在继承链上一级中查找。
self = [super init]
,[super init]
为了让父类将所有的初始化工作一次性完成,init
返回的值就是被初始化的对象,这么做是为了防止父类在初始化过程中返回的对象和一开始创建的不一致。
复合
复合(composition)是指将多个类组合在一起,配合使用。
Objective-C 中复合是通过包含作为实例变量的对象指针来实现的。
复合的类之间的建立的关系为 "has a"(有一个)。
内存管理
- 内存泄漏(leak memory):只分配而不释放内存。
- 对象生命周期:(通过
alloc
或者new
方法实现),(接收消息并执行操作),(通过复合以及向方法传递参数),(被释放掉)。 - 引用技术(reference counting)& 保留计数(retain counting):每个对象都有一个和之相关联的整数,被称作它的引用计数器或保留计数器。当使用
alloc
、new
方法或者通过copy
创建一个对象时,对象的保留计数器值被设置为 1。增加对象的保留计数器的值,可以发送一条retain
消息,反之,发送一条release
消息。 - 自动释放池(autorelease pool):用来存放对象的池子(集合),并且能够自动释放。当给一个对象发送
autorelease
消息时,实际上是将该对象添加到了自动释放池中。当自动释放池被销毁时,会向该池中的所有对象发送release
消息。
可以使用
NSMutableArray
编写自己的自动释放池,以容纳对象并在dealloc
方法中向池中的所有对象发送release
消息。
自动释放池被清理的时间是完全确定的:要么是在代码中你手动销毁,要么是使用
AppKit
时在事件循环(runloop)结束时销毁。你不必担心程序会随机地销毁自动释放池,也不必保留使用的每一个对象,因为在调用函数的过程中自动释放池不会被销毁。
- 内存管理规则:
当你使用
new
、alloc
或copy
方法创建一个对象时,该对象的自动引用计数器的值为 1,当不再使用该对象,应该像该对象发送一条release
或autorelease
消息。当你通过其他方法获得一个对象时,假设该对象的自动计数器的值为1,而且已经被设置为自动释放,那么你就不需要执行任何操作来确保该对象得到清理。如果打算拥有该对象,就需要保留它并确保在操作完成时释放它。
如果保留了某个对象,就需要释放或自动释放该对象。必须保持
retain
方法和release
方法使用次数相等。
- 垃圾回收:Objective-C 2.0 引用了自动内存管理机制,也称垃圾回收(仅支持 OS X 应用开发)。
启用垃圾回收以后,平常的内存管理命令全都变成了空操作指令。Objective-C 的垃圾回收器会定期检查变量和对象并且跟踪他们他们之间的指针,当发现没有任何变量指向某个指针时,就将该对象视为应该丢弃的垃圾。和自动释放池一样,垃圾回收也是在事件循环结束触发的。
- 自动引用计数(automatic reference counting,ARC)
ARC 会追踪你的对象并决定哪一个仍会使用而哪一个不会再用到,ARC 不是垃圾回收器,它是在编译时进行工作的,会在代码中插入合适的retain
和release
语句。
ARC 只对可保留的对象指针(ROPs)有效可保留的对象指针主要有以下三种:
(1)代码块指针
(2)Objective-C 对象指针
(3)通过 __ attribute __((NSObject)) 类型定义的指针
对象初始化
- 分配对象:分配(allocation)就是从操作系统获得一块内存,并将其指定为存放对象的实例变量的位置。向某个类发送
alloc
消息,就能为类分配一块足够大的内存,以存放该类的全部实例变量。同时alloc
方法还顺便将这块内存区域全部初始化为 0。 - 初始化对象:与分配对应的操作是初始化,刚刚分配的对象并不能立即使用,你需要初始化后才能使用。初始化从操作系统取得一块内存用于存储对象,执行初始化操作的方法一般都会返回正在初始化的对象。
if (self = [super init])
这句代码意味着self
可能会发生改变,该声明中最先运行的代码是[super init]
,其作用是让父类完成其自身的初始化工作。对于继承了NSObject
的类来说,调用父类的init
方法可以让NSObject
执行它所需的所有操作,以便对象能够响应消息并处理保留计数器。而对于从其他类继承的类,通过这种方法可以实现自身的全新初始化。
self
参数是通过固定的距离寻找实例变量所在的内存位置的。如果从init
方法返回一个新的对象,则需要更新self
,以便其后的实例变量的引用可以被映射到正确的内存位置。self = [super init]
这个赋值操作只影响该init
方法中的self
的值,而不影响该方法范围以外的任何内容。
类别(category)
类别是一种为现有的类添加新方法的方法。
你可以在类别中添加属性,但是不能添加实例变量,而且属性必须是 @dynamic
类型的。
当类别中的方法与现有的方法重名时,类别具有更高的优先级。
类扩展(class extension)
- 不需要名字
- 可以在包含你的源代码的类中使用它
- 可以添加实例变量
- 可以将只读权限修改成可读写的权限
- 创建数量不限
协议
协议是包含了方法和属性的有名称列表。采用(adopt)协议的方法是在类 @interface
声明中列出协议的名称。采用协议后,你的类就需要遵守协议,实现协议的方法。
代码块
代码块对象是对 C 语言中函数的扩展。除了函数中的代码,代码块还包含变量绑定。代码块也被称为闭包(closure)。
代码块包含两种类型的绑定:自动型和托管型。
- 自动绑定(automatic binding)使用的是栈中的内存。
- 拖管绑定(managed binging)是通过堆创建的。
void (^my_block)(void);
代码块具有以下特征:
- 返回类型可以手动声明有也可以由编译器推导。
- 具有指定类型的参数列表。
- 拥有名称。
本地变量会被代码块作为常量获取到,如果需要修改它们的值,必须将它们声明为 __block
可修改的。
有些变量是无法声明为 __block
类型的:
- 没有长度可变的数组
- 没有包含可变长度数组的结构体
文件加载与保存
- 属性列表
属性列表(property list),简写为 plist,这些列表用来放置一些 Cocoa 能够处理的对象。这些属性列表类是NSArray
、NSDictionary
、NSString
、NSNumber
、NSDate
和NSData
,以及它们的可修改形态(Mutable
开头的)。 - 写入和读取属性列表
集合型属性列表类(NSArray
和NSDictionary
)具有一个-writeToFile:atomically
方法,具有将属性列表的内容写入文件。NSString
和NSData
也具有writeToFile:atomically
方法,不过只能写出字符串或数据块。
atomically
:参数的值为BOOL
类型,它会告诉Cocoa
是否应该将文件保存在临时文件中,再将该临时文件和原始文件交换。这是一种安全机制:如果在保存过程中出现意外,不会破坏原始文件。但是这种安全机制在保存过程中,由于原始文件仍然保存在磁盘中,所以需要使用双倍的磁盘空间。
修改对象类型
NSPropertyListSerialization
可以为存储和加载属性列表的行为添加需要的需要的设定项。
+ (nullable NSData *)dataWithPropertyList:(id)plist format (NSPropertyListFormat)format options:(NSPropertyListWriteOptions)opt error:(out NSError **)error
方法可以把 plist
数据返回给你,并且能在出现异常的时候提供错误信息。
NSError *error = nil;
/*
typedef NS_ENUM(NSUInteger, NSPropertyListFormat) {
NSPropertyListOpenStepFormat = kCFPropertyListOpenStepFormat, //明文
NSPropertyListXMLFormat_v1_0 = kCFPropertyListXMLFormat_v1_0, //XML
NSPropertyListBinaryFormat_v1_0 = kCFPropertyListBinaryFormat_v1_0 //二进制
};
typedef NS_OPTIONS(NSUInteger, NSPropertyListMutabilityOptions) {
NSPropertyListImmutable = kCFPropertyListImmutable, //整个数据容器不变
NSPropertyListMutableContainers = kCFPropertyListMutableContainers, //整个数据容器可变
NSPropertyListMutableContainersAndLeaves = kCFPropertyListMutableContainersAndLeaves //叶子节点和容器都可变
};
*/
NSData *encodeArray = [NSPropertyListSerialization dataWithPropertyList:@"xxx.plist" format:NSPropertyListXMLFormat_v1_0 options:NSPropertyListImmutable error:&error];
[encodeArray writeToFile:@"" atomically:YES];