Swift项目-今日头条

101 - 框架搭建

大家好,从本节课开始,我会一步一步地教大家把这个头条这个 app 实现出来。
今天的课程主要目的是搭建项目主框架。

1.新建项目

首先打开 xcode,我这里使用的 xcode 版本是8.3.3,点击新建一个 xcode 工程,然后选择 single view application ,输入项目名称,News,Team 一般是个人或者公司的开发者账号,这里我选择默认,然后输入组织名称,一般是公司或是组织的名称,我这边输入的 hrscy,下面是组织标识符,一般以 com 开头,后面带上公司或组织名称,我这里输入的是 com.hrscy,语言有两种,一种是 swift,另一种是 OC,这里我们选 swift,设备选 iphone 就可以了,其他都默认设置即可,然后点击下一步,选择想要保存的位置,我这边选择保存到桌面,

2.项目主框架一般有两种方式,一种是使用 storybaord 创建,另一种是使用代码创建

2.1.使用代码搭建基本框架,如下图设置

删除 Main

2.2 添加资源文件

2.3 自定义 UITabBarControllerUINavigationController

3.创建四个控制器,首页,西瓜视频,小视频,我的

image.png

3.1 中间的加号按钮比较特殊

UITabBarController下方的工具条称为UITabBar,首先先来看一下 tabbar 的子视图都有什么:

// 系统view控件准备好之后调用这个方法
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(tabBar.subviews)
}

如下图:

会发现四个名为 UITabBarButton 的类,如果UITabBarController有4个子控制器,那么UITabBar内部就会有4个UITabBarButton作为子控件与之对应。也就是说,UITabBarButtonUITabBarController中各个子控制器在工具条中对应的按钮的称呼,同时我们可以注意到一个现象,那就是我们创建好UITabBarButton之后,各个UITabBarButtonUITabBar中的位置是均分的,UITabBar的高度为49。UITabBarButton 是一个隐藏的子类,所谓隐藏是苹果没有在文档中明确提供却对视图的显示起着至关重要的作用,UITabBarButton⾥面显⽰什么内容,由对应子控制器的tabBarItem属性来决定。
下图就是UITabBar包含四个UITabBarButton和中间的UIButton

4.集成 cocoapods

我的 cocoapods 版本为 1.2.0.rc.1。
进入工程主目录,使用下面两个命令初始化并安装 cocoapods:

pod init

pod install

如下图:

安装完成后,会生成几个文件,分别是:

  • Podfile:该文件可以放在任意一个目录下,需要做的是在Podfile中指定工程的路径,一般会放在工程的主目录,该文件主要用于管理安装依赖库,工程需要的依赖库都在这里添加。
  • Podfile.lock:该文件用于保存已经安装的 Pods 依赖库的版本。
  • Pods 文件夹:安装的依赖库源文件都在放在这个文件夹里。
  • .xcworkspace 文件:该文件用来管理项目,它包含你创建的项目和cocoapods 的项目,以后不再打开 .xcodeproj文件,以后打开这个文件就相当打开最初创建的项目了。
    现在打开xcworkspace如下图:

5.项目架构用的比较多的有两种方式,一种是 MVC,一种 MVVM,这里使用比较传统的 MVC 设计模式。

6.建立文件夹,对项目进行模块划分

最终结构

102 - 补充内容

下面我来说一下上节课没有提到的内容,简单说一下 Xcode 的界面是由哪些部分组成以及它们的功能。

首先,打开项目之后,界面是可能是这个样子的。

项目主界面

上图为主界面。

顶部包括运行按钮,停止按钮,scheme,scheme 定义了编译集合中的若干target,以及编译时的一些设置和要执行的测试集合,我们也可以自定义若干个scheme,但是同一时刻只能运行一个。

顶部

下图是导航区,包含八项,,快捷键是 cmd+(1-8), 分别是工程导航器(cmd+1),项目中的类文件导航器(cmd+2),搜索导航器(cmd 的+3),问题导航器(cmd+4),测试导航器(smd+5),调试导航器(cmd+6),断点导航器(cmd+7),日志信息导航器(cmd+8)。

左边导航

右边是检查器(cmd+option+0),分为上下两部分:

上部分会有两种情况,包括文件就检查器(cmd+option+1),快捷帮助检查器(cmd+option+2),类标识检查器(cmd+option+3),属性检查器(cmd+option+4),尺寸检查器(cmd+option+5),连线检查器(cmd+option+6),如下图:

右上导航-1
右上导航-1

下部分包括文件模版(cmd+option+control+1),代码块(cmd+option+control+2),对象库(cmd+option+control+3),媒体库(cmd+option+control+4),如下图。

[图片上传失败...(image-e43754-1533613300593)]

下图是调试区(cmd+shift+y):

调试区

下面介绍一下 Xcode 中间的主工作区,也可以说是代码编辑区,首先点击项目名称,会出现上图主界面中间的样子,只有两个属性,一个是 info,一个 build setting,这两项是项目的基本信息。

下面点击 TARGETS,找到 General,下面介绍一下它里面的一些设置:

General

  1. 修改 Dispaly Name 为 头条,你也可以设置别的名称,这个名称就是最终 app 的名称。
  2. Version 就是版本号,修改 Version 为 6.3.2,在开发过程中,也就是在 App Store 最终显示的版本号,而 Build 是每次构建的版本,比如我在 App Store 准备提交一次更新,设置的版本为 6.3.2,build 为 1,之后上传到 App Store,那么在 App Store 中选择版本的时候就会出现 6.3.2(1) 这个版本,但是存储之后,还未提交审核之前,又发现了一个 bug,那么就需要马上修改,这个时候 build 就要 +1,或者比之前构建的版本大才可以,比如修改完 bug,设置 build 为 2,再次上传到 App Store,这个时候在版本选择中就会出现两个构建版本,分别是 6.3.2(1) 和 6.3.2(2),这是时候就要选择第二个来提交审核,也就是构建版本可以多次设置。
  3. Signing 就是签名,可以配置开发者账号和证书信息,Team 就是设置的开发者账号,现在都是 Xcode 自动管理的,所以只需要添加开发者账号,然后选择就可以了,当然你也可以设置不让 Xcode 自动管理,这个时候就需要自己来配置一下开发者账号了,为了避免麻烦,还是设置默认吧。
  4. Deployment Info 是设置一些开发信息,Deployment Target 就是 app 在安装的时候,所能支持的最小系统版本。这里可以依据公司的约定进行设置,比如要求系统的最低版本我 8.0,那么选择 8.0 就可以了。我这里默认是 10.3。
  5. App Icons and Launch Images,是 app 的图标和启动图片。
  6. Embedded Binaries 是添加动态框架(Framework),不做设置。
  7. Linked Frameworks and Libraries 是链接 Framework 和库,和对应 Build Phases 的 Linked Frameworks and Libraries,可以手动添加其他需要用的框架,比如 libz.1.2.8.tbd,cocoapods 框架会被自动添加到项目里。

Capabilities

可以添加一些额外的功能,比如地图,切换到后台模式,音频,钥匙串,关联相关 app,数据保护,HomeKit,HealthKit,无线访问配置,以及通知功能,需要添加哪些功能,把它打开即可。

Resourece Tags 不做设置

Info 也就是 info.plist 文件,app 的基本信息。

Build Settings 编译设置,暂不设置

Build Phases 编译阶段的设置,暂不设置

以上就是我对 Xcode 界面的一些简单介绍。

201 - 我的界面分析

经过上节课的学习,我们把项目的大体框架搭了出来。

对比了一下项目的这几个模块,我觉得『我的界面』实现起来还算是比较简单的,如果一开始就实现比较复杂的界面,可能有些基础不好的人会接受不了,所以我决定从简单的界面实现,然后再实现复杂的界面,这样一步一步地实现我们想要完成的项目。

当然这几个模块的关系不是很大,你也可以先实现其他模块。

下面我们就具体分析一下,我的界面是如何实现出来的。

我的界面

我的界面

我的界面由 UITableView 实现,分为上下两个部分,上边是头部,下边是 cell。总体来说比较简单,比较复杂的一点就是 cell 的数据是从后台获取的,不是直接写在代码里的。

2.头部的分析

头部

2. cell 的分析

2.1 第一个 cell

第一个 cell 有三种种情况,分为无任何关注,有一个关注,有两个及以上关注的情况,如下图:

我的-无关注
一个关注
两个或以上关注

其中图一是无任何关注,会隐藏『我的关注』 cell。

其中图二是当没有关注用户或者只关注了一个用户的情况,没有关注用户则右侧的用户头像和用户昵称则隐藏。

图三是当关注用户的数量在两个以上的情况。对于图二的情况,我的实现是使用 UICollectionView ,自定义 UICollectionViewCell,自定义cell时候还要区分关注的用户是否有新动态,有新动态则显示cell右上角的红点,或
者是否是认证用户,是认证用户则显示『v』字图标。

获取到数据之后根据返回的数据判断显示哪种情况,对于请求关注用户的接口,应在每次界面显示时候去请求一次,这样我的关注里的数据都是最新的数据,就会显示刚刚关注的用户。

2.2 其他 cell

其他 cell

其他 cell 就比较简单了,根据返回的数据,显示到对应的 label 上就可以了。

cell 分析就这些,这个界面用到了两个接口,一个是获取除了『我的关注』以外的 cell 数据,另一个是获取『我的关注』用户数据,数据请求我使用的是第三方框架 Alamofire

根据返回的数据,我的界面 cell 分为四组,其中第一组是『我的关注』,第二组是『消息通知』;第二组包括『头条通知』和『京东特供』;第三组包括『我要爆料』,『用户反馈』和『系统设置』。

然后在 Podfile 添加 Alamofire就可以了。
如下代码:

target 'News'  do
 use_frameworks!
 pod 'Alamofire', '~> 4.5.0'  # 数据请求 https://github.com/Alamofire/Alamofire,同 AFNetworking
 end

默认生成的测试 target 不需要,可以删掉。

在 iOS 9 中,默认情况下,是不支持 http 请求的,在 info.plist 添加 App Transport Security Settings 属性,然后在添加 Allow Arbitrary Loads 设置为 YES,表示允许 http 请求。

ATS

改类为继承自 UITableViewController

隐藏导航条。

新建一个 MineOtherCell.swift 文件,继承自 UITableView,勾选 xib 文件。

新建一个 NoLoginHeaderView.swift 文件,继承自 UIView,然后新建一个 xib 文件。

新建一个 swift 文件,命名为 MineCellModel.swift,作为模型文件。

3.头部实现日间夜间功能

夜间

点击夜间按钮,app 主题会变为黑色,实现方式用的是一个第三方框架 SwiftTheme,一个不错的主题替换方案,使用起来比较简单,该框架的介绍请看它的 github 主页,介绍的很详细。

让我们来思考一个问题,就是 Swift 的核心是什么?

不知道大家有没有看过 WWDC 2015 的视频,其中有一个编号为 408 的视频解释了这个问题,下面是视频链接:Protocol-Oriented Programming in Swift

视频中介绍了从 OOP(面向对象编程) 到 POP(面向协议编程)的转变过程。

Swift is a Protocol-Oriented Programming Language

Swift 是一门面向协议 (POP) 开发的语言

我说一下我的体会吧,我刚开始做 iOS 开发的时候使用 OC 来开发的,后来学习了 Swift,当时也是有一搭没一搭的学,了解了一下 Swift 的基本语法,感觉还是很简单的,因为当时 Swift 也是刚出来,很不稳定,1.0 到 2.0,甚至都不兼容,所以也就没有选择使用 Swift 来开发。

直到去年,也就是16 年,在 2.2 版本出来之后,感觉 Swift 还算比较稳定了,才决定使用 Swift 来开发一个简单的项目,当时决定模仿一个 app 来练手,也就是现在在我的 github 上的那个项目了。但是现在看来,那个项目写的不是很好,虽然是使用 Swift 来开发的,但是并没有按照 Swift 的标准来写 Swift 的项目,反而是以 OC 的习惯来写 Swift,也就是还是按照面向对象的思想来写 Swift,虽然也能写出可以运行的项目,但是面向对象的思想就和 Swift 的编程思想还是有本质的区别的。这里我不想着重介绍关于 OC 这门编程语言,毕竟我们现在是用 Swift 来开发的,但是有些东西还是要说明一下,首先面向对象编程的特征是 class,继承,封装和多态,其实 OC 还不能说是一门纯面向对象的的语言,只能说 OC 是 C 语言的超集,或者说是 C 语言的扩展,在 C 语言的基础上增加了面向对象的思想。但是在 Swift 里就不一样了,Swift 里 class 并不是最重要的。

我前面说了 Swift 是面向协议的编程,那么究竟什么是面向协议编程呢?

要回答这个问题,我们可以参考一下刚刚提到的面向对象编程,在面向对象编程里,是从一个 class 开始的,那要是照这样说,在面向协议编程里就是从一个 protocol 了吗?这样解释对不对呢?我们可以在刚刚提到视频里找找答案,如果看过上面的视频,你会发现在上面的视频中 Apple 自己都说:

"从一个 protocol 开始,别从 class 开始。" ——Dave Abrahams: 毁你三观教授

protocol 就是协议的意思。当然,可以从protocol 开始,但是从 protocol 开始了之后,该怎么做呢?

是的,这也是我们该思考的问题,我这里不会太着重去介绍 Swift 的基础,因为我默认看我视频的同学都已经掌握了 Swift 的基础了,所以关于 protocol 的概念我也不在详细介绍了,回到我们刚才的问题,现在我们已经有了 protocol,接下来我们要做的就是使用非常强大的 extension 了,额…,关于 extension 的概念我也不再详细介绍了,如果感觉基础不好的同学可以先去看一下基础,然后再来看我的视频吧,关于 extension,可以为现有的 class,struct,enum,protocol 添加新功能,注意刚刚我提到了 protocol,所以我们先现在可以在 protocol 的extension 里添加任何你需要添加的东西了。

那好,功能也添加了,那怎么该怎么使用这个 protocol 呢?

这也是个问题,让我们再分析一下,protocol 不同于 class 或者 struct,因为后两者可以各自调用它们的类型方法或者实例方法,但是 protocol 却不能直接使用,也不能实例化,既然都不行,那该怎么做啊?别着急,既然不能直接用,那我们就要考虑用上面提到的 class 或者 struct 了,那我们该用哪个呢?我们先来看一张图:

image

这张图是我在网上找到的一篇文章中的截图,下面是文章地址: 不要用子类!Swift的核心是面向协议 ,虽然这篇文章是2015年的文章了,不过还是推荐大家看一下。在上面的图中,可以看出在 Swift 的标准库中,仅有 4 个class,其余下的有 87 个 struct 和 8 个 enum 的实例共同构建了 Swift 功能的核心。如今已经过去两年,我想 struct 的数量应该更多了。既然 Swift 里用了这么多 struct,为什么我们不试试用 struct 呢?

我们前面也说过了 class 是面向对象里的东西,那我们试试用 struct,现在可以新建一个 struct,然后让它遵守我们的 protocol 就可以了,之后就可以实例化一个 struct,接着就可以用 struct 调用 protocol 里的方法或者属性了。

听上去还不是错的,但是总感觉是不是有点太麻烦了,要是按照上面说的,我们直接创建一个 struct 不就完了嘛,还要 protocol 干什么,这么说听上去也没有问题,当然在开发中也是可以的.

但是我们还要考虑一个问题,在实际开发中我们是不是只有 struct 呢?

当然不是,因为我们还要和 cocoa 框架打交道,说到 cocoa 框架,我们还要提一下 UIKit 这个框架,这是 iOS 开发中一个十分重要的框架,但是由于历史关系,为了兼容 OC,UIKit 里的类都是继承自 NSObject 的,也就是说都是 class 类型的,比如在开发中有几十个控制器都继承自某个自定义的基类,就会把基类的所有的方法也继承下来,但是这些方法对每一个子类都有用吗?答案肯定是否定的。所以,既然子类不需要,何必要继承父类的方法呢?自己的方法应该由自己决定才对的,而现在是基类帮着子类决定了它的方法。

所以这样就引出了 protocol,让自己的类实现自己所要遵守的 protocol,这里我说的并不是某一个 class,我这里指的是有那么几个 class 都要实现功能的时候,选择用 protocol 是个不错的选择,而且还可以把几个方法抽象成一个方法,需要的 class 只需要遵守这个 protocol 就可以了。这样解释可能不太清楚,我举一个栗子。

当我自定义 UIView 的时候,我想让 view 从 xib 加载,那么我就需要在每个类里都写一个从 xib 加载的类方法,如下代码:

static func classMethodCreateView() -> MyCustomView {
    return Bundle.main.loadNibNamed("\(self)", owner: nil, options: nil)?.last as! MyCustomView
}

这样在每个代码都写一,很是麻烦,有什么方法可以简单一点吗?方法当然是有的,可以做一下优化,如下代码:

protocol LoadNibProtocol {}
extension LoadNibProtocol where Self: UIView {
    /// 提供加载 Xib 方法
    static func loadViewFromNib(name: String? = nil) -> Self {
        return Bundle.main.loadNibNamed(name ?? "(self)", owner: nil, options: nil)?.last as! Self
    }
}

接下来让需要从 xib 加载的 view 遵守 LoadNibProtocol 协议就可以了,是不是简单了许多呢?上面只是 protocol 的一个简单应用,在后面的项目中,我会介绍其他用法,这里就不再过多说明了,关于协议暂时先介绍这么多。下面还有一个问题,需要思考一下,就是 Swift 里既然有 class 和 struct,那么他们的区别是什么呢?

- 我想大多数人的第一反应应该是 struct 是值类型 class 是引用类型,也就是说 struct 的实例在被赋予变量或者常量或者被函数调用时都会被复制,但是 class 的实例会被引用,引用的就是已经存在的实例本身而不是复制。还可以这样理解 struct 的复制相当于在内存上又开辟了一块内存空间,和之前的 struct 没有关系了,我个人感觉也可以理解成深拷贝,而 class 则是创建一个指针,指向的还是原来的内存地址,可以理解成浅拷贝。
- class 可以继承,struct 不能继承,某些需要继承的地方还是需要用 class,不能用 struct。
- struct 类型方法要加 static修饰,class类型方法要加 class 修饰。
- struct 有默认的初始化方法,class 需要指定变量的初始值。

下面代码关于 class 和 struct 的在初始化的时候的一些区别。

struct MyStruct1 {
    var text: String
    var tip: Int
}

struct MyStruct2 {
    var text: String = "MyStruct2"
    var tip: Int = 2
}

struct MyStruct3 {
    var text: String
    var tip: Int
    init(text: String, tip: Int) {
        self.text = text
        self.tip = tip
    }
}

class MyClass1 {
    var text: String = "MyClass1"
    var tip: Int = 11
}

class MyClass2 {
    var text: String
    var tip: Int
    init() {
        text = "MyClass2"
        tip = 22
    }
}

class MyClass3 {
    var text: String
    var tip: Int
    init(text: String, tip: Int) {
        self.text = text
        self.tip = tip
    }
}                                                                        
let myStruct1 = MyStruct1(text: "MyStruct1", tip: 1)
let myStruct2 = MyStruct2()
let myStruct3 = MyStruct3(text: "MyStruct3", tip: 3)
let myClass1 = MyClass1()
let myClass2 = MyClass2()
let myClass3 = MyClass3(text: "MyClass3", tip: 33)

还有一点,就是关于 struct 和 class 的性能差异,可以阅读下面的文章:理解Swift中struct和class在不同情况下性能的差异,文章介绍的很详细,我这里也不再详细介绍了。 上面是我对 struct 和 class 做的简单说明,以及 Swift 面向协议编程的简单说明,如果还觉得意犹未尽,或者想了解更多内容,请自行去网上找找相关文章。 说了这么多,最后还是希望你们能明白 Swift 是面向协议的编程, 在开发过程中请多使用 struct 和 protocol,当你没有选择的时候再使用 class。

新增一篇参考文章:面向协议的 MVVM 架构介绍。这篇文章也比较早了。

下面我们就继续写代码吧。

首先新建两个 Swift 文件,一个命名为 MyCellModel.Swift,作为我的界面 cell 的模型。

另一个命名为 NetworkTool.Swift,作为网络请求的相关文件。

然后在 Podfile 添加我们需要的第三方框架,分别是 AlamofireSwiftyJSONHandyJSON
如下代码:

target 'News'  do
use_frameworks!
    pod 'Alamofire', '~> 4.5.0'  # 数据请求 https://github.com/Alamofire/Alamofire,同 AFNetworking
    pod 'HandyJSON', '~> 1.7.2'         # JSON序列化/反序列化库 https://github.com/alibaba/HandyJSON/
    pod 'SwiftyJSON'                    # json 解析 https://github.com/SwiftyJSON/
end

默认生成的测试 target 先不需要,可以删掉。关于上面的第三方框架可以去 github 看一下他们的介绍和用法,我这里就不详细说明了,看我是怎么写的就可以了,跟着我写,写着写着就知道怎么用了。

让我们来思考一个问题,就是 Swift 的核心是什么?

不知道大家有没有看过 WWDC 2015 的视频,其中有一个编号为 408 的视频解释了这个问题,下面是视频链接:Protocol-Oriented Programming in Swift

视频中介绍了从 OOP(面向对象编程) 到 POP(面向协议编程)的转变过程。

Swift is a Protocol-Oriented Programming Language

Swift 是一门面向协议 (POP) 开发的语言

我说一下我的体会吧,我刚开始做 iOS 开发的时候使用 OC 来开发的,后来学习了 Swift,当时也是有一搭没一搭的学,了解了一下 Swift 的基本语法,感觉还是很简单的,因为当时 Swift 也是刚出来,很不稳定,1.0 到 2.0,甚至都不兼容,所以也就没有选择使用 Swift 来开发。

直到去年,也就是16 年,在 2.2 版本出来之后,感觉 Swift 还算比较稳定了,才决定使用 Swift 来开发一个简单的项目,当时决定模仿一个 app 来练手,也就是现在在我的 github 上的那个项目了。但是现在看来,那个项目写的不是很好,虽然是使用 Swift 来开发的,但是并没有按照 Swift 的标准来写 Swift 的项目,反而是以 OC 的习惯来写 Swift,也就是还是按照面向对象的思想来写 Swift,虽然也能写出可以运行的项目,但是面向对象的思想就和 Swift 的编程思想还是有本质的区别的。这里我不想着重介绍关于 OC 这门编程语言,毕竟我们现在是用 Swift 来开发的,但是有些东西还是要说明一下,首先面向对象编程的特征是 class,继承,封装和多态,其实 OC 还不能说是一门纯面向对象的的语言,只能说 OC 是 C 语言的超集,或者说是 C 语言的扩展,在 C 语言的基础上增加了面向对象的思想。但是在 Swift 里就不一样了,Swift 里 class 并不是最重要的。

我前面说了 Swift 是面向协议的编程,那么究竟什么是面向协议编程呢?

要回答这个问题,我们可以参考一下刚刚提到的面向对象编程,在面向对象编程里,是从一个 class 开始的,那要是照这样说,在面向协议编程里就是从一个 protocol 了吗?这样解释对不对呢?我们可以在刚刚提到视频里找找答案,如果看过上面的视频,你会发现在上面的视频中 Apple 自己都说:

"从一个 protocol 开始,别从 class 开始。" ——Dave Abrahams: 毁你三观教授

protocol 就是协议的意思。当然,可以从protocol 开始,但是从 protocol 开始了之后,该怎么做呢?

是的,这也是我们该思考的问题,我这里不会太着重去介绍 Swift 的基础,因为我默认看我视频的同学都已经掌握了 Swift 的基础了,所以关于 protocol 的概念我也不在详细介绍了,回到我们刚才的问题,现在我们已经有了 protocol,接下来我们要做的就是使用非常强大的 extension 了,额…,关于 extension 的概念我也不再详细介绍了,如果感觉基础不好的同学可以先去看一下基础,然后再来看我的视频吧,关于 extension,可以为现有的 class,struct,enum,protocol 添加新功能,注意刚刚我提到了 protocol,所以我们先现在可以在 protocol 的extension 里添加任何你需要添加的东西了。

那好,功能也添加了,那怎么该怎么使用这个 protocol 呢?

这也是个问题,让我们再分析一下,protocol 不同于 class 或者 struct,因为后两者可以各自调用它们的类型方法或者实例方法,但是 protocol 却不能直接使用,也不能实例化,既然都不行,那该怎么做啊?别着急,既然不能直接用,那我们就要考虑用上面提到的 class 或者 struct 了,那我们该用哪个呢?我们先来看一张图:

image

这张图是我在网上找到的一篇文章中的截图,下面是文章地址: 不要用子类!Swift的核心是面向协议 ,虽然这篇文章是2015年的文章了,不过还是推荐大家看一下。在上面的图中,可以看出在 Swift 的标准库中,仅有 4 个class,其余下的有 87 个 struct 和 8 个 enum 的实例共同构建了 Swift 功能的核心。如今已经过去两年,我想 struct 的数量应该更多了。既然 Swift 里用了这么多 struct,为什么我们不试试用 struct 呢?

我们前面也说过了 class 是面向对象里的东西,那我们试试用 struct,现在可以新建一个 struct,然后让它遵守我们的 protocol 就可以了,之后就可以实例化一个 struct,接着就可以用 struct 调用 protocol 里的方法或者属性了。

听上去还不是错的,但是总感觉是不是有点太麻烦了,要是按照上面说的,我们直接创建一个 struct 不就完了嘛,还要 protocol 干什么,这么说听上去也没有问题,当然在开发中也是可以的.

但是我们还要考虑一个问题,在实际开发中我们是不是只有 struct 呢?

当然不是,因为我们还要和 cocoa 框架打交道,说到 cocoa 框架,我们还要提一下 UIKit 这个框架,这是 iOS 开发中一个十分重要的框架,但是由于历史关系,为了兼容 OC,UIKit 里的类都是继承自 NSObject 的,也就是说都是 class 类型的,比如在开发中有几十个控制器都继承自某个自定义的基类,就会把基类的所有的方法也继承下来,但是这些方法对每一个子类都有用吗?答案肯定是否定的。所以,既然子类不需要,何必要继承父类的方法呢?自己的方法应该由自己决定才对的,而现在是基类帮着子类决定了它的方法。

所以这样就引出了 protocol,让自己的类实现自己所要遵守的 protocol,这里我说的并不是某一个 class,我这里指的是有那么几个 class 都要实现功能的时候,选择用 protocol 是个不错的选择,而且还可以把几个方法抽象成一个方法,需要的 class 只需要遵守这个 protocol 就可以了。这样解释可能不太清楚,我举一个栗子。

当我自定义 UIView 的时候,我想让 view 从 xib 加载,那么我就需要在每个类里都写一个从 xib 加载的类方法,如下代码:

static func classMethodCreateView() -> MyCustomView {
    return Bundle.main.loadNibNamed("\(self)", owner: nil, options: nil)?.last as! MyCustomView
}

这样在每个代码都写一,很是麻烦,有什么方法可以简单一点吗?方法当然是有的,可以做一下优化,如下代码:

protocol LoadNibProtocol {}
extension LoadNibProtocol where Self: UIView {
    /// 提供加载 Xib 方法
    static func loadViewFromNib(name: String? = nil) -> Self {
        return Bundle.main.loadNibNamed(name ?? "(self)", owner: nil, options: nil)?.last as! Self
    }
}

接下来让需要从 xib 加载的 view 遵守 LoadNibProtocol 协议就可以了,是不是简单了许多呢?上面只是 protocol 的一个简单应用,在后面的项目中,我会介绍其他用法,这里就不再过多说明了,关于协议暂时先介绍这么多。下面还有一个问题,需要思考一下,就是 Swift 里既然有 class 和 struct,那么他们的区别是什么呢?

- 我想大多数人的第一反应应该是 struct 是值类型 class 是引用类型,也就是说 struct 的实例在被赋予变量或者常量或者被函数调用时都会被复制,但是 class 的实例会被引用,引用的就是已经存在的实例本身而不是复制。还可以这样理解 struct 的复制相当于在内存上又开辟了一块内存空间,和之前的 struct 没有关系了,我个人感觉也可以理解成深拷贝,而 class 则是创建一个指针,指向的还是原来的内存地址,可以理解成浅拷贝。
- class 可以继承,struct 不能继承,某些需要继承的地方还是需要用 class,不能用 struct。
- struct 类型方法要加 static修饰,class类型方法要加 class 修饰。
- struct 有默认的初始化方法,class 需要指定变量的初始值。

下面代码关于 class 和 struct 的在初始化的时候的一些区别。

struct MyStruct1 {
    var text: String
    var tip: Int
}

struct MyStruct2 {
    var text: String = "MyStruct2"
    var tip: Int = 2
}

struct MyStruct3 {
    var text: String
    var tip: Int
    init(text: String, tip: Int) {
        self.text = text
        self.tip = tip
    }
}

class MyClass1 {
    var text: String = "MyClass1"
    var tip: Int = 11
}

class MyClass2 {
    var text: String
    var tip: Int
    init() {
        text = "MyClass2"
        tip = 22
    }
}

class MyClass3 {
    var text: String
    var tip: Int
    init(text: String, tip: Int) {
        self.text = text
        self.tip = tip
    }
}                                                                        
let myStruct1 = MyStruct1(text: "MyStruct1", tip: 1)
let myStruct2 = MyStruct2()
let myStruct3 = MyStruct3(text: "MyStruct3", tip: 3)
let myClass1 = MyClass1()
let myClass2 = MyClass2()
let myClass3 = MyClass3(text: "MyClass3", tip: 33)

还有一点,就是关于 struct 和 class 的性能差异,可以阅读下面的文章:理解Swift中struct和class在不同情况下性能的差异,文章介绍的很详细,我这里也不再详细介绍了。 上面是我对 struct 和 class 做的简单说明,以及 Swift 面向协议编程的简单说明,如果还觉得意犹未尽,或者想了解更多内容,请自行去网上找找相关文章。 说了这么多,最后还是希望你们能明白 Swift 是面向协议的编程, 在开发过程中请多使用 struct 和 protocol,当你没有选择的时候再使用 class。

新增一篇参考文章:面向协议的 MVVM 架构介绍。这篇文章也比较早了。

下面我们就继续写代码吧。

首先新建两个 Swift 文件,一个命名为 MyCellModel.Swift,作为我的界面 cell 的模型。

另一个命名为 NetworkTool.Swift,作为网络请求的相关文件。

然后在 Podfile 添加我们需要的第三方框架,分别是 AlamofireSwiftyJSONHandyJSON
如下代码:

target 'News'  do
use_frameworks!
    pod 'Alamofire', '~> 4.5.0'  # 数据请求 https://github.com/Alamofire/Alamofire,同 AFNetworking
    pod 'HandyJSON', '~> 1.7.2'         # JSON序列化/反序列化库 https://github.com/alibaba/HandyJSON/
    pod 'SwiftyJSON'                    # json 解析 https://github.com/SwiftyJSON/
end

默认生成的测试 target 先不需要,可以删掉。关于上面的第三方框架可以去 github 看一下他们的介绍和用法,我这里就不详细说明了,看我是怎么写的就可以了,跟着我写,写着写着就知道怎么用了。

212 - Any 和 AnyObject 的分析

我下面的代码中声明了一个 struct,一个 class,一个闭包,一个无返回值的方法,一个有返回值的方法以及一个枚举:

struct MyStruct {
    var text: String = "MyStruct"
    var tip: Int = 1
}

class MyClass {
    var text: String = "MyClass"
    var tip: Int = 11
}

/// 闭包
typealias closure = (Int, Int) -> ()
/// 无返回值的方法
func testNoReturn() {
    print("testNoReturn")
}
/// 有返回值的方法
func testReturn(a: Int) -> Int {
    return a
}
/// 枚举
enum TestEnum: String {
    case language1 = "Swfift"
    case language2 = "Objective-C"
}
let myStruct = MyStruct()
let myClass = MyClass()

如下代码:


// let array7: [AnyObject] = [1, "2", myStruct1, myClass1, TestEnum.language1]
let array7: [AnyObject] = [1 as AnyObject, "2" as AnyObject, myStruct1 as AnyObject, myClass1, TestEnum.language1 as AnyObject]
// print [1, "2", {NSObject}, {text "MyClass", tip 11}, {NSObject}]
// 上面的打印是在 Playground 中的结果。
let array8: [Any] = [1, "2", myStruct1, myClass1, testNoReturn,testReturn(a: 1), closure.self, TestEnum.language1]
// print [1, "2", {text "MyStruct", tip 1}, {text "MyClass", tip 11}, () -> (), 1, ((Int, Int) -> ()).Type, language1]
// -----------------print array7-------------------------
for item in array7 {
    if item is Int {
        print("\(item) is Int!")
    } else if item is NSNumber {
        print("\(item) is NSNumber!")
    }
    if item is NSString {
        print("\(item) is NSString!")
    }
    if item is NSString {
        print("\(item) is NSString!")
    }
}
// 1 is Int!
// 1 is NSNumber!
// 2 is String!
// 2 is NSString!
// 上面的代码打印整数,并且打印了两行,说明 1 既是 Int 类型的,又是 NSNumber 类型的。
// 上面的代码打印字符串,并且打印了两行,说明字符串既是 String 类型的,又是 NSString 类型的。
// 那么这个 AnyObject 究竟是什么呢?
// AnyObject 是一个成员为空的协议,所有 class 都隐式地实现了这个协议,
// 这也限制了 AnyObject 是只适用于 class 类型的原因,相当于 OC 中的 id 类型,
// Swift 为了与 Cocoa 架构进行协作开发,就将原来的 id 用 AnyObject 来进行替代。
// 也就是说在声明的时候,仅仅是把字符串强转成了 AnyObject 类型,所以在判断的时候,编译器并不知道具体的声明类型,所以就会打印两种结果,
// 但是在 Swift 中 String 是结构体类型的,也就是说,当转成 AnyObject 类型之后
// 系统默认把 String 转成了 NSObject 类型,这就说明 Swift 和 Objective-C 存在相互转化
// 并且这种转化是自动的,并且实现了 Swift 和 Objective-C 的无缝桥接。
// 上面的解释只是我个人的理解,如有不对的地方,还请指出。
// -----------------print array8-------------------------
for item in array8 {
    if item is Int {
        print("\(item) is Int!")
    }
    if item is String {
        print("\(item) is String!")
    }
    if item is NSString {
        print("\(item) is NSString!")
    }
}
// 1 is Int!
// 1 is NSNumber!
// 2 is String!
// 2 is NSString!
// Any 是一个空协议集合的别名,它表示没有实现任何协议,可以表示任意类型,包括 class, struct 和 enum 在内的所有类型,甚至包括方法 (func) 类型。

AnyClass 是 AnyObject.Type 的别名而已。

anyanyboject

参考文章

Any vs. AnyObject in Swift 3.0

下面这篇文章是比较老的文章,可以参考,但是 Swift3 中很多地方不如文章所述。

ANY 和 ANYOBJECT

NYCLASS,元类型和 .SELF

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,968评论 3 119
  • 我不喜欢你了,不用再每天想你 没事去看看风景,一个人走走 偶尔和朋友相约爬山打球 有大把大把的时间 用来发呆 不用...
    眼镜框阅读 289评论 0 0
  • 蛰伏了一个冬天,终于迎来了阳光明媚的春天。出去走走的欲望像惊蛰时节小虫子一样在心头蠢蠢欲动。听说黄浦江两岸沿江步行...
    海上一书生阅读 1,211评论 3 3
  • 清明时节雨纷纷, 路上行人欲断魂。 借问酒家何处有, 牧童遥指杏花村。 今天的天气真...
    曹政妈妈阅读 248评论 0 0
  • 生活中的目标不仅仅只是那种宏伟的,一辈子只有一次的,梦工厂式的目标。 有些时候,我们的确会分心会忘记那些虽然小一点...
    耳纹AI一侯公子阅读 3,256评论 0 3