Swift(iOS9 Programming Fundamentals With swift)
第四章 对象类型
第三章介绍了一些内建对象类型,不过还没有谈及对象类型本身(即 枚举 结构体 和 类);
本章结构:
1.介绍一下对象类型;
2.详细介绍对象类型的3种风格:枚举、结构体与类;
3.介绍Swift中用于增强对象类型灵活性的3种方式:协议、泛型与扩展;
4.介绍3中保护类型;
5.介绍3种集合类型;
结束对Swift内建类型的介绍;
4.10 扩展
扩展是将自己的代码注入到其他地方声明的对象类型中的一种方式;
你所扩展的是一个已有的对象类型,他可以是自定义的也可以是Swift或Cocoa的对象类型;
扩展声明只能位于文件的顶部;
要想声明扩展,请使用关键字extension,后跟已有的对象类型名,然后可以添加冒号,后跟该类型需要使用的协议列表名;最后是花括号,里面通常是对象类型声明的内容,具体限制如下:
1)扩展不能重写已有的成员(不过可以重载已有的方法);
2)扩展不能声明存储属性(不过可以声明计算属性);
3)类的扩展不能声明指定初始化器和析构器(不过可以声明便捷初始化器);
4.10.1 扩展对象类型
示例1:纸牌游戏的洗牌,纸牌存储在数组中,我们通过扩展内建的Array类型,添加一个shuffle方法:(code)
extension Array {
mutating func shuffle(){
for i in (0..<self.count).reversed() {
let ix1 = i
let ix2 = Int(arc4random_uniform(UInt32(i+1)))
(self[ix1],self[ix2]) = (self[ix2],self[ix1])
}
}
}
示例2:CGRect中心点(CGPoint)的便捷方法;(code)
extension CGRect {
var center:CGPoint {//只读计算属性
return CGPoint.init(x: self.midX, y: self.midY)
}
}
·扩展可以声明静态或类方法;
由于对象类型通常是全局可见的,所以这是给全局函数指定恰当命名空间的绝佳方式;
示例3:经常使用的一个颜色(UIColor),比将生成它的代码封装到全局函数中更好的方法是,使之成为UIColor的一个类方法;(code)
extension UIColor {
class func myGoldenColor() -> UIColor {
return self.init(colorLiteralRed: 1.000, green: 0.894, blue: 0.541, alpha: 0.900)
}
}
·扩展的另一个用途是让内建的Cocoa类能够处理你的私有数据类型:可以通过在类的扩展中重载方法,使之能够处理我们希望的数据类型;
·对自定义对象类型的扩展有助于组织代码;
经常应用的一个约定是为了对象类型需要使用每个协议添加扩展;(这种比较常用)
·在扩展Swift结构体时,我们可以声明一个初始化器,同时又保留隐式初始化器
struct Digit {
var number:Int
}
extension Digit {
init() {
self.init(number: 5)
}
}
let di = Digit()//没有扩展到话就会报错
print(di.number)//5
有了扩展之后,你可以通过调用显示声明的初始化器Digit(),或是调用隐式初始化器Digit(number:5)来实例化一个Digit;
因此,通过扩展显式声明的初始化器并不会导致隐式初始化器的丢失;(如果是在原来的结构体中声明了相同的初始化器,那么就会导致默认的隐式初始化器丢失的情况);
4.10.2 扩展协议
在协议扩展时,你可以向其中添加方法与属性,就行扩展对象类型那样;
与协议声明不同,这些方法与属性并不仅仅要被添加协议使用者实现,它们还是要被协议使用者所继承的实际方法与属性;
怎么理解呢,我们看个例子:
protocol FlierEx {
}
extension FlierEx{
func fly() {
print("Flap flap flap!")
}
}
struct BirdEx : FlierEx {
}
let bird1 = BirdEx()
bird1.fly()//Flap flap flap!
print(type(of: bird1))
我们注意到BirdEx可以使用FlierEx而无须实现fly方法;
即便fly方法作为一种要求添加到了FlierEx协议的声明中,BirdEx仍然可以使用FilerEx而无须实现fly方法;
这是因为,FlierEx协议扩展支持了fly方法,fly方法同时会被BirdEx继承下来;
使用者如果实现从协议扩展继承下来的方法的话,因此也可以重写这个方法;
不过你要知道,这种继承不是多态:
使用者的实现并非重写;他只不过是另一个实现而已;
内在一致性原则并不适用;重要的是引用类型到底是什么?
虽然f本质上是一个BirdEx,但fly消息却是发送给类型为Flier的对象引用(如果不重新实现的话),因此调用的是fly方法的FlierEx实现而非BirdEx实现;
要想实现多态继承,我们需要在原始协议中将fly声明为必须实现的方法:这样BirdEx才能实现其内在一致性;
这种差异有其现实意义,因此协议使用者并不会引入(也不能引入)动态分派的开销;
因此,编译器要做出静态的决定:如果方法在原始协议中声明为必须要实现的方法,那么我们就可以确保使用者会实现它,因此可以调用(也只能这么调用)使用者的实现;但如果方法只存在于协议扩展中,那么决定使用者是否重新实现了它就需要运行期的动态分布,这违背了协议的本质,因此 编译器会将消息发送给协议的扩展;
协议扩展的主要好处在于可以将代码移到合适的范围中;比如多个枚举都需要相同的对外方法,那么就可以将方法统一到他们都遵循的一个协议的扩展中;
在Swift标准库中,协议扩展使得很多全局函数都可以转换为方法;因为很多方法都是用的泛型约束到指定协议上,现在就可以通过协议扩展提供默认的实现;
Swift2.0之后,有大量的全局函数都变成了方法,这种转变改变了语言的风格;
4.10.3 扩展泛型
扩展泛型类型时(请注意,扩展的是泛型类型),占位符类型名对于扩展声明来说是可见的;
当然,这也会导致代码变得令人困惑,因为你看起来在使用未定义的类型,添加注释是个好主意:
class DogEx<T> {
var name:T?
}
extension DogEx {
func sayYourname() -> T? {
return self.name
}
}
let dogex:DogEx<String> = DogEx()
dogex.name = "haha"
print(dogex.sayYourname() ?? "NIL")//haha
泛型类型扩展可以使用一个where子句:这与泛型约束的效果是一样的,他会限制泛型的哪个解析者可以调用该扩展所注入的代码,并向编译器保证代码对于这些解析者来说是合法的;
还记得之前介绍泛型类型约束时举的一个求多个可比较参数中最小值的例子吗?
他像这样:
func myMin<T:Comparable>(things:T...) -> T {
var minemum = things[0]
for ix in 0..<things.count {
if things[ix] < minemum {
minemum = things[ix]
}
}
return minemum
}
现在我们可以这样写:(基于占位符可见 以及 where子句可用)
extension Array where Element:Comparable {
func myMin() -> Element {
var minium = self[0]
for ix in 1..<self.count {
if self[ix] < minium{
minium = self[ix]
}
}
return minium
}
}
let array = [2,4,1,5,6,3,6]
print(array.myMin())//1
该方法只能在Comparable元素的数组上调用;他不会注入到其他类型的数组中;
Swift语言的这种变化导致了Swift标准库发生了大规模的重组,可以将全局函数移到结构体扩展与协议扩展中作为方法;
4.11 保护类型
Swift提供了几个内建的保护类型,他们可以通过一个声明表示多个实际类型;
4.11.1 AnyObject
实际开发中,最常用的保护对象就是AnyObject,他实际上是一个协议,没有属性也没有方法,是空白的;
有个特别的特性:所有的类 类型都会自动遵循它;
因此,在需要AnyObject 的地方,我们可以赋值或传递任何类实例,并且可以进行双向的类型转换;
某些非类类型的Swift类型(如String和基本数字类型)都可以桥接到OC的类型上,他们是由Foundation框架定义的类类型;
这意味着:在Foundation框架下,Swift桥接类型可以赋值、传递或转换为AnyObject,即便是非类类型也可以;这是因为背后,它会先被自动转换为相应的OC桥接类类型;
AnyObject也可以向下类型装换为Swift桥接类型;
let s_any :String = "flower"
let any_s :AnyObject = s_any as AnyObject
let s_any1 = any_s as! String
print(s_any + (any_s as! String) + s_any1)
let i_any = 1
let any_i :AnyObject = i_any as AnyObject
let i_any1 = any_i as! Int
print(i_any,any_i,i_any1)
我们常常会在与OC交换过程中遇到AnyObject;
Swift可以将任何类类型转换为AnyObject;以及将AnyObject转换为类类型;(相当于Swift版本的id)
注意,将Optional展开并将其从AnyObject向下类型转换是我们的职责;当然在使用as!对AnyObject进行向下类型转换时,要将其转换为正确的类型,否则将代码运行时程序会崩溃;如果不确定,可以使用is与as?运算符来确保装换是安全的;
1.压制类型检查
AnyObject一个令人惊叹的特性就是他可以将编译器对某条消息是否可以发送给某个对象的判断推迟;(OC中id类型会导致编译器对什么消息可以发送给他的判断)
向AnyObject发送的消息需要满足如下条件之一:
1)它是OC类的成员;
2)他要是你自己定义的OC类的Swift子类(或扩展)的成员;
3)他要是Swift类的成员,并且标记为@objc(或dynamic);
类似于之前介绍的可选协议成员,但是有些区别;
注:这种延迟判断已经不支持了,Swift要求明确的对象类型才能进行消息的发送;
class DogAny{
@objc var noise:String = "woof"
@objc func bark() -> String{
return "woof"
}
}
class CatAny{
}
let c_any:AnyObject = CatAny()
let d_any:AnyObject = DogAny()
print(c_any,type(of: c_any))
//print(c_any.noise)//编译器报错
//print(d_any.noise)//编译器报错
2.对象恒等性与类型恒等性
对于应用类型(值类型不会出现这种情况),如果需要知道对象本身是否是你所认为的那个特定的对象;Swift的解决方案是恒等运算符(===);
该运算符可用于使用AnyObject协议的任何对象类型实例;他会比较对象引用,判断两个对象引用是否指向了相同的对象;(注意不是在比较值)
和==一样,===运算符可与Optional无缝连接(因为它也是一种比较运算符);
4.11.2 AnyClass
AnyClass是AnyObject对应的类,它对应于OC的Class类型;在Cocoa API的声明中如果需要一个类,就会用到AnyClass;
实际应用中,想要在自己的实现中返回一个实际的类,请向类名发送self消息;
class TestAnyClass {
class var whatDogSays:String {
return "woof"
}
class func testAnyClass() -> AnyClass {
return DogAny.self
}
}
print(TestAnyClass.testAnyClass())//DogAny #1
let c_anyClass:AnyClass = TestAnyClass.self //TestAnyClass.self 是引用
print((c_anyClass as! TestAnyClass.Type).whatDogSays) //TestAnyClass.Type 是类型
对类的引用可以通过函数type(of:)获得,也可以通过向类型名发送self获得;
类的引用类型使用了AnyClass,可以通过===运算符比较这种类型的引用;
class SubTestAnyClass:TestAnyClass{
}
let ddd = TestAnyClass()
let ddds = SubTestAnyClass()
func typeTester(t:TestAnyClass , whattype:TestAnyClass.Type) -> Bool{
if type(of: t) === whattype {
return true//true
}else {
return false
}
}
print(typeTester(t: ddd, whattype: TestAnyClass.self))//true
print(typeTester(t: ddds, whattype: TestAnyClass.self))//false
4.11.3 Any
Any类型是被所有类型自动使用的一个空协议的类型别名;这样,在需要一个Any对象的地方,你可以使用任何对象;
类型为Any的对象可以与任何对象或函数类型进行比较,也可以向下类型转换为它们;
4.12 集合类型
Swift拥有内建的集合类型:Array Dictionary Set;
Swift为其提供的函数有限,不足的通过Cocoa的NSArray与NSDictionary补充;
Array与NSArray Dictionary与NSDictionary彼此桥接, Set与NSSet桥接;
4.12.1 Array
数组(Array,是一个结构体)是对象实例的一个有序集合(即数组元素),可以通过索引号进行访问,索引号是个Int,值从0开始;
Swift数组不可能是稀疏数组:如果有一个元素索引为3,那么肯定还有一个元素索引为2,以此类推;
Swift数组最显著的特征就是其严格的类型;数组必须包含相同类型的元素;甚至连空数组都必须要有确定的类型,尽管此时数组中并没有元素的存在;
数组类型与元素类型一样也是多态的:如果NoisyDog是Dog子类,那么NoisyDog的数组就可以用在需要Dog数组的地方;
Swift数组也是泛型,被声明为Array<Element>,其中占位符Element是特定数组元素的类型;
一个数组中的元素类型限制可以是:
1)父类数组,子类、父类实例;
2)协议数组,使用者实例;
3)AnyObject数组,类以及Swift桥接类型的实例;
4)类型本身也可能是不同的可能类型的载体;本章之前介绍的Error枚举(关联String和Int)就是一个例子,这样Error元素的数组就可能包含Int值与String值;
要声明或给定数组元素的状态,应该显示解析出泛型占位符;如Array<Int>;
Swift提供了语法糖来表示数组的元素类型,通过将方括号包围元素类型来表示,如[Int];
字面数组表示为方括号中逗号间隔的元素列表,[]表示空数组的字面值;
数组初始化:
1)数组默认的初始化器可以在数组类型后使用()调用;
let arr1 = [Int]()
print(arr1)//[]
//也可以这样
let arr2 :[Int] = []
print(arr2)//[]
2)数组又一个初始化器,其参数是个序列;这意味着,如果类型是个序列,你可以将其实例分割到数组元素中;
print(Array("flower".characters))//["f", "l", "o", "w", "e", "r"]
3)另一个数组初始化器 init(count: repeatValue:) 可以使用相同的值来装配数组;
let strings:[String?] = Array.init(repeating: nil, count: 5)
print(strings)//[nil, nil, nil, nil, nil]
这是Swift中最接近稀疏数组的数组创建方式:有5个槽,每个槽可能包含或不包含一个字符串(一开始都不包括);
当然还有很多其他的初始化方式,可自行查看API;
1.数组转换与类型检测
将一个数组类型赋值,传递或装换为另一个数组类型时,你操作的实际上是数组中的每个元素;
1)let arr:[Int?] = [1,2,3]//将Int数组看做包装了Int的Optional数组意味着原始数组中的每个Int都要包装到Optional中;
2)如下示例:
class ElephontSuper{
}
class Elephont:ElephontSuper{
}
let ele1:ElephontSuper = Elephont();
let ele2:ElephontSuper = Elephont();
let elearr1 = [ele1,ele2];
let elearr2 = elearr1 as! [Elephont];
print(elearr2);//Elephont...
我们将数组向下类型转换为它的子类型,这意味着我们将原来数组中的每个ElephontSuper都转换为了Elephont;
3)可以将数组的所有元素与is运算符进行比较来判断数组本身;
if elearr1 is [Elephont] {
print("sonthing")//sonthing
}
if elearr2 is [Elephont] {
print("sonthing")//sonthing
}
如果数组中的每个元素都是Elephont,那么结果就是true;
4)与之类似:as?运算符会将数组装换为包装了数组的Optional,如果底层的转换无法进行,那么结果就是nil;
let ele3:ElephontSuper = ElephontSuper()
let elearr3 = [ele2,ele3]
let eleasarr1 = elearr1 as? [Elephont]
print(eleasarr1 ?? "err-Null")//Elephont #1...
let eleasarr2 = elearr3 as? [Elephont]
print(eleasarr2 ?? "err-Null")//err-Null
注意是as?所在表达式的结果为nil;
2.数组比较
数组相等:如果两个数组包含相同数量的元素,并且相同位置上的元素全都相等,那么这两个数组就是相等的;
比较两个数组,两个数组不必非得是相同类型的,可以是子父类关系,不过除非它们包含的对象都彼此相等,否则这两个数组就不会相等;
3.数组是值类型
数组是一个结构体,因此他是个值类型,这意味着每次将数组赋值给变量或作为参数传递给函数时,数组都会被复制;
(Swift设计者已经考虑到了这种复制对性能的影响,并且在背后高效的进行了实现)
虽然数组本身是一个值类型,但是元素会按照元素本身的情况对待;
特别的:如果一个数组是类实例的数组,将其赋给多个变量,那么结果就是会有多个引用指向相同的实例;
4.数组下标
Array结构体实现了subscipt方法,可以通过对数组的引用后使用方括号来访问元素;
1)可以在方括号中使用Int;
2)可以在方括号中使用Int的Range;
array[1...2],技术上会生成一个ArraySlice,叫做切片,类似数组,也可以使用下标,在需要数组的地方也可以传递ArraySlice过去;
修改元素:
1)如果对数组的引用是可变的(var),那么就可以对下标表达式赋值,这么做会改变槽中的值;
2)如果下标是个范围,那么赋值就必须是一个数组;这会改变被赋值的数组长度;
var testarray1 = [1,2,3,4]
testarray1[1..<2] = []
print(testarray1)//[1, 3, 4]
如果访问数组元素时使用的下标大于最大值或小于最小值,会产生运行时错误,程序会崩溃;
5.嵌套数组
数组元素也可以是数组;
let testarray2:[[Int]] = [[1,2],[2,3,4],[3,4,5,6]]
print(testarray2)//[[1, 2], [2, 3, 4], [3, 4, 5, 6]]
上面示例:类型声明是[[Int]],被包含的数组长度可不同;
可以使用arr[i][j]的方式来访问;
如果是var修饰的数组,就可以赋值了;
6.基本的数组属性与方法
数组是一个集合(CollectionType协议),集合本身又是个序列(SequenceType协议);
1)-count只读属性:返回数组元素个数;
2)-isEmpty属性;
3)-first和last只读属性:返回第一个和最后一个元素,不过会被包装到Optional中,因为可能没有;
注:这里会出现Swift中较少遇见的将一个Optional包装到另一个Optional中的情况;比如,考虑包装Int的Optional数字,回去它的last;
let testarray3:[Int?] = [1,2,3]
print(type(of: testarray3.last))//Optional<Optional<Int>>
4)-数组可访问的最大索引比count小1;
5)-如果想要访问数组的最后n个元素,可以使用suffix方法;类似的还有prefix;他们有一个指的注意的特性,那就是超出范围后并不会报错;
let testarray4 = [1,2,3]
print(testarray4.suffix(10))//[1, 2, 3]
6)-相对于通过数量来描述后缀或前缀的大小;你可以通过索引来表示后缀或前缀的限制;
let testarray5 = [1,2,3]
let testarray6 = testarray5.suffix(from: 1)//从索引1到最后的元素(包括当前索引)
let testarray7 = testarray5.prefix(upTo: 1)//从开始到索引1的元素(不包括到达索引)
let testarray8 = testarray5.prefix(through: 1)//从开始到索引1的元素(包括到达索引)
print(testarray6,testarray7,testarray8)//[2, 3] [1] [1, 2]
print(testarray5.indices,type(of:testarray5.indices))//0..<3 CountableRange<Int>
7)-数组的startIndex属性值是0,其endIndex属性值是其count;
8)-数组的indices属性值是一个半开区间,端点分别是startIndex和endIndex,即访问整个数组的范围;
9)-indexOf方法会返回一个元素在数组中首次出现位置处的索引,不过他被包装到了Optional中。如果数组不存在这个元素就会返回nil;
10)-index(where:)还可以接收一个函数,这个函数接受一个元素类型并返回一个Bool,会得到返回true的第一个元素;
11)-作为序列,数组的contains方法会判断它是否包含了某个元素;类似于上边的,contains也可以提供自定义函数,同样接受一个元素,返回一个布尔;
12)-startWith方法判断数组的起始元素是否与给定的相同类型序列的元素相匹配;也可以接收自定义函数,该函数接收两个数组元素的类型值,并返回表示他们是否匹配的Bool;
print(testarray5.starts(with: [1,2]))//true
print(testarray5.starts(with: [1,-2], by: ({abs($0) == abs($1)})))//true
13)-elementEqual方法是数组比较的序列泛化:两个序列长度必须相同,其元素要么是Equatables,要么是自己提供的元素;
14)-min Element与max Element方法分别返回数组中最小与最大的元素;并且包装到了Optional中;
var testarray9 = [3,1,-2]
print(testarray9.min())//Optional(-2)
print(testarray9.min(by: {abs($0) < abs($1)}) ?? "err-null")//1
15)-append与appendContentsOf实例方法,在数组引用可变的情况下,会将元素添加到数组末尾;append接收单个元素,后者接收一个序列;
16)-若+运算符左侧是个数组,那么+会被重载,行为类似appendContentsOf,只不过他会生成新的数组,这种即便数组是常量也是可行的;
17)-数组引用可变时,实例方法insert(element: ,at:)或insert(contentsOf:Collection , at:Int)可以在指定索引处插入一个元素或多个元素;
18)-数组引用可变时,可以调用remove相关方法:
removeAtIndex会删除指定所引出的元素;removeLast会删除最后一个元素,removeFirst会删除第一个元素;这些方法还会返回从数组中删除的值;这些方法不会讲返回值包装到Optional中,访问越界的索引会导致程序崩溃;testarray9 .removeLast(n),这是另一种形式,可以指定删除元素个数,不过他不返回值;如果数组中没有那么多元素,程序将崩溃;
19)-popFirst和popLast会展开Optional中的返回值;这样即便数组为空也是安全的;如果引用不可变,那么可以通过dropFirst与dropLast方法返回删除了最后一个元素后的数组(实际是个切片);
print(testarray9.popLast()) //Optional(-2)
print(testarray9) //[3, 1]
testarray9.append(contentsOf: [1,2,3,4,5])
print(testarray9.dropFirst()) //[1, 1, 2, 3, 4, 5]
print(testarray9.dropLast()) //[3, 1, 1, 2, 3, 4]
print(type(of: testarray9.dropLast()))//ArraySlice<Int>
20)-joined(separator:)实例方法接收一个数组的数组,他会提取出每个元素,并将参数数组中的元素插入到提取出的每个元素序列之间;结果是一个叫JoinSequence的中间序列,如果必要,还要将他转换为Array;
调用joined(separator:)时将空数组作为参数可以将数组的数组打平;
let arrj1 = [[1,2],[3,4],[5,6]]
print(type(of: arrj1.joined(separator: [9,9])))
let arrj2 = Array(arrj1.joined(separator: [9,9]))
let arrj3 = Array(arrj1.joined(separator: []))
print(arrj2)//[1, 2, 9, 9, 3, 4, 9, 9, 5, 6]
print(arrj3)//[1, 2, 3, 4, 5, 6]
21)-reversed实例方法会生成一个新数组,其元素顺序与原始数组的相反;
22)-sort会对可变数组本身进行排序,sorted会返回一个排好序的新数组;可以提供一个比较函数,返回bool值,表示第一个参数是否应该在第二个参数之前;
testarray9.sort()//[1, 1, 2, 3, 3, 4, 5]
testarray9.sort(by:>)//[5, 4, 3, 3, 2, 1, 1]
print(testarray9)
23)-split实例方法会在 通过测试的元素位置处 将一个数组分解为数组切片的数组;通过测试的元素会被去除,这里的测试可以是一个元素也可以是一个函数;
print(testarray9.split(separator: 3))//[ArraySlice([5, 4]), ArraySlice([2, 1, 1])]
7.数组枚举与转换
数组是一个序列,因此你可以对其进行枚举,并按照顺序查看或操作每个元素;
for...in:
forEach:
forEach实例方法,其参数是一个函数,该函数接收数组(或是其他序列)
enumerate:
如果既需要索引号又需要元素,可以调用enumerate实例方法对结果进行循环;每次迭代得到的将是一个元组;
let perboys = ["Manny","Moe","Jack"]
for perboy in perboys {
print(perboy)
}
perboys.forEach{print($0)}
for (idx,perboy) in perboys.enumerated() {
print(idx,perboy)
}
perboys.enumerated().forEach{print($0.0,$0.1)}//0 Manny 1 Moe 2 Jack
Swift还提供了3个强大的数组转换实例的方法,和forEach一样这些方法都会枚举数组,这样循环就被隐藏到了方法调用中,代码也变得更加紧凑和整洁;
map实例方法:
他会生成一个新的数组,数组中每个元素都是将原有数组中相应的元素传递给你所提供的函数进行处理后的结果;
该函数接收一个元素类型的参数,并返回可能是其他类型的结果;Swift通常会根据函数返回的类型推断出生成的数组元素的类型;
let arrm1 = [1,2,3]
let arrm2 = arrm1.map{$0 * 2}
print(arrm2)//[2, 4, 6]
实际上,map是CollectionType的一个实例方法,Range类型本身也是CollectionType类型,因此(0..<10).map{}的调用也是合法的;
filter实例方法:
filter实例方法也会生成一个新数组,新数组中的每个元素都是老数组中的元素,顺序也相同;只不过,老数组中的一些元素会被过滤掉;
起过滤作用的是你所提供的函数;它接受一个元素类型的参数并返回一个Bool,表示这个元素是否应该被放到新数组中;
let arrf1 = ["Manny","Moe","Jack"]
let arrf2 = arrf1.filter{$0.hasPrefix("M")}
print(arrf2)//["Manny", "Moe"]
reduce实例方法:
这是一种将数组中(实际上是序列)所有元素合并为单个值的方式;这个值的类型(结果类型)不必与数组元素类型相同;
你提供一个函数,接受两个参数:第一个是结果类型,第二个是元素类型,结果是这两个参数的组合,他们作为结果类型;
没次迭代都会作为下一次迭代的第一个参数,同时数组的下一个元素会作为第二个参数;组合对不断累计的输出,以及最终的累计值就是reduce函数的最终结果;
你需要自己提供reduce调用的第一个参数;
//求数组和
let arrr1 = [1,23,44,5,6,77]
let result = arrr1.reduce(0){$0 + $1}
print(result)//156
//更简洁的写法
let result1 = arrr1.reduce(0, +)
print(result1)//156
我们接着举一个复杂一点的例子 会结合着三个实例方法一起使用:
let arrs1 = [["Manny","Moe","Jack"],["Harpo","Chico","Groucho"]]
let target = "M"
let arrs2 = arrs1.map{
$0.filter{
return $0.range(of: target, options: String.CompareOptions.caseInsensitive, range: nil, locale: nil) != nil
}
}.filter{$0.count > 0}
print(arrs2)//[["Manny", "Moe"]]
print(arrs2.reduce([],+))//["Manny", "Moe"]
8.Swift Array与Objective-C NSArray
在编写iOS应用时,你会导入Foundation框架(或是UIKit,因为它会导入Foundation),它包含了OC NSArray类型;
Swift的Array类型与Objective-C的NSArray类型是桥接的;不过,前提是数组中的元素类型可以桥接;
OC NSArray中元素,不必是同类型,但必须是对象,因为只有对象才能被OC所理解;
一般来说,如果类型能够向上转换为AnyObject(这意味着他是个类类型),或者如Int,Double及String这样特殊的桥接结构体,那木才可以桥接到OC;
Swift数组传递给OC很简单:要么通过赋值,要么作为函数调用的实参;(直接用即可)
Swift数组上调用NSArray方法,需要将其转换为NSArray;
let arrn1 = ["Manny","Moe","Jack"]
let sarrn1 = (arrn1 as NSArray).componentsJoined(by: ",")
print(sarrn1)//Manny,Moe,Jack
可变数组:
Swift数组可以通过var引用看出;要想在OC中获得可变数组,你需要NSArray的子类NSMutableArray;
你不能将Swift数组通过类型转换、赋值或传递的方式赋给NSMutableArray,必须强制进行,最佳方法就是调用NSMutableArray的初始化器init(array:),你可以直接向其传递一个Swift数组;
var arru1 = ["Manny","Moe","Jack"]
print(arru1);//["Manny", "Moe", "Jack"]
let arru2 = NSMutableArray(array:arru1)
arru2.remove("Moe")
arru1 = arru2 as NSArray as! [String]
print(arru2);
/*
(
Manny,
Jack
)
*/
print(arru1);//["Manny", "Jack"]
将NSMutableArray转换会Swift数组只需直接转换即可;如果需要一个拥有原始Swift类型的数组,那木需要转换两次才能通过编译;
手工“桥接”数组元素:
如果Swift对象类型不能向上转换为AnyObject,那就无法无法桥接到OC;如果需要一个NSArray,但你传递了一个包含这种类型的Array,那么编译器就会报错;
这种就需要手工“桥接”数组元素;
比如,Swift中的CGPoint结构体数组,就不能被放到NSArray中,如果在NSArray的地方传递了这个数组,那就会导致编译错误:[CGPoint]is not convertible to NSArray;
解决办法就是:将每个CGPoint包装为NSValue,这是个OC对象类型,专门用作各种非对象类型的载体;
let arrCGPoints = [CGPoint.init(x: 0, y: 0),CGPoint.init(x: 0, y: 1)]
let arrNSValues = arrCGPoints.map{ NSValue.init(cgPoint: $0) }
print(arrNSValues)//[NSPoint: {0, 0}, NSPoint: {0, 1}]
另一种情况是Swift的Optional数组;OC集合不能包含nil(OC中,nil不是对象);
在需要NSArray时如果传递Optional数组,那就需要事先对这些Optional进行处理;如果Optional包装了值,那么你可以将其展开;但如果没有包装值,就无法将其展开;
一种解决办法:
采取OC中的做法:OC NSArray不能包含nil,因此Cocoa提供了一个特殊的类NSNull,当需要一个对象时,其单例NSNull()可以代替nil;
这样,如果有一个包装了String的Optional数组,那么我可以将那些不为nil的元素展开,并使用NSNull()代替nil元素;
let arroc1:[String?] = ["flower",nil,"wohaha"]
let arroc2:[AnyObject] = arroc1.map{ if $0 == nil {return NSNull()}else {return $0! as AnyObject} }
print(arroc1)//[Optional("flower"), nil, Optional("wohaha")]
print(arroc2)//[flower, <null>, wohaha]
将NSArray从OC传递给Swift时会发生什么:
跨越桥接不会有问题:NSArray会安全地变成Swift Array;但是,就其本身来说,NSArray并没有携带关于它所包含的元素类型的任何信息;
因此,默认是OC NSArray转换为Swift的AnyObject数组(使用是,需要进行向下类型转换);
庆幸的是,Xcode7之后,OC语法发生了变化:
NSArray,NSDictionary,NSSet(这3种集合类型会桥接到Swift)的声明已经包含了元素类型信息,OC称之为轻量级泛型;
ios9中,Cocoa的API也包含了这些;这样,多数情况下,从Cocoa接收到的数组都是带有类型的;
4.12.2 Dictionary
字典(Dictionary,是个结构体)是成对对象的无序集合;