Swift中的iOS设计模式(一)

来源:互联网 发布:查找域名是否被注册 编辑:程序博客网 时间:2024/06/04 18:16

iOS设计模式 - 大伙应该都听说过,但是有多少人真正的了解他们呢?虽然大多数开发者都认同设计模式的重要性,但是在实际开发中却并不怎么注意使用设计模式,而且关于设计模式的文章也是凤毛麟角,这更使得开发者无从下手去学习设计模式。

设计模式是一个处理软件设计中常见问题的解决方法,并可以重复使用。它向开发者提供了设计模板,使开发者更容易写出逻辑清晰、具有可复用性的代码。它还可以使代码具有松耦合性,能让开发者轻松的更新或替换项目中使用的组件。

在本教程中,大伙要开发一个音乐仓库应用,能显示你们收藏的专辑以及相关信息。

在开发过程中,大伙会逐渐掌握大多数通用的Cocoa设计模式:

构建设计模式:Singleton。

架构设计模式:MVC,Decorator,Adapter,Facade。

行为设计模式:Observer,Memento。

千万不要认为该文章只是对设计模式理论上的讲解,大伙需要在你们的音乐仓库应用中运用到这些设计模式。你们的应用最终看起来大概是这个模样:

027.png

准备开始

从这里下载初始项目,解压Zip文件,然后在Xcode中打开BlueLibrarySwift.xcodeproj工程。

在开始之前,有三件事需要大伙注意:

在ViewController中有两个IBOutlet连接着Storyboard中的TableView和Toolbar。

在Storyboard中的ViewController里含有三个组件,并且设置了AutoLayout布局约束。最上面的组件用来显示音乐专辑的封面。中间是一个TableView,用来显示与该专辑相关的信息。最下面是两个Toolbar按钮,一个是撤销操作按钮,另一个是删除选中专辑的按钮。你们的Storyboard看起来应该是下面这个样子:

pic

在工程里还有一个HTTP Client类(HTTPClient),这个类目前是空的,但是你们在之后会充实它。

注意:你们知道吗,当你们创建了一个新的Xcode项目后,你们所编写的代码其实就已经在遵循一定的设计模式了,Model-View-Controller, Delegate, Protocol, Singleton这些设计模式统统可以免费使用哦。

在带你们深入了解第一个设计模式之前,你们需要创建两个类,用于存储和展示音乐专辑的数据。

点击菜单 File\New\File… (或者按下 Command+N 快捷键)。选择 iOS > Cocoa Touch Class ,点击 Next。设置该类的名称为 Album ,并让它继承 NSObject。最后选择语言为 Swift 然后点击 Next,最后点击 Create。

打开Album.swift文件,在Album类中定义如下属性:

1
2
3
4
5
var title : String!
var artist : String!
var genre : String!
var coverUrl : String!
var year : String!

然后添加一个初始化方法:

1
2
3
4
5
6
7
8
9
init(title: String, artist: String, genre: String, coverUrl: String, year: String) {
  super.init()
  
  self.title = title
  self.artist = artist
  self.genre = genre
  self.coverUrl = coverUrl
  self.year = year
}

这段代码为Album类创建了一个初始化方法,当你要创建一个新的专辑时你要通过这个初始化方法,传入专辑名称、演唱者、风格、专辑封面图片的URL以及年份这些属性。

接下来需要再添加一个方法:

1
2
3
4
5
6
7
func description() -> String {
  return "title: \(title)" +
   "artist: \(artist)" +
   "genre: \(genre)" +
   "coverUrl: \(coverUrl)" +
   "year: \(year)"
}

description()方法将专辑的这些属性拼成一个字符串,并返回。

我们再次通过刚才创建类的步骤创建一个名为AlibumView的类,注意该类要继承UIView。

打开AlbumView.swift文件,在该类中添加两个属性:

1
2
private let coverImage: UIImageView! 
private let indicator: UIActivityIndicatorView!

coverImage属性用来展示专辑封面,indicator是当正在下载封面图片时转动的菊花。

大伙注意,这里的两个属性分配了private访问级别,也就是说这两个属性只能在AlbumView.swift文件中使用,因为其他的类压根没有必要知道这两个属性的存在。(译者:其实这里用不用private都无所谓,因为咱们写的又不是library或者framework。)

接下来为AlbumView类添加初始化方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
required init(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
}
  
init(frame: CGRect, albumCover: String) {
  super.init(frame: frame)
  backgroundColor = UIColor.blackColor()
  coverImage = UIImageView(frame: CGRectMake(5, 5, frame.size.width - 10, frame.size.height - 10))
  addSubview(coverImage)
  indicator = UIActivityIndicatorView()
  indicator.center = center
  indicator.activityIndicatorViewStyle = .WhiteLarge
  indicator.startAnimating()
  addSubview(indicator)
}

由于UIView遵循了NSCoding,而AlbumView又继承了UIView,所以这里需要写一个NSCoder的初始化方法,但我们不会在这个方法中处理什么逻辑,所以调用super.init即可。

AlbumView真正的初始化方法是另外一个init方法,在这个方法中设置了一些默认的属性,比如将背景色设置为黑色、实例化了coverImage属性,并让它与父容器有5px的四周间距、实例化indicator并设置位置及风格。

最后再添加一个方法:

1
2
3
4
5
6
7
func highlightAlbum(#didHighlightView: Bool) {
  if didHighlightView == true {
    backgroundColor = UIColor.whiteColor()
  else {
    backgroundColor = UIColor.blackColor()
  }
}

这个方法是专辑封面高亮开关,高亮时专辑封面背景色为白色,否则为黑色。

现在编译你们的工程,确保一切都没有问题,然后准备开始第一个设计模式的学习。

设计模式的王者 - MVC模式

pic

Model-View-Controller (MVC)设计模式是Cocoa框架的基石,毋庸置疑它是开发者们最常用的设计模式没有之一。它把应用中的对象按它们的角色进行分类,并鼓励开发者按这种角色分类创建项目目录,将代码放置在合适的位置,保证项目结构清晰明确。

顾名思义,MVC中有三种角色:

  • Model:这种对象保存应用程序的数据,并定义如何操作处理这些数据。比如之前你们创建的Album类就是一个Model对象。

  • View:这种对象主要负责Model对象的呈现,以及用户交互。基本上,由UIView衍生出的类都是View对象。在你们的音乐仓库应用中AlbumView就是一个View对象。

  • Controller:这种对象充当着应用程序的协调者,由它来协调所有的事情。它会访问Model对象的数据,然后展示在相应的View对象中;它也会监听用户在View对象上的交互,从而通知Model对象进行相应的数据操作等等。你们应用中的ViewController就是一个Controller对象。

下图可以很好的说明Model对象和View对象是如何通过Controller对象进行通信的:

pic

当Model对象的数据发生改变时,它会通知Controller对象,然后Controller对象更新对应的View对象上展示的数据。当用户在View对象进行了交互操作时,View对象会通知Controller对象,然后Controller对象会更新对应的Model对象中的数据。

你们可能会有这样的疑惑,为什么不把这些操作处理都写在Controller对象中呢,这样就不用通知来通知去的,不是更简单吗?

我告诉你们两个概念,你们就明白这样做的目的了,那就是低耦合性和高复用性。我举个例子,在一个应用中每个界面中的数据大多数是来自于多个Model对象,如果把View对象和Model对象绑定死了,那么就没法处理这种情况了。

拿咱们这个音乐仓库的应用来说,如果你们以后想做一个电影仓库或者图书仓库,你仍然可以使用AlbumView这个View对象来展示你的电影或者图书Model对象。假如你的电影仓库应用需要展示电影主题曲的一些信息,那么或许你就可以直接复用Album对象,因为Album对象不依赖于任何View对象。这就是MVC设计模式的强大之处。

如何使用MVC设计模式

首先,你要确保项目中每个类的功能,要么是Controller,要么是View,要么是Model。千万不要将两种角色混合于一个类,每个类只有单一的职责。不过,到目前为止,你们已经创建了标准的Model类Album和View类AlbumView。

其次,为了使项目目录结构清晰明了,以及能够更感官的实行MVC模式,你们需要在工程中创建三个文件组,来区分开这三种角色的文件。

通过File\New\Group(或同时按下Command+Option+N)在工程中创建一个名为 Model 的文件组,然后以同样的方式创建 View 文件组和 Model 文件组。

最后将 Album.swift 文件拖进Model文件组,将 AlbumView.swift文件拖进View文件组,将 ViewController.swift 文件拖进Model文件组。

此时,你们的项目目录结构应该是这样的:

pic

现在的项目结构看起来已然井然有序,当然你们还可以创建其他的文件组和类文件,但是要记住的是,Model、View、Controller这三个文件组是整个程序的核心所在。

现在项目结构已经理清楚了,接下来的工作就需要从某个地方获取到专辑的相关数据。你们可以创建一个名为API的类,用于负责整个应用的数据管理工作。并且将拉开你们要了解的下一个设计模式 -- Singleton。

Singleton设计模式

单例模式使一个类在整个应用生命周期内只存在一个实例,并且有一个全局的方法来访问这个实例。在单例模式下,当第一次访问某个类的实例时,该类通常使用延迟加载的方式创建该类的单例。

注意: Apple在iOS和OSX中大量使用了单例模式,比如: NSUserDefaults.standardUserDefaults(), UIApplication.sharedApplication(), UIScreen.mainScreen(), NSFileManager.defaultManager()。

你们可能会有疑问,为什么我们要这么在意一个类有一个或多个实例?代码和内存现在是如此的廉价,不是么?

其实不然,有些情况下,确实只需要类实例化一次,且仅有一次。比如有这么一种情况,在一个应用的生命周期里,应用(Application)设备的主屏幕是只存在一份的,那么你当然希望应用和设备屏幕的实例有且只有一个。或者你需要一个全局的处理配置的类,这样能线程安全的访问配置文件,避免多个配置类同时访问一个配置文件。这些就是单例模式的好处所在。

如何使用Singleton设计模式

先看看下面这张图:

pic

上图是Logger的类图,从图中可以看出,Logger有一个instance属性以及sharedInstance和init两个方法。

当第一次调用sharedInstance方法时,instance属性还没有初始化,所以你会创建一个新的Logger类实例,并返回一个该实例的引用。

当再次调用sharedInstance方法时,instance属性会立即返回,并且不需要再进行任何实例化操作。这个逻辑就保证了Logger类的实例有且仅有一份。

你们将要通过Singleton设计模式,创建一个单例的类用于管理所有专辑的数据。

你们应该注意到了,在项目的目录结构中,有一个文件组叫做API,这个文件组里存放的类基本都是为应用提供服务的类。我们在这个文件组中创建一个名为 LibraryAPI 的类。

打开 LibraryAPI.swift 文件,添加如下代码:

1
2
3
4
5
6
7
8
9
10
//1
class var sharedInstance: LibraryAPI {
  //2
  struct Singleton {
    //3
    static let instance = LibraryAPI()
  }
  //4
  return Singleton.instance
}

创建一个类变量的计算属性。类变量类似Objective-C中的类方法,也就是说在任何时候你访问sharedInstance属性时,都不需要对LibraryAPI进行实例化,关于属性类型更多的知识请参阅Swift文档 -- properties。

在类变量中内嵌一个结构体,名为Singleton。

Singleton中包含一个名为instance的静态常量属性。用static申明属性意味着该属性只能存在一份。这里要注意的是Swift中的静态属性都会延迟加载,也就是说只有instance被使用时,才会初始化它。还要注意的一点是,一旦instance被初始化了,那么它就是一个常量属性,不会有第二次初始化的机会了。这就是Singleton模式的精髓所在。

返回该计算属性的值。

注意:如果想了解Swift中创建单例的其他方法请参阅这里:Github page。

你们现在已经有一个单例模式的对象,作为管理专辑数据的入口。接下来要更进一步,创建一个类,用于处理你们数据的持久性。

继续在 API 这个文件组中创建一个类,名为PersistencyManager,并让它继承NSObject类。

打开 PersistencyManager.swift 文件,申明一个属性:

1
private var albums = [Album]()

这里申明了一个private访问权限的变量属性,用于储存专辑数据。这个数组是可变数组,所以你们可以很轻松的增删专辑。

接下来我们在该类中添加如下初始化方法:

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
override init() {
  //Dummy list of albums
  let album1 = Album(title: "Best of Bowie",
         artist: "David Bowie",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png",
         year: "1992")
  
  let album2 = Album(title: "It's My Life",
         artist: "No Doubt",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png",
         year: "2003")
  
  let album3 = Album(title: "Nothing Like The Sun",
         artist: "Sting",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png",
         year: "1999")
  
  let album4 = Album(title: "Staring at the Sun",
         artist: "U2",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png",
         year: "2000")
  
  let album5 = Album(title: "American Pie",
         artist: "Madonna",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png",
         year: "2000")
  
  albums = [album1, album2, album3, album4, album5]
}

在初始化方法中,你们可以构建一些专辑添加到albums数组中,这里我构建了5个专辑。

然后再添加几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func getAlbums() -> [Album] {
  return albums
}
  
func addAlbum(album: Album, index: Int) {
  if (albums.count >= index) { 
    albums.insert(album, atIndex: index)
  else {
    albums.append(album)
  }
}
  
func deleteAlbumAtIndex(index: Int) {
  albums.removeAtIndex(index)
}

这些方法可以让你们方便的存、取、删除album数组中的专辑。

然后编译你们的项目,确保编译通过。此时大伙也许又有了疑问,我们应该如何使用PersistencyManager类呢?别着急,在下一节里,会向大家介绍 Facade 设计模式,届时你们就会明白LibraryAPI与PersistencyManager之间的关系,以及如何使用PersistencyManager了。

Facade设计模式

021.png

Facade设计模式为多个子模块或子系统提供统一的、单独的API接口。也就是说,不用给用户暴露一堆乱七八糟的接口,只需要暴露一个简单的、标准的接口即可。

下面这张图能更好的描述这个概念:

022.png

用户在使用我们暴露的标准的API时,根本不知道在这个API底下其实疏导着大量复杂的接口。该设计模式是暴露大量接口的最佳模式,尤其当接口使用很复杂、很难理解时,尤为体现该模式的价值。

Facade模式可以有效保证调用各个模块功能的接口和你隐藏起来的实现模块功能的逻辑代码是一个松耦合的关系。同时也可以减少你的子系统或子模块对外部代码的依赖。例如一个遵循Facade模式的类,如果逻辑有变化,需要修改内部实现代码,但是并不需要修改该逻辑的接口。使用接口的用户甚至根本都不知道你已经修改了该接口背后的某些逻辑功能。

如何使用Facade设计模式

目前,你们已经有PersistencyManager类用于在本地保存专辑的数据,以及HTTPClient类用于和远程服务器进行交互。项目中的其他类压根不会知道这两个类中具体的处理逻辑,因为他们将要遵循Facade模式,隐藏与LibraryAPI身后。

要遵循Facade模式,我们要有一个暴露给用户接口的类,那就是LibraryAPI,它持有PersistencyManager和HTTPClient的实例,并暴露一些简单的接口来访问这两个类中的方法:

023.png

LibraryAPI就是作为暴露给其他类的标准接口,它能有效的向接口使用者规避掉HTTPClient和PersistencyManager复杂的逻辑代码。

打开LibraryAPI.swift文件添加如下常量属性:

1
2
3
private let persistencyManager: PersistencyManager
private let httpClient: HTTPClient
private let isOnline: Bool

isOnline属性决定了是否向远程服务器上更新专辑数据的变化,比如添加或删除专辑。

然后需要添加init初始化方法:

1
2
3
4
5
6
7
override init() {
  persistencyManager = PersistencyManager()
  httpClient = HTTPClient()
  isOnline = false
  
  super.init()
}

因为本篇教程中的示例应用只是为了向大家介绍如何运用各种设计模式,所以没有远程服务器的需求,HTTPClient自然不会用到。所以这里isOnline属性总是设置为false。

下一步,向LibraryAPI.swift文件中添加如下三个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func getAlbums() -> [Album] {
  return persistencyManager.getAlbums()
}
  
func addAlbum(album: Album, index: Int) {
  persistencyManager.addAlbum(album, index: index)
  if isOnline {
    httpClient.postRequest("/api/addAlbum", body: album.description())
  }
}
  
func deleteAlbum(index: Int) {
  persistencyManager.deleteAlbumAtIndex(index)
  if isOnline {
    httpClient.postRequest("/api/deleteAlbum", body: "\(index)")
  }
}

大家看一下addAlbum(_:index:)方法,该方法顾名思义是添加专辑的方法,在实现时会先更新本地的数据,如果网络连通且需要使用远程服务的时候,再调用远程服务接口更新数据状态。当该模块以外的类调用了LibraryAPI接口的addAlbum方法时,它并不知道添加专辑的具体实现逻辑,并且也不需要知道,这就是Facade设计模式的魅力所在。

现在编译运行你们的应用。你们会看到两个空的视图,和一个Toolbar。顶部的视图用于显示专辑的封面,下面的视图会以列表的形式显示该专辑的相关信息。

024.png

接下来的工作就是将专辑的数据或图片等显示在屏幕上,这就给我们带来了学习另一个设计模式的机会 -- Decorator设计模式。

Decorator设计模式

Decorator模式可以自动的为对象添加某些行为或响应能力,并且不需要对该对象做任何修改。

该模式可以通过将希望添加的行为或响应能力打包到另一个对象中,然后通过该对象获得添加的行为或响应能力。

在Swift中,有两种最为常用的实现该模式的方案: Extensions 和 Delegation。

Extensions

你们可以对class、struct或者enum添加Extension,用于添加新的行为或响应能力,最关键的是不需要让它们继承乱七八糟的父类。更厉害的是通过Extension你们甚至不需要去访问目标class、struct或enum,就可以给它们添加新的能力。这意味着,你们可以让Cocoa框架中的对象元素更加符合你们自己的口味,比如给UIView或UIImage添加你们想要的能力。这就是Extension机制的强大之处。

这里要注意,通过Extension新添加的方法在编译阶段被加载,然后你们可以像使用某类的原生方法一样使用扩展的方法。但是与传统的Decorator模式有些许不同的是,扩展体不会持有被扩展对象的实例,说白了就是你不能实例化一个扩展体,只能通过实例化被扩展的对象,才能使用其扩展的方法。

如何使用Extensions

下图中展示了一种情况,你现在有了Albun对象,然后你想把该对象中的数据展示在一个UITableView中:

024.png

在该情况下,专辑的这些数据从哪来呢?很明显是从Album对象中获取,因为它是一个Model对象,而且它根本不关心它持有的数据要如何展现,展现在哪里。那么这时,你就要使用额外的代码让Album具备将数据按需归位的能力,并且不能直接修改该类。

这就需要使用Extension机制了,你们将通过Extension机制扩展Album类,目的是给Album添加一个方法,并返回一个UITableView便于使用的数据结构。

该数据结构图如下:

025.png

接下来我们新建一个Swift文件,名为 AlbumExtensions ,然后打开 AlbumExtensions.swift 文件,添加如下代码:

1
2
3
4
5
extension Album {
  func ae_tableRepresentation() -> (titles:[String], values:[String]) {
    return (["Artist""Album""Genre""Year"], [artist, title, genre, year])
  }
}

这里注意该方法名的开头ae_,它是AlbumExtensions的缩写。这是一个约定俗成的扩展方法名的写法,目的在于防止扩展方法与原生方法名产生冲突。

注意:通常类可以重写父类的方法或属性,但是在Extension中不可以。Extension中的方法名、属性名也不能和原生方法名、原生属性名相同。

我们再来总结一下Extension机制:

  • 当Album被扩展后,直接使用Album访问扩展方法。

  • 你们通过扩展的方式已经给Album添加了新的能力,如果你们还想通过子类的方式,那么依然可以这么做。

  • 上面这个简单的扩展可以看出,你们不用对Album做任何改动,它就具备了可以返回适用于UITableView数据格式的能力。

Delegation

另一个Decorator设计模式是Delegation,它是一种代表其他对象或协调其他对象的机制。

比如当你使用UITableView时,你必须要实现tableView(_:numberOfRowsInSection:)方法,你不要期望UITableView会知道你希望每个Section里有多少行。因此,计算每个Section有多少行的任务就交给了UITableView的代理。这样就可以使UITableView显示与数据松耦合,独立开来。

下图是UITableView和它的代理之间的运行关系示意图:

025.png

UITableView的工作是将一些信息展示在一个列表视图中。但是它只关注于展示,并不会持有需要展示的数据。那么这时就需要向它的代理询问获取相关的信息了。在Objective-C的代理模式中,代理或协议可以申明两种类型的方法,一种是必须类型,另一种是可选类型。前者是遵循该协议的类必须要实现的方法,后者是可以实现,也可以不实现的方法。你们在该教程中会实践到这些内容。

这时大伙可能会有疑问了,为何我们要使用协议而不直接继承一个对象,然后重写需要的方法呢?这样不是更省事么?但是你们考虑一下,如果这样做,你们只能基于一个单独的父类去实现其子类,也就是说,该父类只能作为某一个对象的代理。如果想让一个对象成为多个对象的代理,那么用继承父类这种形式就行不通了。

注意:代理机制是一个很重要的模式。Apple在UIKit框架中大量应用了该模式,比如UITableView、UITextView、UITextField、UIWebView、UIAlert、UIActionSheet、UICollectionView、UIPickerView、UIGestrueRecognizer、UIScrollView等等。

如何使用Delegate模式

打开ViewController.swift文件,然后添加以下私有属性:

1
2
3
private var allAlbums = [Album]()
private var currentAlbumData : (titles:[String], values:[String])?
private var currentAlbumIndex = 0

然后重写viewDidLoad方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
override func viewDidLoad() {
  super.viewDidLoad()
  //1
  self.navigationController?.navigationBar.translucent = false
  currentAlbumIndex = 0
  
  //2
  allAlbums = LibraryAPI.sharedInstance.getAlbums()
  
  // 3
  // the uitableview that presents the album data
  dataTable.delegate = self
  dataTable.dataSource = self
  dataTable.backgroundView = nil
  view.addSubview(dataTable!)      
}

我们对上述的代码来进行一一讲解:

  • 关闭导航栏的透明效果。

  • 通过API获取所有专辑的列表。这里要记住,要使用符合Facade模式的LibraryAPI而不是直接使用PersisencyManager。

  • 对UITableView进行相关设置,将它的delegate和datasource设置为当前的ViewController。这样一来UITableView所有对数据的请求都会由当前的ViewController响应并提供了。这里需要注意的是如果你们在Storeboard中创建UIViewController和UITableView,那么也可以在Storeboard中通过拖拽来设置delegate和datasource。

接下来,在ViewController.swift中添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
func showDataForAlbum(albumIndex: Int) {
  // 保证代码健壮性,确保专辑数至少大于0,避免数组下标越界的错误
  if (albumIndex < allAlbums.count && albumIndex > -1) {
    //获取专辑对象
    let album = allAlbums[albumIndex]
    // 保存专辑的数据,用于一会在tableview中展现
    currentAlbumData = album.ae_tableRepresentation()
  else {
    currentAlbumData = nil
  }
  // 我们已经获取到了需要的数据,可以刷新tableview来显示数据了
  dataTable!.reloadData()
}

showDataForAlbum()方法的作用是从专辑数组中获取到专辑的对象,然后请求并保存专辑的数据。当你想显示新的数据或有改变的数据时,你只需要调用reloadData方法即可。这样UItableView会重新请求它的代理获取相关数据,比如一共要显示多少个Section、每个Section里显示多少行、每行看起来是什么样的等等。

我们在viewDidLoad方法中再加入一行代码:

1
self.showDataForAlbum(currentAlbumIndex)

该行代码让应用启动时就开始加载专辑数据。因为之前已经将currentAlbumIndex的值设为了0,所以从专辑数组中的第一个专辑开始显示。

现在,是时候实现DataSource协议的方法了,你们可以将DataSource的方法直接写在ViewController里。也可以通过使用扩展的方式使代码保持整洁。

如果使用扩展的方式,那么一定要确保它们是写在文件的最下面,并且要在ViewController类定义的大括号之外!

1
2
3
4
5
extension ViewController: UITableViewDataSource {
}
  
extension ViewController: UITableViewDelegate {
}

上面这两行代码的含义就是ViewController通过扩展的方式遵循了Delegate和DataSource协议 -- 你可以把协议想象成是与委托之间的约定,只要你实现了约定的方法,就算是实现了委托。在我们的代码中,ViewController需要遵守UITableViewDataSource和UITableViewDelegate这两个协议。这样 UITableView 才能明确的知道,需要用到的代理中的方法是由这个ViewController实现的。

在遵循UITableViewDataSource协议的扩展中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  if let albumData = currentAlbumData {
    return albumData.titles.count
  else {
    return 0
  }
}
  
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
  if let albumData = currentAlbumData {
    cell.textLabel?.text = albumData.titles[indexPath.row]
      if let detailTextLabel = cell.detailTextLabel {
        detailTextLabel.text = albumData.values[indexPath.row]
      }
  }
  return cell
}

tableView(_:numberOfRowsInSection:)方法返回每个Section中显示多少行内容,这里对应着专辑数据结构中的标题。

tableView(_:cellForRowAtIndexPath:)方法会逐个创建每行的内容,包括专辑标题和它的值。

注意:你们可以把这些协议的方法直接加在类声明里面,也可以放在扩展里,编译器不会去管DataSource的方法是放在扩展里还是类申明里,只要实现了必须的方法即可。而我们之所以这样写,是为了保证代码的整洁性和可读性。

编译并运行你们的项目,你们的应用应该已经可以显示出专辑的基本信息了:

026.png

0 0
原创粉丝点击