0%

近期在我厂的实际项目中发现,在iOS 8系统中,Tableview cell 的排版出现了错乱,特别是在执行reloadRows:方法后,cell不仅会出现一个动画,最后高度还会不对,详情看动图:
swift_ios8_tablview_bug

后来经过层层排查,基本可以确定是系统的一个bug,在Swift3 + iOS 8的情况下才会出现。

阅读全文 »

今天看了罗振宇的《时间的朋友2016》跨年演讲,讲了未来的4只黑天鹅、商业机会,其实这些并不是什么黑天鹅,因为这是个方面的机会早就有人提出过,有的已经是现在大热门了。这四个方面有:时间战场,挖掘现有用户的消费潜力;服务升级,直接给用户超乎想象的优质服务;智能革命,算法不重要,大数据称王;认知迭代,创造认知,开辟新市场。

阅读全文 »

最近负责完成了公司项目中一个模块的拆分,在此做一个简单的总结,只总结从项目中把某个模块拆分出去的一点经验,不涉及整个项目的模块化工作。

主要步骤:

  1. 确认模块的范围。
  2. 将模块与项目中其它部分解耦。
  3. 将模块拆分出去,并作为一个独立pod,通过cocoapods引入项目。
  4. 为拆分出去的模块创建一个可供独立开发的demo。
阅读全文 »

每一种强有力的语言都为此提供三种机制:

  • 基本的表达形式,用于表示语言所关心的最简单的个体。
  • 组合的方式,通过它们可以从较简单的东西出发构造出复合的元素。
  • 抽象的方法,通过它们可以为复合对象命名,并将它们当作单元去操作。
    在程序设计中,我们需要处理两类要素:过程和数据。而这两者并不是严格分离的,数据是一种我们希望去操作的东西,而过程是有关操作这些数据的规则的描述。这样,任何强有力的程序设计语言都必须能表述基本的数据和基本的过程,还需要提供对过程和数据进行组合和抽象的过程。
阅读全文 »

1
2
3
4
5
6

var str = "Pokémon"
var nsstr = str as NSString
str.characters.count // 7
nsstr.length // 8

如上,同样一个字符串,在String和NSString下,它的长度却不一样。因为Unicode编码是一种可变长格式,Unicode字符串是由编码点(code point)组成,而编码点又是由编码单元(code unit)组成。在不同的编码标准下,同一个字符串,可能会有不同的编码方式。Swift中的String内部实现尽量符合“标准等价”的编码规范,使我们更准确的处理字符长度。

阅读全文 »

在Swift3.0之前,访问控制分为三种:public、internal、private。而在Swift3.0,实际上有5种。

  • Open, 最高访问控制,通过引入这个模块,外部文件就能使用它,并且可以继承和覆盖重写。
  • Public, 次高访问控制,通过引入这个模块,外部文件就能使用它,但不能继承和覆盖重写。
  • Internal, 模块内部任何文件可以访问。
  • File-private, 文件内部才能访问。
  • Private, 代码作用域内才能访问。

默认的访问控制类型是Internal。

结构体(Struct)在Swift中占有重要地位,在Swift标准库中,大约有90%的公开类型都是结构体,包括我们常用的Array、String、Dictionary。结构体相比类,一个最重要的特性就是它是值类型,而类似引用类型。值类型是通过复制值来赋值的,而不是引用同一个内存地址,这样就不存在数据共享的问题,能防止意外的数据改变,并且它是线程安全的。

举一个很简单的例子,在objc中,数组是类,是引用类型,在Swift中,数组是结构体,是值类型。因此下面的代码中:

1
2
3
4
5
6
7

let array1 = NSMutableArray(array: ["lihua", "liming"])
let array2 = array1

array1.addObject("xiaowang")
array2

array1array2最后都变成了["lihua", "liming", "xiaowang"],也就是array1的改变会导致array2也发生改变,因为它们两个都是引用类型,并且都引用了同一个内存地址。

而在Swift中,就不存在这样的问题:

1
2
3
4
5
6
7

var array3 = ["lihua", "liming"]
var array4 = array3

array3.append("xiaowang")
array4

这段代码执行后,array3变成了["lihua", "liming", "xiaowang"],而array4还是["lihua", "liming"]。这就是结构体和类的最大区别。

那么,是不是每次将struct赋值给其它变量或者传递给函数时都会发生复制呢。答案是否定的,在Swift中的Array、Dictionary、String这些类型中,尽管它们都是值类型,但在Swift的具体实现中做了优化,可以避免不必要的复制。在《The Swift Programming Language (Swift 2.2)》一书的“Classes and Structures”一章末尾写道:

The description above refers to the “copying” of strings, arrays, and dictionaries. The behavior you see in your code will always be as if a copy took place. However, Swift only performs an actual copy behind the scenes when it is absolutely necessary to do so. Swift manages all value copying to ensure optimal performance, and you should not avoid assignment to try to preempt this optimization.

在Swift中采用的优化方式叫做写时复制技术,简单的说就是,只有当一个结构体发生了写入行为时才会有复制行为。具体的做法就是,在结构体内部用一个引用类型来存储实际的数据,在不进行写入操作的普通传递过程中,都是将内部的reference的应用计数+1,在进行写入操作时,对内部的reference做一次copy操作用来存储新的数据,防止和之前的reference产生意外的数据共享。

在Swift中有一个方法:isUniquelyReferencedNonObjC(Swift 2.2),在Swift3中这个函数变成了这样:isKnownUniquelyReferenced,他能检查一个类的实例是不是唯一的引用,如果是,我们就不需要对结构体实例进行复制,如果不是,说明对象被不同的结构体共享,这时对它进行更改就需要进行复制。

但这个函数只对Swift对象有用,如果要用在Objective-C对象上,可以将OC对象用Swift进行一次封装。

下面是《Advanced Swift》书中的一个实现写时复制技术的代码实例,我已经把它转为Swift3了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

final class Box<A> {
var unbox: A
init(_ value: A) {
unbox = value
}
}

struct GaussianBlur {
private var boxedFilter: Box<CIFilter> = {
var filter = CIFilter(name: "CIGaussianBlur", withInputParameters: [:])!
filter.setDefaults()
return Box(filter)
}()

fileprivate var filter: CIFilter {
get { return boxedFilter.unbox }
set { boxedFilter = Box(newValue) }
}

private var filterForWriting: CIFilter {
mutating get {
if !isKnownUniquelyReferenced(&boxedFilter) {
filter = filter.copy() as! CIFilter
}
return filter
}
}

var inputImage: CIImage {
get { return filter.value(forKey: kCIInputImageKey) as! CIImage }
set { filterForWriting.setValue(newValue, forKey: kCIInputImageKey) }
}
var radius: Double {
get { return filter.value(forKey: kCIInputRadiusKey) as! Double }
set { filterForWriting.setValue(newValue, forKey: kCIInputRadiusKey) }
}
}

extension GaussianBlur {
var outputImage: CIImage? {
return filter.outputImage
}
}

切片(Slice)是基于任何集合类型(遵守CollectionType的类型)的轻量级封装,默认的实现是返回了一个对原来集合的封装,再加上一个索引的子范围,所以它的内存大小会比原来更大。而且包括Swift的数组和字符串在内的很多可切片的容器,切片和原集合共享存储缓存,这会导致即使原集合离开了作用域,切片依然会持有原集合的缓存,这可能会导致内存问题。

With many sliceable containers, including Swift’s arrays and strings, a slice shares the storage buffer of the original collection. This has an unpleasant side effect: slices can keep the original collection’s buffer alive in its entirety, even if the original collection falls out of scope. If you read a 1 GB file into an array or string, and then slice off a tiny part, the whole 1 GB buffer will stay in memory until both the collection and the slice are destroyed.

另外,因为切片改变了索引范围,因此我们不能默认其索引是从0开始,而应该是从其startIndex开始,这也是为什么在Swift中应该用 for in 循环,而弃用C风格的for循环的原因之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let array = [1, 4, 5, 2, 11, 34, 33, 88, 43]
let subArray = array.suffixFrom(4)

// subArray改变了索引范围,即改变了startIndex和endIndex。
subArray.startIndex // 4
subArray.endIndex // 9

// 因为subArray占用的内存比原集合更大
sizeofValue(array) // 8
sizeofValue(subArray) // 32


// subArray持有原集合,只是改变了索引范围
// 所以直接以数字为索引,很容易出错。
// 标准的索引:
subArray[subArray.startIndex.advancedBy(0)]
subArray[0] //这一句会报错,因为subArray.startIndex 是4,0越界了。
subArray.count


// 这也是Swift中弃用传统C风格的for循环,改用for in循环的原因之一。
for int in subArray {
print(int)
}

本文中,我们将会探索Swift原生Array数组的实现方式,并且自定义实现一个数组类型,能够字面量来创建数组,通过下标来获取元素。

通过查看文档我们发现,Swift的数组是一个结构体类型,它遵守了CollectionTypeMutableCollectionType_DstructorSafeContainer协议,其中最重要的就是CollectionType协议,数组的一些主要功能都是通过这个协议实现的。
CollectionType协议又遵守IndexableSequenceType这两个协议。而在这两个协议中,SequenceType协议是数组、字典等集合类型最重要的协议,在文档中解释了SequenceType是一个可以通过forin循环迭代的类型,实现了这个协议,就可以forin循环了。

A type that can be iterated with a forin loop.

SequenceType是建立在GeneratorType基础上的,sequence需要GeneratorType来告诉它如何生成元素。

GeneratorType

GeneratorType协议有两部分组成:

  1. 它需要有一个Element关联类型,这也是它产生的值的类型。
  2. 它需要有一个next方法。这个方法返回Element的可选对象。通过这个方法就可以一直获取下一个元素,直到返回nil,就意味着已经获取到了所有元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/// Encapsulates iteration state and interface for iteration over a
/// sequence.
///
/// - Note: While it is safe to copy a generator, advancing one
/// copy may invalidate the others.
///
/// Any code that uses multiple generators (or `for`...`in` loops)
/// over a single sequence should have static knowledge that the
/// specific sequence is multi-pass, either because its concrete
/// type is known or because it is constrained to `CollectionType`.
/// Also, the generators must be obtained by distinct calls to the
/// sequence's `generate()` method, rather than by copying.
public protocol GeneratorType {
/// The type of element generated by `self`.
associatedtype Element
/// Advance to the next element and return it, or `nil` if no next
/// element exists.
///
/// - Requires: `next()` has not been applied to a copy of `self`
/// since the copy was made, and no preceding call to `self.next()`
/// has returned `nil`. Specific implementations of this protocol
/// are encouraged to respond to violations of this requirement by
/// calling `preconditionFailure("...")`.
@warn_unused_result
public mutating func next() -> Self.Element?
}

我把自己实现的数组命名为MYArray,generator为MYArrayGenerator,为了简单,这里通过字典来存储数据,并约定字典的key为从0开始的连续数字。就可以这样来实现GeneratorType:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// 需保准dic的key是从0开始的连续数字
struct MYArrayGenerator<T>: GeneratorType {
private let dic: [Int: T]
private var index = 0

init(dic: [Int: T]) {
self.dic = dic
}

mutating func next() -> T? {
let element = dic[index]
index += 1
return element
}
}


这里通过next方法的返回值,隐式地为Element赋值。显式地赋值可以这样写typealias Element = T。要使用这个生成器就非常简单了:

1
2
3
4
5
6
7
8
9
10
let dic = [0: "XiaoHong", 1: "XiaoMing"]

var generator = MYArrayGenerator(dic: dic)

while let elment = generator.next() {
print(elment)
}
// 打印的结果:
// XiaoHong
// XiaoMing

SequenceType

有了generator,接下来就可以实现SequenceType协议了。SequenceType协议也是主要有两部分:

  1. 需要有一个Generator关联类型,它要遵守GeneratorType
  2. 要实现一个generate方法,返回一个Generator。
    同样的,我们可以通过制定generate方法的方法类型来隐式地设置Generator:
1
2
3
4
5
6
7
8
struct MYArray<T>: SequenceType {
private let dic: [Int: T]

func generate() -> MYArrayGenerator<T> {
return MYArrayGenerator(dic: dic)
}
}

这样我们就可以创建一个MYArray实例,并通过for循环来迭代:

1
2
3
4
5
6
7
8
let dic = [0: "XiaoHong", 1: "XiaoMing", 2: "XiaoWang", 3: "XiaoHuang", 4: "XiaoLi"]
let array = MYArray(dic: dic)

for value in array {
print(value)
}

let names = array.map { $0 }

当然,目前这个实现还存在很大的隐患,因为传入的字典的key是不可知的,虽然我们限定了必须是Int类型,但无法保证它一定是从0开始,并且是连续,因此我们可以通过修改初始化方法来改进:

1
2
3
4
init(elements: T...) {
dic = [Int: T]()
elements.forEach { dic[dic.count] = $0 }
}

然后我们就可以通过传入多参数来创建实例了:

1
let array = MYArray(elements: "XiaoHong", "XiaoMing", "XiaoWang", "XiaoHuang", "XiaoLi")

再进一步,通过实现ArrayLiteralConvertible协议,我们可以像系统的Array数组一样,通过字面量来创建实例:

1
2
3
4
5
6
7
8
extension MYArray: ArrayLiteralConvertible {
init(arrayLiteral elements: T...) {
dic = [Int: T]()
elements.forEach { dic[dic.count] = $0 }
}
}

let array = ["XiaoHong", "XiaoMing", "XiaoWang", "XiaoHuang", "XiaoLi"]

最后还有一个数组的重要特性,就是通过下标来取值,这个特性我们可以通过实现subscript方法来实现:

1
2
3
4
5
6
7
8
extension MYArray {
subscript(idx: Int) -> Element {
precondition(idx < dic.count, "Index out of bounds")
return dic[idx]!
}
}

print(array[3]) // XiaoHuang

至此,一个自定义的数组就基本实现了,我们可以通过字面量来创建一个数组,可以通过下标来取值,可以通过for循环来遍历数组,可以使用map、forEach等高阶函数。

小结

要实现一个数组的功能,主要是通过实现SequenceType协议。SequenceType协议有一个Generator实现GeneratorType协议,并通过Generator的next方法来取值,这样就可以通过连续取值,来实现for循环遍历了。同时通过实现ArrayLiteralConvertible协议和subscript,就可以通过字面量来创建数组,并通过下标来取值。

CollectionType

上面我们为了弄清楚SequenceType的实现原理,通过实现SequenceTypeGeneratorType来实现数组,但实际上Swift系统的Array类型是通过实现CollectionType来获得这些特性的,而CollectionType协议又遵守IndexableSequenceType这两个协议。并扩展了两个关联类型GeneratorSubSequence,以及9个方法,但这两个关联类型都是默认值,而且9个方法也都在协议扩展中有默认实现。
因此,我们只需要为Indexable协议中要求的 startIndexendIndex 提供实现,并且实现一个通过下标索引来获取对应索引的元素的方法。只要我们实现了这三个需求,我们就能让一个类型遵守 CollectionType 了。因此这个自定义的数组可以这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct MYArray<Element>: CollectionType {

private var dic: [Int: Element]

init(elements: Element...) {
dic = [Int: Element]()
elements.forEach { dic[dic.count] = $0 }
}

var startIndex: Int { return 0 }
var endIndex: Int { return dic.count }
subscript(idx: Int) -> Element {
precondition(idx < endIndex, "Index out of bounds")
return dic[idx]!
}
}

extension MYArray: ArrayLiteralConvertible {
init(arrayLiteral elements: Element...) {
dic = [Int: Element]()
elements.forEach { dic[dic.count] = $0 }
}
}

注意:在Swift 3 中,SequenceTypeGeneratorTypeArrayLiteralConvertible都已改名。

本文将介绍filterreduce两个高阶函数的使用和内部实现。

filter

filter方法可以过滤掉数组中不符合条件的元素,返回包含所有符合条件的元素的数组。比如现在需要选出一个数组中所有的偶数,传统的做法是这样的

1
2
3
4
5
6
7
8
let numbers = [1, 2, 3, 4, 5]
var evens = [Int]()
for num in numbers {
if num % 2 == 0 {
evens.append(num)
}
}
print(evens) // [2,4]

map方法一样,filter也是讲for循环中的代码块用闭包封装了,我们只需要在闭包中传入筛选条件即可:

1
2
3
let numbers = [1, 2, 3, 4, 5]
let evens = numbers.filter{$0 % 2 == 0}
print(evens) // [2,4]

使用filter方法的优点和map方法一样,代码简洁、语义清晰、更安全。

filter的内部实现和map差不多,就是在像新数组添加元素时增加了一个条件判断。可以这样实现它:

1
2
3
4
5
6
7
8
9
10
11

extension Array {
func my_filter(includeElement: Element -> Bool) -> [Element] {
var result: [Element] = []
for x in self where includeElement(x) {
result.append(x)
}
return result
}
}

点击查看源码,然而我并没有完全看懂,囧。

reduce

1
2
3
4
5
6
7
8
9
extension SequenceType {
/// Returns the result of repeatedly calling `combine` with an
/// accumulated value initialized to `initial` and each element of
/// `self`, in turn, i.e. return
/// `combine(combine(...combine(combine(initial, self[0]),
/// self[1]),...self[count-2]), self[count-1])`.
@warn_unused_result
public func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T
}

从文档来看,reduce方法就是传入一个初始值和合并函数,然后计算合并后的值并返回,相比之前的map,flatMap,filter都是返回一个新的数组不同,这里只是返回一个元素合并后的新元素。比如我们想要将数组的元素全部加起来,可以这样写:

1
2
3
4
5
6
7
8

let numbers = [1, 2, 3, 4, 5]
var result = 0
for num in numbers {
result = result + num
}
print(reduce) // 15

reduce,也只需要一句代码:

1
2
3
4
let numbers = [1, 2, 3, 4, 5]
let reduce = numbers.reduce(0, combine: +)
print(reduce) // 15

因为在Swift中,运算符也是函数,因此可以将运算符作为参数传入。
我们可以这样实现一个reduce函数:

1
2
3
4
5
6
7
8
9
10
extension Array {
func my_reduce<U>(initial: U, combine: (U, Element) -> U) -> U {
var result = initial
for x in self {
result = combine(result, x)
}
return result
}
}