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即可

数组

  • 数组的遍历
    区别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 = 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 是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码
    • Swift项目新浪微博_第1张图片
      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)
}

加载表情的视图模型

Swift项目新浪微博_第2张图片
15195472525419.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= { 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)  
} 

你可能感兴趣的:(Swift项目新浪微博)