Foundation框架是iOS开发人员工具箱中的基础。 它为iOS开发提供了NSObject
根类和大量基本构件,从数字和字符串的类到数组和字典。 刚开始时,Foundation框架可能看起来有些枯燥,但是它利用了很多功能,对于开发iOS应用程序是必不可少的。
关于核心基金会的一句话
在上一篇文章中 ,我简要提到了Core Foundation及其与Foundation的关系。 即使我们在本系列文章的其余部分中不会明确使用Core Foundation框架,熟悉框架并了解它与将广泛使用的面向对象的兄弟Foundation Foundation的区别也很有用。
虽然Foundation框架是在Objective-C中实现的,但Core Foundation框架却是基于C的。 尽管存在这种差异,Core Foundation框架确实实现了有限的对象模型。 该对象模型允许定义通常称为对象的不透明类型的集合,尽管严格地说,它们不是对象。
两个框架的主要目标是相似的,从而可以在各种库和框架之间共享数据和代码。 核心基金会还包括对国际化的支持。 此支持的关键组件通过CFString
不透明类型提供,该类型有效地管理Unicode字符数组。
如前所述,通过在功能参数中启用Cocoa对象替代Core Foundation对象,免费电话桥接从字面上弥合了这两个框架之间的差距,反之亦然。
重要的是要注意,自动引用计数(ARC)不管理Core Foundation“对象”。 这意味着您在使用Core Foundation时负责管理内存。 Mike Ash撰写了一篇很棒的文章,内容涉及自动引用计数以及如何将ARC与Core Foundation和免费电话桥接结合使用。
如果您想了解有关该框架的更多信息以及该框架的不透明类型的完整列表,请访问Apple的Core Foundation框架参考 。
实践,实践,实践
学习新技能最好通过实践来完成。 让我们开始在Xcode中创建一个新的Playground。 命名游乐场基金会 ,并将Platform设置为iOS 。 单击下一步继续。 告诉Xcode您想在哪里保存游乐场,然后单击“ 创建” 。
这就是游乐场内容的样子。
//: Playground - noun: a place where people can play
import UIKit
var str = "Hello, playground"
游乐场在顶部包含注释, UIKit框架的import语句和变量声明。 在右侧,您可以看到游乐场的输出。
由于我们将使用Foundation框架,因此需要用Foundation的import语句替换UIKit的import语句。 删除顶部的注释,更新import语句,然后删除变量声明。
import Foundation
基础
Foundation框架不仅仅是用于处理数字,字符串和集合(数组,字典和集合)的类的集合。 它还定义了许多协议,函数,数据类型和常量。
在本文的其余部分中,我将主要关注开发iOS应用程序时最常使用的类。 但是,我还将简要讨论由Foundation框架定义的三个关键协议: NSObject
, NSCoding
和NSCopying
。
通讯协定
多种语言(例如Perl,Python和C ++)为多重继承提供支持。 这意味着一个类可以源自一个以上的类(是该类的子类)。
尽管Swift和Objective-C不提供对多重继承的支持,但它们确实通过协议形式的规范来支持多重继承。 我们已经在本系列的前面部分介绍了协议,但是有一个重要的细节尚未讨论。
可选和必填方法
请记住,Swift协议的每种方法都是必需的。 如果符合Swift协议的类型未实现协议定义的所有属性和方法,则编译器将引发错误。 对于Objective-C协议而言并非如此。 在Objective-C中,协议的方法可以标记为可选。 如果协议方法是可选的,则实现该方法不需要符合协议的类型。
由于iOS SDK的绝大多数框架都是用Objective-C编写的,因此我们将满足许多定义可选方法的Objective-C协议。 要记住的重要一点是,Swift协议的每个属性和方法都是必需的。
好处
协议的好处是多方面的。 当类采用或符合协议时,则该类应实现协议声明的方法。 对于某些人来说,协议可能会让人联想到Java中的接口。 这意味着可以使用协议来声明与对象的接口,而无需透露对象的类型。
多重继承有其好处,但是它无疑具有缺点 。 协议的优点在于,不相关的类型,枚举,类和结构仍可以通过使用协议共享相似的行为。
NSObject
除了NSObject
根类,Foundation框架还定义了NSObject
协议。 但是,在Swift中,类和协议不能具有相同的名称。 结果, NSObject
协议在Swift中被称为NSObjectProtocol
。
可以询问符合NSObjectProtocol
协议的对象的类和超类,可以将其与其他对象进行比较,并响应self
。 这只是添加到符合NSObjectProtocol
协议的对象的行为的一小部分。
NSCoding
符合NSCoding
协议的NSCoding
可以进行编码和解码。 这对于需要归档或分发的对象是必需的。 例如,对象归档是在将对象或对象图存储到磁盘时进行的。
NSCopying
NSCopying
协议仅声明一个方法copyWithZone(_:)
。 如果一个类要支持复制对象,则它需要符合NSCopying
协议。 复制对象是通过向其发送copy()
或copyWithZone(_:)
消息来完成的。
班级
NSObject
NSObject
类是绝大多数Objective-C类层次结构的根类。 如果我们对Swift感兴趣,那为什么这么重要? 请记住,iOS SDK的大多数框架都是由Objective-C支持的。 即使您决定在Swift中开发应用程序,您仍将在后台使用Objective-C。
通过从NSObject
根类继承,对象知道如何充当Objective-C对象以及如何与Objective-C运行时进行交互。 NSObject
符合我们刚才讨论的NSObject/NSObjectProtocol
协议,这不足为奇。
让我们看看如果一个类继承自NSObject
类,我们将免费获得什么。 将以下代码段添加到您的游乐场。 如果您已阅读本系列的前几篇文章,则应该看起来很熟悉。 我们声明一个继承自NSObject
Book
类。 Book
类实现了两个实例方法, chapters()
和pages()
。
class Book: NSObject {
func chapters() {
}
func pages() {
}
}
因为Book
继承自NSObject
并且NSObject
符合NSObjectProtocol
协议,所以我们可以向Book
类询问其行为和继承树。 看下面的例子。
我们首先通过在Book
类上调用classForCoder()
来classForCoder()
Book
类。 classForCoder()
方法是一个类方法,这意味着我们可以在Book
类本身上调用此方法。 接下来,我们向Book
类询问其超类或父类,它是直接继承自该类的类。 因为并非每个类都有一个父类,所以它返回一个可选的。 毫不奇怪, Book
的超类是NSObject
。
下一行告诉我们Book
符合NSObjectProtocol
协议,这并不意外,因为它从NSObject
类继承了此特征。 我们还了解到Book
不符合NSCoding
协议。
看来Book
类没有响应chapters()
方法。 这并不奇怪,因为chapters()
是实例方法,而不是类方法。 我们的示例的最后两行证明了这一点,其中我们实例化了一个Book
实例,并向该实例询问相同的问题。
在上面的示例中,我们使用了respondsToSelector(_:)
方法,但是您可能想知道选择器是什么? “响应选择器”是什么意思? 选择器是函数或方法的名称的奇特词。 Objective-C运行时使用选择器将消息发送到对象。 长话短说,如果您阅读单词选择器,请考虑功能或方法。 更详细的信息超出了本教程的范围。
NSNumber
NSNumber
类是管理任何基本数字数据类型的实用程序类。 它是NSValue
类的子类,它为标量类型,指针,结构和对象ID提供了面向对象的包装器。 NSNumber
类定义用于检索其存储的值,比较值以及返回存储值的字符串表示形式的方法。
请记住,从NSNumber
实例检索的值必须与存储在其中的值一致。 NSNumber
类将尝试将存储的值动态转换为请求的类型,但是不用说, NSNumber
可以管理的数据类型具有固有的局限性。
让我用一个例子来说明。 将以下代码段添加到您的游乐场。
我们通过将Double
值传递给init(double:)
来创建一个新的NSNumber
实例。 接下来,我们使用三种不同的NSNumber
方法请求存储的值。 如您所见,结果并不总是反映存储在myNumber
的值。
该课程很简单,使用NSNumber
时要保持一致,方法是跟踪存储在NSNumber
实例中的类型。
NSString
该实例NSString
类管理的阵列unichar
形成的文本的字符串的字符。 与普通的C字符串,它管理的细微但重要的区别char
的字符,是一个unichar
一个字符是一个多字节字符。 这类似于Swift的String
类型。
顾名思义, unichar
字符非常适合处理Unicode字符。 由于此实现, NSString
类为国际化提供了现成的支持。
我想强调一下,由NSString
实例管理的字符串是不可变的。 这意味着一旦创建了字符串,就无法对其进行修改。 来自其他语言(例如PHP,Ruby或JavaScript)的开发人员可能会对这种行为感到困惑。
Foundation框架还定义了NSString
的可变子类NSMutableString
,可以在初始化后对其进行修改。
有多种创建字符串对象的方法。 创建字符串对象的最简单方法是在NSString
类上调用string()
方法。 这将返回一个空的字符串对象。 看一下NSString
的类引用 ,了解完整的初始化程序列表。
创建字符串对象的另一个常见路径是通过字符串文字。 在下一个示例中,将字符串文字分配给stringA
。 在编译时,编译器将用NSString
的实例替换字符串文字。
注意,我们不依赖Swift的类型推断来确定stringA
的类型。 第二个示例stringB
,说明了原因。 如果我们没有明确指定stringA
的类型为NSString
,Swift就会认为我们要创建一个String
实例。 该示例还说明, NSString
继承自NSObject
而String
不继承。 请记住,Swift中的String
类型是一个结构,并且结构不支持继承。
NSString
类具有用于创建和处理字符串的各种各样的实例和类方法,并且您几乎永远不会觉得需要NSString
类。
让我们通过将以下代码段添加到您的游乐场中来探索NSString
及其可变子类NSMutableString
。
let stringA: NSString = "This is "
let stringB: NSString = NSString(string: "a mutable string.")
var mutableString = NSMutableString(string: stringA)
mutableString.appendString(stringB as String)
我们首先创建两个NSString
实例,一个使用文字语法,一个使用init(string:)
初始化程序。 然后,通过将第一个字符串作为参数传递来创建可变字符串。 为了说明可变字符串可以在创建后进行修改,将stringB
附加到可变字符串上并打印其值。
NSArray
和NSSet
NSArray
类管理对象的不可变的有序列表。 Foundation框架还定义了NSArray
的可变子类NSMutableArray
。 NSArray
类的行为非常类似于C或Swift数组,不同之处在于NSArray
实例管理对象。 NSArray
类声明了许多有助于使用数组的方法,例如用于查找和排序数组中对象的方法。
重要的是要了解NSArray
, NSSet
和NSDictionary
实例只能存储对象。 这意味着不可能在这些集合类(或其子类)的任何一个中存储标量类型,指针或结构,如果这样做,编译器将抛出错误。 解决方案是将标量类型,指针和结构包装在NSValue
或NSNumber
实例中,就像我们在本文前面看到的那样。
将以下代码片段添加到您的游乐场中,以探索NSArray
及其可变的副本NSMutableArray
。
let myArray = NSArray(objects: "Bread", "Butter", "Milk", "Eggs")
print(myArray.count)
print(myArray.objectAtIndex(2))
var myMutableArray = NSMutableArray(object: NSNumber(int: 265))
myMutableArray.addObject(NSNumber(int: 45))
我们使用init(objects:)
初始化程序创建一个数组。 此方法接受可变数量的参数。 接下来,我们打印数组中的对象数,并在索引2
处向数组询问对象。
因为NSMutableArray
所继承NSArray
,它的行为在很多方法一样NSArray
。 主要区别在于可以在初始化后从数组中添加和删除对象。
在继续之前,我想谈谈NSSet
。 此类类似于NSArray
,但主要区别在于集合管理的对象的集合是无序的,并且集合不能包含重复项。
NSSet
的优点在于,如果您只需要知道对象是否包含在集合中,则查询其对象的速度会更快。 Foundation框架还定义了NSOrderedSet
。 该类的实例具有NSSet
的优点,但也可以跟踪每个对象的位置。
NSDictionary
像数组一样,字典是大多数编程语言中的常见概念。 例如,在Ruby中,它们称为哈希。 基本概念很简单,字典管理键值对或条目的静态集合。
就像在Ruby哈希中一样,条目的键本身不必是字符串对象。 只要键在字典中是唯一的,它就可以是符合NSCopying
协议的任何类型的对象。 但是,在大多数情况下,建议使用字符串对象作为键。
像数组一样,字典不能存储空值。 如果要表示一个空值,则可以使用NSNull
。 NSNull
类定义一个单例对象,该对象用于符号化数组,字典和集合中的空值。
单例模式是许多编程语言中的重要模式。 它将类的实例化限制为一个对象。 开发iOS应用程序时,您将经常处理单例对象。
与NSArray
一样,Foundation框架定义了NSDictionary
的可变子类NSMutableDictionary
。 有多种实例化字典的方法。 看一下下面的代码片段。
let keyA: NSString = "myKey"
let keyB: NSString = "myKey"
let myDictionary = NSDictionary(object: "This is a string literal", forKey: keyA)
print(myDictionary.objectForKey(keyB))
我们声明两个包含相同字符串的单独的NSString
对象。 然后,我们通过调用init(object:forKey:)
实例化字典。 接下来,我们向字典查询与keyB
内容相关联的对象,并打印其值。
注意细节很重要。 即使我们使用keyA
作为键值对的键,并使用keyB
作为键来获取键值对的值或对象,字典也为我们提供了正确的对象(作为可选对象)。 NSDictionary
类非常聪明,足以知道我们想要与字符串"myKey"
关联的对象。 这是什么意思? 即使对象keyA
和keyB
是不同的对象,它们包含的字符串也是相同的,这正是NSDictionary
类用来引用该对象的字符串。
在结束本教程之前,我想向您展示一个嵌套字典和数组的示例以及一个NSMutableDictionary
的示例。 下面的代码片段显示了一个字典可以包含另一个字典(或数组),并且还显示了如何使用可变字典。
let myMutableDictionary = NSMutableDictionary()
myMutableDictionary.setObject(myDictionary, forKey: "myDictionary")
结论
即使我们在本文中介绍了很多基础知识,也几乎没有涉及Foundation框架必须提供的内容。 不过,不必知道在Foundation框架中定义的每个类或函数的详细信息即可开始iOS开发。 探索iOS SDK时,您将了解有关Foundation框架的更多信息。
在下一篇文章中,我们将探讨UIKit框架,并且还将讨论iOS应用程序的来龙去脉。
如果您有任何问题或意见,可以将其留在下面的评论中,或通过Twitter与我联系。