Swift项目新浪微博
第一节
《The Swift Programming Language》
http://numbbbbb.gitbooks.io/-the-swift-programming-language-/参考资料
if分支语句
- 和OC中if语句有一定的区别
- 判断句可以不加()
- 在Swift的判断句中必须有明确的真假
- 不再有非0即真
- 必须有明确的Bool值
- Bool有两个取值:false/true
guard的使用
- guard是Swift2.0新增的语法
- 它与if语句非常类似,它设计的目的是提高程序的可读性
- guard语句必须带有else语句,它的语法如下:
- 当条件表达式为true时候跳过else语句中的内容,执行语句组内容
- 条件表达式为false时候执行else语句中的内容,跳转语句一般是return、break、continue和throw
* guard 条件表达式 else {
* // 条换语句
* break
* }
* 语句组
注意:guard必须用在函数中
switch分支
- switch的简单使用
- 基本用法和OC用法一致
- 不同之处:
- switch后可以不跟()
- case后可以不跟break(默认会有break)
- 一个case判断中,可以判断多个值,多个值以","隔开
- 如果希望出现之前的case穿透,则可以使用关键字fallthrough
- 浮点型的switch判断
- case后面的判断支持字符串类型
- case后面支持区间判断
while和do while循环
- while循环
- while的判断句必须有正确的真假,没有非0即真
- while后面的()可以省略
- do while循环
- 使用repeat关键字来代替了do
在swift3.0中取消了传统的for循坏
for循环
在swift3.0中取消了传统的for循坏
那么我们如果要在swift中范旭遍历
for i in (0..<10).reverse(){
print(i)
}
字符串
-
OC和Swift中字符串的区别
- 在OC中字符串类型时NSString,在Swift中字符串类型是String
- OC中字符串@"",Swift中字符串""
-
使用 String 的原因
- String 是一个结构体,性能更高
- NSString 是一个 OC 对象,性能略差
- String 支持直接遍历
- Swift 提供了 String 和 NSString 之间的无缝转换
- 字符串的格式化
- 比如时间:03:04
let min = 3
let second = 4
let time = String(format: "%02d:%02d", arguments: [min, second])
- 字符串的截取
- Swift中提供了特殊的截取方式
- 该方式非常麻烦
- Index创建较为麻烦
- 简单的方式是将String转成NSString来使用
- 在标识符后加:as NSString即可
- Swift中提供了特殊的截取方式
数组
- 数组的遍历
区别OC多个一个区间遍历
// 设置遍历的区间
for item in array[0..<2] {
print(item)
}
- 数组的合并
- 只要相同类型类型的数组就可以合并
var array = ["why", "lmj","lnj"]
var array1 = ["yz", "wsz"]
var array2 = array + array1;
字典
字典跟数组一样也是一个泛型集合
字典的遍历
// 遍历字典中所有的值
for value in dict.values {
print(value)
}
// 遍历字典中所有的键
for key in dict.keys {
print(key)
}
// 遍历所有的键值对
for (key, value) in dict {
print(key)
print(value)
}
元组
- 组成元组类型的数据可以称为“元素”
元组的简单使用
let error = (404, "Not Found")
print(error.0)
print(error.1)
// 写法二:
let error = (errorCode : 404, errorInfo : "Not Found")
print(error.errorCode)
print(error.errorInfo)
// 写法三:
let (errorCode, errorIno) = (404, "Not Found")
print(errorCode)
print(errorIno)
可选类型
-
概念:
- 在OC开发中,如果一个变量暂停不使用,可以赋值为0(基本属性类型)或者赋值为空(对象类型)
- 在swift开发中,nil也是一个特殊的类型.因为和真实的类型不匹配是不能赋值的(swift是强类型语言)
- 但是开发中赋值nil,在所难免.因此推出了可选类型
-
可选类型的取值:
- 空值
- 有值
-
定义可选类型
let name : Optional<String> = nil
// 写法二:定义可选类型,语法糖(常用)
let name : String? = nil
可选绑定
//原理:首先判断等号左边的值是否为空,如果为空就不执行大括号内的代码,如果不为空那么会把值赋值给等号后边,然后执行大括号内的代码
if let str = string {
print(str)
}
//可选类型+可选绑定让我们的swift代码更加严谨
函数的使用注意
-
1.注意点:外部参数和内部参数
- 在函数内部可以看到的参数,就是内部参数
- 在函数外面可以看到的参数,就是外部参数
- 默认情况下,从第二个参数开始,参数名称既是内部参数也是外部参数
- 如果第一个参数也想要有外部参数,可以设置标签:在变量名前加标签即可
- 如果不想要外部参数,可以在参数名称前加_
- 2.注意: 默认参数
- 某些情况,如果没有传入具体的参数,可以使用默认参数
func makecoffee(type :String = "卡布奇诺") -> String {
return "制作一杯\(type)咖啡。"
}
let coffee1 = makecoffee("拿铁")
let coffee2 = makecoffee()
- 3.注意: 可变参数
- swift中函数的参数个数可以变化,它可以接受不确定数量的输入类型参数
- 它们必须具有相同的类型
- 我们可以通过在参数类型名后面加入(...)的方式来指示这是可变参数
func sum(numbers:Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total
}
//其实可变参数,相当于数组
sum(100.0, 20, 30)
sum(30, 80)
- 4注意: 引用类型(指针的传递)
- 默认情况下,函数的参数是值传递.如果想改变外面的变量,则需要传递变量的地址
- 必须是变量,因为需要在内部改变其值
- Swift提供的inout关键字就可以实现
指针的传递
func swap1(inout a : Int, inout b : Int) {
let temp = a
a = b
b = temp
print("a:\(a), b:\(b)")
}
swap1(&a, b: &b)
print("a:\(a), b:\(b)")
- 5.注意: 函数的嵌套使用
- swift中函数可以嵌套使用
- 即函数中包含函数,但是不推荐该写法
Swift中类的使用
-
Swift中类的属性有多种
- 存储属性:存储实例的常量和变量
- 计算属性:通过某种方式计算出来的属性
- 类属性:与整个类自身相关的属性
-
类属性是与类相关联的,而不是与类的实例相关联
-
所有的类和实例都共有一份类属性.因此在某一处修改之后,该类属性就会被修改
-
类属性的设置和修改,需要通过类来完成
-
下面是类属性的写法
- 类属性使用static来修饰
监听属性的变化
- 区别oc代码,如果在oc代码中药监听摸一个属性的变化我们一般重写set的方法
- 我们通过设置以下观察方法来定义观察者
- willSet:在属性值被存储之前设置。此时新属性值作为一个常量参数被传入。该参数名默认为newValue,我们可以自己定义该参数名
- didSet:在新属性值被存储后立即调用。与willSet相同,此时传入的是属性的旧值,默认参数名为oldValue
- willSet与didSet只有在属性第一次被设置时才会调用,在初始化时,不会去调用这些监听方法
类的构造函数
- 真实创建对象时,更多的是将字典转成模型
- 注意:
- 去字典中取出的是NSObject,任意类型.
- 可以通过as!转成需要的类型,再赋值(不可以直接赋值)
- KVC并不能保证会给所有的属性赋值
- 因此属性需要有默认值
class Person: NSObject {
// 结构体或者类的类型,必须是可选类型.因为不能保证一定会赋值
var name : String?
// 基本数据类型不能是可选类型,否则KVC无法转化
var age : Int = 0
// 自定义构造函数,会覆盖init()函数
init(dict : [String : NSObject]) {
// 必须先初始化对象
super.init()
// 调用对象的KVC方法字典转模型
setValuesForKeysWithDictionary(dict)
}
}
// 创建一个Person对象
let dict = ["name" : "why", "age" : 18]
let p = Person(dict: dict)
OC中Block的复习
block的写法:
类型:
返回值(^block的名称)(block的参数)
值:
^(参数列表) {
// 执行的代码
};
闭包
- Swift中的闭包是一个特殊的函数
- 闭包的写法
闭包的写法:
类型:(形参列表)->(返回值)
技巧:初学者定义闭包类型,直接写()->().再填充参数和返回值
值:
{
(形参) -> 返回值类型 in
// 执行代码
}
- 尾随闭包写法:
- 如果闭包是函数的最后一个参数,则可以将闭包写在()后面
- 如果函数只有一个参数,并且这个参数是闭包,那么()可以不写
// 开发中建议该写法
httpTool.loadRequest {
print("回到主线程", NSThread.currentThread());
}
闭包的循坏引用
- swift中解决循环引用的方式
- 方案一:
- 使用weak,对当前控制器使用弱引用
- 但是因为self可能有值也可能没有值,因此weakSelf是一个可选类型,在真正使用时可以对其强制解包(该处强制解包没有问题,因为控制器一定存在,否则无法调用所在函数)
- 方案二:(常用方法)
- 和方案一类型,只是书写方式更加简单
- 可以写在闭包中,并且在闭包中用到的self都是弱引用
httpTool.loadData {[weak self] () -> () in
print("加载数据完成,更新界面:", NSThread.currentThread())
self!.view.backgroundColor = UIColor.redColor()
}
- 方案三:(常用)
- 使用关键字unowned
- 从行为上来说 unowned 更像OC中的 unsafe_unretained
- unowned 表示:即使它原来引用的对象被释放了,仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil
httpTool.loadData {[unowned self] () -> () in
print("加载数据完成,更新界面:", NSThread.currentThread())
self.view.backgroundColor = UIColor.redColor()
}
懒加载
- 懒加载的介绍
- swift中也有懒加载的方式
- (苹果的设计思想:希望所有的对象在使用时才真正加载到内存中)
- 和OC不同的是swift有专门的关键字来实现懒加载
// 懒加载的本质是,在第一次使用的时候执行闭包,将闭包的返回值赋值给属性
// lazy的作用是只会赋值一次
lazy var array : [String] = {
() -> [String] in
return ["why", "lmj", "lnj"]
}()
第二节
通过json动态创建控制器
override func viewDidLoad() {
super.viewDidLoad()
// 1.加载json中的数据
// 1.1.获取路径
let path = NSBundle.mainBundle().pathForResource("MainVCSettings.json", ofType: nil)
// 1.2.加载数据
guard let data = NSData(contentsOfFile: path!) else {
XMGLog("没有获取到json数据")
addChildViewController()
return
}
// 1.3.通过序列化获取内容
guard let childVcArray = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! [[String : AnyObject]] else {
print("没有获取到值")
addChildViewController()
return
}
// 1.4.遍历数组,并且创建控制器
for dict in childVcArray {
// 1.取出类名称
let vcName = dict["vcName"] as! String
// 2.取出标题
let title = dict["title"] as! String
// 3.取出图标名称
let imageName = dict["imageName"] as! String
// 4.创建控制器
addChildViewController(vcName, imageName: imageName, title: title)
}
}
private func addChildViewController() {
self.addChildViewController("HomeViewController", imageName: "tabbar_home", title: "主页")
self.addChildViewController("MessageViewController", imageName: "tabbar_message_center", title: "消息")
self.addChildViewController("DiscoverViewController", imageName: "tabbar_discover", title: "广场")
self.addChildViewController("ProfileViewController", imageName: "tabbar_profile", title: "我")
}
private func addChildViewController(childCVcName: String, imageName : String, title : String) {
// 0.获取命名空间
guard let executable = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as? String else {
XMGLog("没有命名空间")
return
}
// 1.获取对应的类
guard let childVcClass : AnyClass = NSClassFromString(executable + "." + childCVcName) else {
XMGLog("转成对应的类失败")
return
}
// 2.拿到对应的类
let childClass = childVcClass as! UITableViewController.Type
let childVc = childClass.init()
// 3.创建自控制器
let homeNav = UINavigationController(rootViewController: childVc)
// 4.设置标题
childVc.title = title
childVc.tabBarItem.selectedImage = UIImage(named: imageName + "_highlighted")
childVc.tabBarItem.image = UIImage(named: imageName)
// 5.添加到UITabbarController
self.addChildViewController(homeNav)
}
Swift的异常处理
-
如果在调用系统某一个方法时,该方法最后有一个throws.说明该方法会抛出异常.如果一个方法会抛出异常,那么需要对该异常进行处理
-
在swift中提供三种处理异常的方式
方式一:try方式 程序员手动捕捉异常
do {
try NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers)
} catch {
// error异常的对象
print(error)
}
方式二:try?方式(常用方式) 系统帮助我们处理异常,如果该方法出现了异常,则该方法返回nil.如果没有异常,则返回对应的对象
guard let anyObject = try? NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers) else {
return
}
方式三:try!方法(不建议,非常危险) 直接告诉系统,该方法没有异常.注意:如果该方法出现了异常,那么程序会报错(崩溃)
let anyObject = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers)
遍历构造函数
convenience : 便利,使用convenience修饰的构造函数叫做便利构造函数
遍历构造函数通常用在对系统的类进行构造函数的扩充时使用
/*
遍历构造函数的特点
1.遍历构造函数通常都是写在extension里面
2.遍历构造函数init前面需要加载convenience
3.在遍历构造函数中需要明确的调用self.init()
*/
时间监听的本质
// 事件监听本质发送消息.但是发送消息是OC的特性
// 将方法包装成@SEL --> 类中查找方法列表 --> 根据@SEL找到imp指针(函数指针) --> 执行函数
// 如果swift中将一个函数声明称private,那么该函数不会被添加到方法列表中
// 如果在private前面加上@objc,那么该方法依然会被添加到方法列表中
重写init
swift中规定:重写控件的init(frame方法)或者init()方法,必须重写init?(coder aDecoder: NSCoder)
modal一个控制器
modal一个控制器,默认之前的控制器被移除,如果不想被移除,需要设置
popoverVc.modalPresentationStyle = .custom
第三课
请求授权的材料
App Key:3102179627
App Secret:c6663f645ea9ee709e7368a13edb36c9
授权回调页:http://www.baidu.com
取消授权回调页:http://www.baidu.com
请求地址
https://api.weibo.com/oauth2/authorize?client_id=3102179627&redirect_uri=http://www.baidu.com
服务器返回的code
code=6a10e36850c396efa921be8a38bc47b8
AFN使用常见错误
- AFN 访问方法最常见的错误
status code == 200,但是提示 unacceptable content-type,表示网络访问正常,但是无法对返回数据做反序列化
解决办法:增加 反序列化数据 格式
另外一个常见错误
status code == 405,不支持的网络请求方法,检查 GET / POST 是否写错
SnapKit框架替代Masonry框架
第四课
属性监听器didSet中如果在init的构造函数中监听某个值无效,谨记
巨坑
accout.screen_name = userInfoDict["screen_name"] as? String
accout.avatar_large = userInfoDict["avatar_large"] as? String
NSKeyedArchiver.archiveRootObject(accout, toFile: UserAccountViewModel.shareInstance.accountPath)
UserAccountViewModel.shareInstance.account = accout
swift4.0中字典转模型不要再用kvc,谨记
MVVM的简介
-
MVVM 是 Model-View-ViewModel 的简写,MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model)
-
MVC 存在的问题
- 模型的代码很少
- 控制器的代码一不小心就越来越多
- 不好测试
-
MVVM 概念
- 在 MVVM 中,view 和 view controller 正式联系在一起,我们把它们视为一个组件
- view 和 view controller 都不能直接引用 model,而是引用视图模型
- view model 是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码
-
15184301235885.png
- MVVM 使用注意事项
- view 引用 view model,但反过来不行
- view model 引用了 model,但反过来不行
- 如果我们破坏了这些规则,便无法正确地使用 MVVM
微博中发布时间的处理代码
class func createTimeString(dateString : String) -> String {
// 1.创建dateFmt对时间进行格式化
let fmt = NSDateFormatter()
fmt.dateFormat = "EEE MM dd HH:mm:ss Z yyyy"
fmt.locale = NSLocale(localeIdentifier: "en")
// 2.将字符串转成时间
guard let createDate = fmt.dateFromString(dateString) else {
return ""
}
// 3.获取当前时间
let nowDate = NSDate()
// 4.比较两个时间差
let interval = Int(nowDate.timeIntervalSinceDate(createDate))
// 5.根据时间差,计算要显示的字符串
// 5.1.1分钟内:刚刚
if interval < 60 {
return "刚刚"
}
// 5.2.1小时内:15分钟前
if interval < 60 * 60 {
return "\(interval / 60)分钟前"
}
// 5.3.1天内:3小时前
if interval < 60 * 60 * 24 {
return "\(interval / (60 * 60))小时前"
}
let calendar = NSCalendar.currentCalendar()
// 5.4.昨天: 昨天 03:24
if calendar.isDateInYesterday(createDate) {
fmt.dateFormat = "HH:mm"
return "昨天 \(fmt.stringFromDate(createDate))"
}
// 5.5.一年内: 02-23 03:24
let comps = calendar.components(.Year, fromDate: createDate, toDate: nowDate, options: [])
if comps.year < 1 {
fmt.dateFormat = "MM-dd HH:mm"
return fmt.stringFromDate(createDate)
}
// 5.6.一年后: 2015-2-23 03:23
if comps.year >= 1 {
fmt.dateFormat = "yyyy-MM-dd HH:mm"
return fmt.stringFromDate(createDate)
}
return ""
}
归档 & 解档的复习
归档和解档是用来保存OC对象,OC对象要进行归档和解档的话对象必须遵守NScoding协议,然后实现协议方法
// MARK: - 归档 & 解档
/// 归档 -> 将当前对象归档保存至二进制文件之前被调用
///
/// - parameter aCoder: 编码器
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(access_token, forKey: "access_token")
aCoder.encodeObject(expiresDate, forKey: "expiresDate")
aCoder.encodeObject(uid, forKey: "uid")
aCoder.encodeObject(screen_name, forKey: "screen_name")
aCoder.encodeObject(avatar_large, forKey: "avatar_large")
}
/// 解档 -> 从二进制文件恢复成对象时调用,与网络的反序列化功能类似
///
/// - parameter aDecoder: 解码器
///
/// - returns: 用户账户对象
required init?(coder aDecoder: NSCoder) {
access_token = aDecoder.decodeObjectForKey("access_token") as? String
expiresDate = aDecoder.decodeObjectForKey("expiresDate") as? NSDate
uid = aDecoder.decodeObjectForKey("uid") as? String
screen_name = aDecoder.decodeObjectForKey("screen_name") as? String
avatar_large = aDecoder.decodeObjectForKey("avatar_large") as? String
}
- 实现将当前对象归档保存的函数
/// 将当前对象保存至沙盒
func saveUserAccount() {
var path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last!
path = (path as NSString).stringByAppendingPathComponent("account.plist")
print(path)
NSKeyedArchiver.archiveRootObject(self, toFile: path)
}
- 调用归档使用 NSKeyedArchiver
- 调用解档使用 NSKeyedUnarchiver
第五课
在新浪项目的视图模型中处理配图数据
if let picURLDicts = status.pic_urls{
for picURLDict in picURLDicts{
guard let picURLString = picURLDict["thumbnail_pic"] else{
continue
}
picURLs.append(NSURL(string: picURLString)!)
}
}
单张配图的展示
因为新浪的返回数据中没有图片的宽高,所以我们要获取单张配图的宽高,需要先缓存图片
计算cellHeight
在开发中我们可以使用自动布局,让cell中的所有空间自动计算高度,只要在tableView中设置
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 200;
还需要设置cell最底部的控件距离cell的底部固定间距
也可以在viewModel模型中设置一个cellHeight属性
在tableView中设置
self.tableView.estimatedRowHeight = 200;
如果在tableview中设置估算高度,那么tableview加载的时候会首先加载数据源方法,在数据源方法中会调用uitableviewCell,我们可以在cell中setModel属性计算属性,再保存到模型的cellHeight当中
在项目中集成MJRefresh
在项目开始加载首页时进入下拉加载最新数据
在reloadData后面停止刷新
self.tableView.reloadData()
self.tableView.mj_header.endRefreshing()
self.tableView.mj_footer.endRefreshing()
第六课
通知的使用
在Swift3.0以后通知的改变
注册通知
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "AuthSuccessNotification"), object: nil)
监听通知
NotificationCenter.default.addObserver(self, selector:#selector(ViewController.pageJump), name: NSNotification.Name(rawValue: "AuthSuccessNotification"), object: nil)
销毁通知
deinit {
NotificationCenter.default.removeObserver(self)
}
加载表情的视图模型
![](https://img.haomeiwen.com/i10921965/2c99e754d6c1d8a4.png)
第七课
图文混排
let attStr = NSAttributedString(string: "陈栐齐", attributes: [NSAttributedStringKey.foregroundColor : UIColor.orange])
let attstr1 = NSAttributedString(string: "陈恒均", attributes: [NSAttributedStringKey.foregroundColor : UIColor.red])
//图文混排
let attachment = NSTextAttachment()
attachment.image = UIImage(named: "compose_emotion_delete")
let font = textView.font
attachment.bounds = CGRect(x: 0, y: -4, width: (font?.lineHeight)!, height: (font?.lineHeight)!)
let attrImageStr = NSAttributedString(attachment: attachment)
let attrMStr = NSMutableAttributedString()
attrMStr.append(attStr)
attrMStr.append(attrImageStr)
attrMStr.append(attstr1)
textView.attributedText = attrMStr
AFN常见错误
请求失败-Error Domain=com.alamofire.error.serialization.response Code=-1011 "Request failed: bad request (400)" UserInfo={com.alamofire.serialization.response.error.response=<NSHTTPURLResponse: 0x7cec30d0> { URL: https://api.weibo.com/oauth2/access_token } { status code: 400, headers {
- 检查URL里面的错误。400错误请求错误最常见的原因是由于URL输入错误或点击的链接指向一个URL和一种特定的错误,比如语法问题。
- 重点:这是最可能出现的问题如果你得到一个400错误请求错误。尤其,检查在URL中一些典型的字符,比如%字符。像%字符在一些地方能够被有效使用,你不会在标准URL中就发现一个。
正则表达式
http://www.cnblogs.com/zxin/archive/2013/01/26/2877765.html
let str = "sdadsadsadsad1233122"
let pattern = "^1[3578]\\d{9}$"
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else{
return
}
let results = regex.matches(in: str, options: [], range: NSRange(location: 0, length: str.characters.count))
for result in results {
print((str as NSString).substring(with: result.range))
print(result.range)
}
for循环swift3.0的改变
倒序遍历
for i in (0...10).reversed() {
print(i)
}