Swift 从零开始 15_自定义tableViewCell及自定义cell删除按钮

MARK: 此篇介绍的是用纯代码自定义封装cell,xib及sb后续更新.

1.创建自定义cell文件,继承UITableViewCell

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
46
47
48
49
50
51
52
53
54
55
56
57
58
import UIKit
class RecordTableViewCell: UITableViewCell {
var deleteBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
//设置cell的分割线顶靠屏幕边缘(自定义方法)
setSeparatorByEdge()
}
//如果子控件的frame会根据内容的变化而变化(即每个cell的子控件不是完全一样的),
考虑到cell会重用,这种时候就不在这里直接写死子控件的frame,
而是重写layouSubviews方法,动态的根据计算子控件的frame.
override func layoutSubviews() {
super.layoutSubviews()
setUpViews()
}
//重写initWithStyle:方法,在里面创建所有可能出现的子控件
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
if !self.isEqual(nil) {
deleteBtn = UIButton()
self.contentView.addSubview(deleteBtn)
}
}
func setUpViews() {
deleteBtn("删除cell", forState: UIControlState.Normal)
deleteBtn.setTitleColor(UIColor.hexStringToColor("#EA5413"), forState: UIControlState.Normal)
deleteBtn.titleLabel?.font = UIFont.systemFontOfSize(12)
deleteBtn.layer.borderWidth = 1
deleteBtn.layer.borderColor = UIColor.hexStringToColor("ea5413").CGColor
deleteBtn.snp_makeConstraints(closure: { (make) in
make.centerY.equalTo(self.contentView.snp_centerY)
make.right.equalTo(self.contentView.snp_right).offset(-10)
make.width.equalTo(self.contentView.snp_width).multipliedBy(0.155)
make.height.equalTo(self.contentView.snp_height).multipliedBy(0.455)
})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

2.在控制器中

(1) 注册cell

1
tableView.registerClass(RecordTableViewCell.classForCoder(), forCellReuseIdentifier: "cell")

(2) cellForRowAtIndex方法中创建自定义cell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: RecordTableViewCell = self.tableView.dequeueReusableCellWithIdentifier(identifier) as! RecordTableViewCell
cell.backgroundColor = UIColor.clearColor()
cell.deleteBtn.tag = indexPath.row
cell.deleteBtn.addTarget(self, action: #selector(RecordViewController.deleteBtnClick(_:)), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
//deleteBtn响应方法
func deleteBtnClick(sender: UIButton) {
dataSourceArr.removeAtIndex(sender.tag)
tableView.reloadData()
}

总结:

代码自定义cell要注意cell下子控件的创建及frame约束等.这里只是提供一个思路,抛砖引玉,具体的需求具体对待.

Swift 从零开始 14_构造器的继承和重写

1.构造器的继承:

Swift的子类不会自动继承父类的构造器, 若继承, 则满足如下规则:

1.如果子类没有提供任何指定构造器, 那么它将自动继承父类的所有指定构造器

2.如果子类实现了父类所有的指定构造器, 无论如何实现的, 都将自动继承父类的所有便利构造器

2.构造器的重写:

1.子类构造器重写了父类的指定构造器, 必须添加override修饰符

2.子类中定义的构造器只是和父类中便利构造器的形参列表, 外部形参名相同, 不算重写

总结:

1.如果一个子类没有定义任何构造器, 那么它将自动继承父类中的所有构造器

2.如果一个子类重写父类的所有指定构造器, 那么它将自动继承父类中的所有便利构造器

3.如果一个子类中任意的构造器和父类的便利构造器一模一样, 不算重写, 创建对象的时候也只会显示子类定义的构造器

示例代码
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
46
47
48
49
50
51
52
53
54
55
class Person {
var name: String!
var weight: Double
init(name: String) {
self.name = name
self.weight = 0.0
}
// 定义指定构造器
init(name: String, weight: Double) {
self.name = name
self.weight = weight
}
// 定义便利构造器(使用convenience修饰)
convenience init(n name: String, w weight: Double) {
// 便利构造器必须调用同类中的指定构造器
self.init(name: name, weight: weight)
}
convenience init(showStr: String) {
self.init(name: "", weight: 0.0)
print(showStr)
}
}
class Man: Person {
var sex: String = "男"
override init(name: String) {
// 子类的指定构造器中必须调用父类的指定构造器
super.init(name: name)
self.name = name
self.weight = 0.0
}
override init(name: String, weight: Double) {
super.init(name: name, weight: weight)
self.name = name
self.weight = weight
}
// 定义指定构造器与父类的便利构造器一样, 这里不算重写
convenience init(showStr: String) {
self.init(name: "", weight: 0.0)
print(showStr)
}
}
var man = Man(name: "Rinpe", weight: 62.0)
var man1 = Man(showStr: "HelloWorld")

Swift 从零开始 13_自定义init方法

自定义init初始化构造函数实现参数传递

对于UIKit框架类,不能像下面一样简单的重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import UIKit
class WebViewController: UIViewController {
var imageUrl: String!
init(imageUrl: String) {
self.imageUrl = imageUrl
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
}
}
这样编译器会报如下错误:
1
Super.init isn‘t called before returning from initializer
原因分析:

1.在 Swift 中, 类的初始化器有两种, 分别是Designated Initializer(指定初始化器)和Convenience Initializer(便利初始化器)

2.如果子类没有定义任何的指定初始化器, 那么会默认继承所有来自父类的指定初始化器。

3.如果子类提供了所有父类指定初始化器的实现, 那么自动继承父类的便利初始化器

4.如果子类只实现部分父类初始化器,那么父类其他的指定初始化器和便利初始化器都不会继承。

5.子类的指定初始化器必须要调用父类合适的指定初始化器。

错误分析:
指定构造器必须调用它最近父类的指定构造器。

正确写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import UIKit
class WebViewController: UIViewController {
var imageUrl: String!
init(imageUrl: String) {
super.init(nibName: nil, bundle: nil)
self.imageUrl = imageUrl
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
}
}

Swift 从零开始 12_Swift和H5的交互

本文主要讲述项目中用到的一套交互机制,方法很多,欢迎交流

首先,用的WkWebView

1
2
3
4
5
6
7
8
9
10
11
12
13
extension CBWebView: WKScriptMessageHandler {
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
guard let _handleViewController = handleViewController else { return }
//接受h5那边发过来的消息,消息存放在message的body中
guard let cmd = Cmd.create(with: message.body as! String) else { return }
CMDManager.sharedInstance.performCmd(_handleViewController, cmd: cmd)
}
}

cmd是自定义的存放消息的类

1
2
3
4
5
6
7
8
9
10
11
12
class Cmd {
class func create(with text: String) -> Cmd? {
if let json = JSON.createFromString(text) {
let cmd = Cmd()
cmd.action = json["cmd"].stringValue
cmd.params = json["data"]
return cmd
}
}

CMDManager是oc方法的管理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CMDManager: NSObject {
var currentCmdAction: String!
var currentCmdParams: JSON!
weak var currentViewController: UIViewController!
func performCmd(handleViewController: UIViewController, cmd: Cmd) {
currentCmdAction = cmd.action
currentViewController = handleViewController
currentCmdParams = cmd.params
if respondsToSelector(Selector(currentCmdAction)) {
performSelector(Selector(currentCmdAction))
DDLogVerbose("cmd: \(cmd.action), params: \(self.currentCmdParams.rawString() ?? "")")
} else {
DDLogWarn("\(cmd.action) has not be implement")
}
}
}
通过上面三段代码就可以知道:
h5发送消息到WKScriptMessageHandler的Message中,然后将消息体(message.body存到到cmd中,对应的action和params).然后再调用CMDManager的performCmd方法,将cmd传过来,然后通过respondsToSelector 方法判断该类中是否实现了对面方法(cmd.action),实现了就可以调用,没有实现则提示.

比如h5加载时,调用oc的hide_loading方法

通过h5的文档,知道方法名及参数,然后自己把方法实现即可.

这里是h5调用oc的方法,并且没有回调
1
2
3
4
5
//隐藏加载页面
func hide_loading () {
//调用CBWebView 的 hideLoadView()方法
(currentViewController as? FruitViewController )?.webView.hideLoadView()
}
下面说说h5调用oc方法之后,还要回调h5方法的例子
1
2
3
4
5
6
7
8
9
10
11
//获取app的数据
func get_app_data() {
//callbackName和callbackID是参数,通过cmd.params传过来的
let callbackName = self.currentCmdParams["callbackName"].stringValue
let callbackID = self.currentCmdParams["callbackID"].intValue
let loginInfo : LoginInfo
loginInfo = InfoManager.sharedInstance.readLoginInfo()!
//回调h5方法
(self.currentViewController as? FruitViewController)?.webView.getAppData(callbackName, callbackID: callbackID, loginInfo: loginInfo)
}

然后在CBWebView的Extension(新建一个类叫 WebViewExtension)中实现oc调用js的代码

1
2
3
4
5
//这是WkWebView种调用js方法的方法
private func perfromJs(method:String,param1:String,param2:String, completionHandler: ((Any?, NSError?) -> Void)?)
{
self.evaluateJavaScript("\(method)('\(param1)','\(param2)')", completionHandler: completionHandler)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//回调方法
//回调参数(callbackID,{uid:”10000”,”auth”:” asdasdasd213”})
func getAppData(callbackName: String, callbackID: Int,loginInfo: LoginInfo) {
var str1 = ""
var str2 = ""
str1 = "\(callbackID)"
str2 = "{\"uid\":\"\(loginInfo.uid)\",\"auth\":\"\(loginInfo.auth)\"}"
print("str------\(str1 + str2)")
perfromJs(callbackName, param1:str1, param2:str2) { (obj, err) in
print(err)
}
}
至此,swift中和h5的互调完成,整个逻辑其实很简单,主意和h5开发人员沟通好就行!

IOS开发实用小技巧(持续更新)

1.设置电池栏
1
2
3
4
5
6
7
didFinishLaunchingWithOptions:
//MARK :-设置电池栏(状态栏)
//设置颜色(亮色/白色)
application.statusBarStyle = UIStatusBarStyleLightContent;
//启动时隐藏
application.statusBarHidden = NO;
iOS7开始状态栏的设置权交给了当前界面

隐藏状态栏

1
2
3
- (BOOL)prefersStatusBarHidden {
return YES;
}

修改状态栏的颜色

1
2
3
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}

Swift 从零开始 11_多文件下载及存到沙盒

本文主要说说,App第一次启动时,去下载一些数据保存到沙盒该如何实现.
这里用的是ios7后新出的NSUrlSession
先看代码
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
46
47
48
49
50
func downloadGameAudioWithUrl(urlStr: String) {
// 1. URL
let url = NSURL(string: urlStr)
let request = NSURLRequest(URL: url!)
// 2. 自定义session,需要设置代理
// 选择默认配置信息
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let downloadSession = NSURLSession(configuration:
config, delegate: self, delegateQueue: nil)
// 3. 发起下载任务(在这里回调里面,就可以对下载好的文件进行处理)
let downloadTask = downloadSession.downloadTaskWithRequest(request) { (location, response, error) in
// 下载的位置,沙盒中tmp目录中的临时文件,会被及时删除
print("文件下载路径: \(location.path)")
/*
document 备份,下载的文件不能放在此文件夹中
cache 缓存的,不备份,重新启动不会被清空,如果缓存内容过多,可以考虑新建一条线程检查缓存目录中的文件大小,自动清理缓存,给用户节省控件
tmp 临时,不备份,不缓存,重新启动iPhone,会自动清空
*/
//要把下载下来的文件,从tmp文件夹拷贝到cache文件夹下
//获取cachew文件路径
let path = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).lastEle
//获取下载文件的名字保存以便下面的路径拼接
self.downloadFileName = (url?.lastPathComponent)!
// 拼接文件路径
let filePath = path.stringByAppendingString("/" + self.downloadFileName)
print("文件保存路径: \(filePath)")
//创建文件管理器
//这里,copyItemAtPath方法需要做异常处理
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.copyItemAtPath(location!.path!, toPath: filePath)
} catch let error as NSError {
// 发生了错误
print(error.localizedDescription)
}
}
// 4. 启动下载任务
downloadTask.resume()
}
}
后记:
这里只提到了我项目中用到的方法,很有很多方法很多思路可以实现,欢迎大家来讨论!

Swift 从零开始 10_判断App是否第一次打开及判断是否是新版本

1.判断App是否第一次打开

1
2
3
4
5
6
7
8
9
10
11
//App第一次启动调用方法 :didFinishLaunchingWithOptions:
if (!NSUserDefaults.standardUserDefaults().boolForKey("firstLaunch")) {
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "firstLaunch")
print("App第一次打开")
}else{
print("App不是第一次打开")
}

2.判断App版本是否是新版本(OC)

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
#pragma mark - 判断是不是新版本,是否需要显示新特性
- (BOOL)isNewVision {
// MARK: - 目标:第一次启动这个应用时,显示新特性界面,以后就直接进入主程序!
// 版本号:两个版本号。Info.plist中有对应的版本号
// 1. 获取应用当前的版本号
// 获取Info.plist文件的所有信息
NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary;
// 获取当前版本号
NSString *currentBete = infoDict[@"CFBundleShortVersionString"];
// 2. 获取之前存储的版本号
// 获取偏好设置对象
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *oldBete = [defaults objectForKey:@"app_version"];
// 3. 比较新旧版本号
if ([currentBete isEqualToString:oldBete])
{
//如果新旧版本号相等,直接进入主程序
return NO;
}else {
//如果不相等
//获取偏好设置
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//存储
[defaults setObject:currentBete forKey:@"app_version"];
//立即同步
[defaults synchronize];
return YES;
}
didFinishLaunchingWithOptions:
//设置根控制器(DXGuideController:新特性)
if ([self isNewVision]) {
self.window.rootViewController = [[DXGuideController alloc] init];
}else {
self.window.rootViewController = tabBarVC;

Swift 从零开始 09_as,as!,as? 三种类型转换操作符使用详解

这里总结了下 as、as!、as? 这三种类型转换操作符的异同,以及各自的使用场景。

1.as使用场合

摘自中文api的话:仅当一个值的类型在运行时(runtime)和as模式右边的指定类型一致或者是该类型的子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成as模式左边指定的模式。

首先是运行时就不多说了,重要的是 as 应用条件有2种情况:

1
2
1,和 "as" 右边类型一致
2,是右边类型的子类(这种情况在Java里叫向上转型)

示例代码:

1
2
3
4
5
6
7
8
claa Animal {}
class Dog: Animal{}
let a = Animal()
a as Animal // a和右边Animal类型一致,编译成功
let d = Dog()
d as Animal // d是右边Animal的子类,编译成功

少数用法:

(1) 消除二义性,,数值类型转换

1
2
3
4
let num1 = 42 as CGFloat
let num2 = 42 as Int
let num3 = 42.5 as Int
let num4 = (42 / 2) as Double

(2) switch语句中进行模式匹配
如果不知道一个对象是什么类型,你可以通过switch语法检测它的类型,并且尝试在不同的情况下使用对应的类型进行相应的处理。

1
2
3
4
5
6
7
switch animal {
case let cat as Cat:
print("如果是Cat类型对象,则做相应处理")
case let dog as Dog:
print("如果是Dog类型对象,则做相应处理")
default: break
}

2.as!适用场合

上例中 第二种情况,左边是右边类型的子类,如果碰到 as 左边类型是右边类型的父类则会报错!(as不可以用于父类转子类,用java话说,不支持向下转型),由此可用as!(强转) ,如果转换失败会报 runtime 运行错误。

1
2
3
4
5
6
7
class Animal {}
class Dog: Animal {}
let a: Animal = Dog()
a as! Dog //编译成功
a as Dog //报错 -- 编译器也会提示你用as!, as!强转如果类型不符合会报错。

3.as?适用场合

as? 和 as! 操作符的转换规则完全一样。但 as? 如果转换不成功的时候便会返回一个 nil 对象。成功的话返回可选类型值(optional),需要我们拆包使用。
由于 as? 在转换失败的时候也不会出现错误,所以对于如果能确保100%会成功的转换则可使用 as!,否则使用 as?

1
2
3
4
5
6
7
let animal:Animal = Cat()
if let cat = animal as? Cat {
print("cat is not nil")
} else {
print("cat is nil")
}
1
2
3
4
5
6
7
class Animal {}
class Cat: Animal {}
class Dog: Animal {}
let animal: Animal = Cat()
animal as? Dog //取到的是nil
animal as! Dog //运行时报错

Swift 从零开始 08_Swift3.0必看:新的访问控制fileprivate和open

在swift 3中新增加了两种访问控制权限 fileprivate和 open。下面将对这两种新增访问控制做详细介绍。

fileprivate

在原有的swift中的 private其实并不是真正的私有,如果一个变量定义为private,在同一个文件中的其他类依然是可以访问到的。这个场景在使用extension的时候很明显。

1
2
3
4
5
6
7
8
9
class User {
private var name = "private"
}
extension User{
var accessPrivate: String {
return name
}
}

这样带来了两个问题:

1
2
1.当我们标记为private时,意为真的私有还是文件内可共享呢?
2.当我们如果意图为真正的私有时,必须保证这个类或者结构体在一个单独的文件里。否则可能同文件里其他的代码访问到。

由此,在swift 3中,新增加了一个 fileprivate来显式的表明,这个元素的访问权限为文件内私有。过去的private对应现在的fileprivate。现在的private则是真正的私有,离开了这个类或者结构体的作用域外面就无法访问。

open

open则是弥补public语义上的不足。
现在的pubic有两层含义:

1
2
这个元素可以在其他作用域被访问
这个元素可以在其他作用域被继承或者override

继承是一件危险的事情。尤其对于一个framework或者module的设计者而言。在自身的module内,类或者属性对于作者而言是清晰的,能否被继承或者override都是可控的。但是对于使用它的人,作者有时会希望传达出这个类或者属性不应该被继承或者修改。这个对应的就是 final。

final的问题在于在标记之后,在任何地方都不能override。而对于lib的设计者而言,希望得到的是在module内可以被override,在被import到其他地方后其他用户使用的时候不能被override。

这就是 open产生的初衷。通过open和public标记区别一个元素在其他module中是只能被访问还是可以被override。

下面是例子

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
/// ModuleA:
// 这个类在ModuleA的范围外是不能被继承的,只能被访问
public class NonSubclassableParentClass {
public func foo() {}
// 这是错误的写法,因为class已经不能被继承,
// 所以他的方法的访问权限不能大于类的访问权限
open func bar() {}
// final的含义保持不变
public final func baz() {}
}
// 在ModuleA的范围外可以被继承
open class SubclassableParentClass {
// 这个属性在ModuleA的范围外不能被override
public var size : Int
// 这个方法在ModuleA的范围外不能被override
public func foo() {}
// 这个方法在任何地方都可以被override
open func bar() {}
///final的含义保持不变
public final func baz() {}
}
/// final的含义保持不变
public final class FinalClass { }
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
/// ModuleB:
import ModuleA
// 这个写法是错误的,编译会失败
// 因为NonSubclassableParentClass类访问权限标记的是public,只能被访问不能被继承
class SubclassA : NonSubclassableParentClass { }
// 这样写法可以通过,因为SubclassableParentClass访问权限为 `open`.
class SubclassB : SubclassableParentClass {
// 这样写也会编译失败
// 因为这个方法在SubclassableParentClass 中的权限为public,不是`open'.
override func foo() { }
// 这个方法因为在SubclassableParentClass中标记为open,所以可以这样写
// 这里不需要再声明为open,因为这个类是internal的
override func bar() { }
}
open class SubclassC : SubclassableParentClass {
// 这种写法会编译失败,因为这个类已经标记为open
// 这个方法override是一个open的方法,则也需要表明访问权限
override func bar() { }
}
open class SubclassD : SubclassableParentClass {
// 正确的写法,方法也需要标记为open
open override func bar() { }
}
open class SubclassE : SubclassableParentClass {
// 也可以显式的指出这个方法不能在被override
public final override func bar() { }
}

总结

现在的访问权限则依次为:open,public,internal,fileprivate,private。
有的人会觉得访问权限选择的增加加大了语言的复杂度。但是如果我们思考swift语言的设计目标之一就是一门安全的语言(“Designed for Safety”)就能理解这次的改动。更加明确清晰的访问权限控制可以使程序员表达出更准确的意图,当然也迫使在编码时思考的更加深入。

Swift 从零开始 07_swift中的关键字说明(一)

1.let关键字

用let修饰的变量会是一个不可变的常量, 也就是说不可以对它进行修改, 但如果用let修饰的常量是一个类, 那么我们可以对其所在的属性进行修改, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class PersonInfo {
let name = "xiaoming"
var age = 18
var height = 170
var weight = 65
}
let personInfo = PersonInfo()
personInfo.age += 1
print("person age is \(personInfo.age).")
// 输出的结果为: person age is 19.

这里边的name就是不可变的, 如果非要去改变, Xcode会建议你把let修改成var.

PS: 如果这个时候, 再声明一个person常量去引用personInfo, 那么person的所指向的内存块是和personInfo相同的.

2.var关键字

用var关键字声明的变量将会是一个可变的变量, 在这里, 我们并不会用var去引用一个类, 也没有必要, 继续拿上面的例子来说:

1
2
3
4
5
6
7
8
9
10
11
12
class PersonInfo {
let name = "xiaoming"
var age = 18
var height = 170
var weight = 65
}
let personInfo = PersonInfo()
personInfo.age += 1
print("person age is \(personInfo.age).")

这里面的age就是属于var类型, 在初始化之后, 我们仍然可以给它进行修改.

PS: 如果这个时候, 我们用var修饰personMode变量去引用personInfo, 那么personMode和personInfo所指向的内存块并不同, personMode会将personInfo完整的拷贝一份, 然后放在另外一块内存块去管理.

3.class关键字

在Swift当中, 我们是使用Class关键字去声明一个类, 比如:

1
2
3
class PersonInfo {
// class body
}

4.struct关键字

在Swift中, 如果我们需要声明一个结构体, 我们就需要使用到struct关键字, 比如:

1
2
3
struct PersonInfo {
// struct body
}

5.enum关键字

而我们需要声明枚举的时候, 我们就使用enum关键字, 比如:

1
2
3
4
5
6
enum PersonMode {
case zhangsan
case lisi
case wangwu(String)
case luoliu(String, Double, Int)
}

在Swift当中的enum和Objective-C并不一样, Swift的enum不会被隐式赋值为0, 1, 里面的zhangsan就是这个枚举分支的完整值, 并且在Swift中, 我们可以给enum case声明各种类型, 比如里面的case luoliu一样.

当然, 我们也可以给枚举值默认声明一个值, 但在枚举名之前, 要声明是什么类型, 比如:

1
2
3
4
5
6
7
enum Numbers: Int {
case One = 1, Two, Three, Four
}
let number = Numbers.One.rawValue
print(number)

6.override关键字

在Swift中, 如果我们要重写某个方法, 或者某个属性的话, 我们需要在重写的变量前增加一个override关键字, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Car {
var speed: Int {
return 200
}
func carSpeed() {
print("Car max speed is \(speed)km 1 hours.")
}
}
class NewCar: Car {
override var speed: Int {
return 150
}
override func carSpeed() {
print("Car max speed is \(speed)km 1 hours.")
}
}

7.final关键字

上面我们介绍了override重写属性或者方法的关键字, 当然有重写, 肯定会有防止重写的关键字, 比如:

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
class Car {
var speed: Int {
return 200
}
final var carType: String {
return "SUV"
}
func carSpeed() {
print("Car max speed is \(speed)km 1 hours.")
}
}
class NewCar: Car {
override var speed: Int {
return 150
}
override func carSpeed() {
print("Car max speed is \(speed)km 1 hours.")
}
override var carType: String {
return "MPV"
}
}
// 这个时候, 下面这个override var carType: String{}属性就会报Var override a "final" var 的错误.

8.subscript关键字

所谓的下标, 其实就可以快捷方式的设置或者获取对应的属性, 而不需要调用对应的方法去获取或者存储, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
struct Animal {
let catType: String
subscript(animalName: String) -> String {
return "catType is \(animalName)."
}
}
let tiger = Animal(catType: "Tiger")
print(tiger)
// 输出的结果为: Animal(catType: "Tiger")

9.mutating关键字

作用:写在func前面,以便于让func可以修改struct和protocol的extension中的成员的值。 如果func前面不加mutating,struct和protocol的extension中的成员的值便被保护起来,不能修改.

mutating这个关键字指的是可变, 只能用在struct和enum当中,为的就是可以方便我们在特定环境下, 需要在struct中修改对应的属性值, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
struct Animal {
let catType: String
subscript(animalName: String) -> String {
return "catType is \(animalName)."
}
}
let tiger = Animal(catType: "Tiger")
print(tiger)
// 输出的结果为: Animal(catType: "Tiger")

10.static

用static修饰的变量或者方法, 就会变成静态变量和静态方法, 并且保证在对应的作用域当中只有一份, 同时也不需要依赖实例化, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animals {
static var catName: String!
func catTypeName() {
_ = "Tiger"
}
}
Animals.catName = "Tiger"
print(Animals.catName)
// 输出的结果为: Tiger
static关键字和class关键字的区别

在方法的func关键字之前加上关键字static或者class都可以用于指定类方法.
不同的是用class关键字指定的类方法可以被子类重写, 如下:
override class func work() {
print(“Teacher: University Teacher”)
}

但是用static关键字指定的类方法是不能被子类重写的, 根据报错信息: Class method overrides a ‘final’ class method.
我们可以知道被static指定的类方法包含final关键字的特性–防止被重写.

11.lazy关键字

就是swift中的懒加载

被lazy关键修饰的变量, 只有在第一次被调用的时候才会去计算它初始化值的属性, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SuvCar {
var BydSuv = "S7"
}
class MpvCar {
lazy var suvCar = SuvCar()
var mpvCars = [String]()
}
let mpvCar = MpvCar()
mpvCar.mpvCars.append("CarOne")
mpvCar.mpvCars.append("CarTwo")
print("I have \(mpvCar.mpvCars.count) MPV.")
// 输出的结果为: I have 2 MPV.
print("I have a SUV Car, is \(mpvCar.suvCar.BydSuv)")
// 输出的结果为: I have a SUV Car, is S7

在这里需要注意两点:


1.用lazy修饰的变量必须是用var声明的, 因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

2.如果被lazy修饰的变量没有在初始化时就被多个线程调用, 那就没有办法保证它只被初始化一次了.

12.init关键字

在Swift 中也有对应的构造器, 来看看:

1
2
3
4
5
6
7
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = 1000
}
}

这样子coinsInPurse的值就为1000了.

还有一种用法, 就是在init后面加个”?”号, 表明该构造器可以允许失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
let someCreature = Animal(species: "Giraffe")
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
let anonymousCreature = Animal(species: "")
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}

13.convenient关键字

该关键字是用来修饰init的, 经过convenient修饰的init方法, 表明该init方式是比较次要, 辅助型的, 比如:

1
2
3
4
5
6
7
8
9
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}

14.required关键字

required也是用来修饰init方法的, 用required修饰说明该构造方法是必须实现的, 比如:

1
2
3
4
5
class SomeClass {
required init() {
// init body
}
}

PS: 如果一个子类继承了父类required修饰的init方法, 就必须得去实现该init方法, 但子类可以觉得它之后继承于它的子类可以实现该方法.

15.deinit关键字

在Swift中, 有一个类似dealloc方法, 就是deinit, 但有一些区别, dealloc方法是在引用计数为0的时候, 也就是被释放的时候才会调用, 而deinit是在实例不再引用时自动调用, 并且不用手动去管理引用计数, 比如:

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
46
47
48
49
50
51
52
53
54
class Bank {
static var coinsInBank = 10_000
static func vendCoins(var numberOfConisToVend: Int) -> Int {
numberOfConisToVend = min(numberOfConisToVend, coinsInBank)
coinsInBank -= numberOfConisToVend
return numberOfConisToVend
}
static func receiveCoins(coins: Int) {
//print("coinsInBank = \(coinsInBank)")
coinsInBank += coins
}
}
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
print("Player is nil.")
}
}
var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins.")
// 输出的结果为: A new player has joined the game with 100 coins
print("There are now \(Bank.coinsInBank) coins left in the bank.")
// 输出的结果为: There are now 9900 coins left in the bank.
playerOne?.winCoins(2_000)
print("PlayerOne wn 2000 coins & now has \(playerOne!.coinsInPurse) coins.")
// 输出的结果为: PlayerOne wn 2000 coins & now has 2100 coins.
print("The bank now only has \(Bank.coinsInBank) coins left.")
// 输出的结果为: The bank now only has 7900 coins left.
playerOne = nil
// 这个时候就会去调用deinit方法, 输出Player is nil.

16.is关键字

在Swift中, is关键字是用来判断类型所使用的, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let number = ["xiaoming", 18]
for item in number {
if item is Int {
print("item is Int type.")
} else if item is String {
print("item is String type")
}
}
// 输出的结果为:
// item is String type.
// item is Int type.

我们可以遍历一个[NSObject]类型的数组, 判断里面各元素的类型

17.throw,do-catch关键字

在Swift 2.0之前, Swift是没有自带的错误信息处理方法, 在2.0更新之后就有了错误信息的处理方法, 让我们来看看.

在这里我们会使用到一个新的语句do-catch, 这个语句和Switch-case有些类似, 但唯独有一点不太一样的就是, 在do-catch语句中需要使用到try关键字, 而Switch-case则不需要, 好了, 下面让我们来看看.

首先我们需要定义一个ErrorType的枚举

1
2
3
4
5
enum getError: ErrorType {
case ErrorOne
case ErrorTwo
case ErrorThree
}

然后我们来写一个方法, 用来获取错误类型

1
2
3
4
func doDangerousStuff() throws {
throw getError.ErrorOne
}

最后我们使用do-catch语句来获取对应的错误类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
do {
try doDangerousStuff()
} catch getError.ErrorOne {
print(getError.ErrorOne)
} catch getError.ErrorTwo {
print(getError.ErrorTwo)
} catch getError.ErrorThree {
print(getError.ErrorThree)
}
//输出结果为: ErrorOne

18.extension关键字

extension的作用是在不改变原来的类型或者协议基础下增加新的属性或者方法, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
var name = "xiaoming"
}
extension Person {
var age: Int { return 20 }
}
let person = Person()
print("person name is \(person.name), age is \(person.age).")
// 输出的结果为:
// person name is xiaoming, age is 20.

19.protocol关键字

protocol关键字在Swift中也是属于协议的意思, 所谓的协议就是约束对象, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protocol Person {
var name: String { get }
var age: String { get }
}
class Xiaoming: Person {
var name: String { return "xiaoming" }
var age: String { return "20" }
}
class Laozhang: Person {
var name: String
var age: String
init(name: String, age: String) {
self.name = name
self.age = age
}
}

20.public关键字

public: 指的是可以访问在一个模块内的任何实体, 也可以通过导入该模块来访问, 也就是我们经常在Objective-C中经常需要导入的.h文件中的方法, 该关键字可以用来修饰变量, 方法, 类, 枚举, 结构体等等之类, 比如:

1
2
3
4
5
6
7
8
9
10
11
public struct Person {
// struct body
}
public enum CompassPoint {
// enum body
}
public class SomePublecClass {}
public var somePublecVariable = 0

21.internal关键字

internal: 指的是可以访问同一模块源文件中得任何实体, 但不能从模块的外部去访问该源文件中得实体, 同样, internal也可以修饰变量, 方法, 类, 枚举, 结构体等等之类等, 比如:

1
2
3
4
5
6
7
8
9
10
11
internal struct Person {
// struct body
}
internal enum CompassPoint {
// enum body
}
internal class SomePublecClass {}
internal var somePublecVariable = 0

22.private关键字

private: 指的是限制实体时能在源文件内部使用, 外部不能访问, private也同样可以用来修饰变量, 方法, 类, 枚举, 结构体等等之类, 比如:

1
2
3
4
5
6
7
8
9
10
11
private struct Person {
// struct body
}
private enum CompassPoint {
// enum body
}
private class SomePublecClass {}
private var somePublecVariable = 0