iOS开发-OAuth授权(下篇)

  • 获取未授权RequestToken(登录界面)
    • 按照文档发送GET请求
  • 获取已经授权RequestToken(让用户登录)
    • 截取授权回调页code=后面字符串
  • 换取AccessToken(令牌)
    • 传递已经授权RequestToken, 和其它参数
  • 守护者和可选绑定
    • guard let value = xxx else {}
      • 有效避免{}嵌套, 提高阅读性
      • 条件表达式中的变量可以在后面使用
      • 条件表达式中的变量不能在{}中使用
    • if let value = xxx {}
      • 容易形成{}嵌套
      • 条件表达式中的变量不能在后面使用
      • 条件表达式中的变量可以在{}中使用
    • 守护者和可选绑定可以添加额外条件

Swift
// 两个条件同时为false进入{}
guard let value = xxx where 条件表达式
else
{
return true
}

// 两个条件同时为true进入{}
if let value = xxx where 条件表达式
{

}
```
  • NSURLquery属性
    • 用于获取URL中的参数(?后面的内容)
  • 单例
    • dispatch
      • 原理默认dispatch_once_t等于0, 执行一次之后变为-1
        *Swift不推荐这样编写单例
swift
    static var onceToken: dispatch_once_t = 0;
    static var instance: NetworkTools?;
    class func shareInstance() -> NetworkTools
    {
        print(onceToken)
        dispatch_once(&onceToken) { () -> Void in
            instance = NetworkTools()
        }
        return instance!
    }
    ```



    + 静态常量
        * let是线程安全的
        * let只能赋值一次
        * 推荐写法

    swift
    static let shareInstance:NetworkTools = NetworkTools()
    

    + 如何在创建单例时初始化对象
        * 静态常量 + 重写构造方法
        swift
        static let shareInstance:NetworkTools = NetworkTools()
        override init() {
            print("初始化操作")
        }
        
        * 静态常量 + 闭包
        swift
        static let shareInstance:NetworkTools = {
            let t = NetworkTools()
            print("初始化操作")
            // 对T进行初始化
            return t
        }()
        
- AFN
    + Swift使用不同命名空间(不同项目)中的类需要导入头文件
    ```swift
    import UIKit
    import AFNetworking

    class NetworkTools: AFHTTPSessionManager {

        static let shareInstance: NetworkTools = {
            // 注意: 指定baseURL一定要以/结尾
           let url = NSURL(string: "https://api.weibo.com/")
           let tools =  NetworkTools(baseURL: url)
            tools.responseSerializer.acceptableContentTypes = NSSet(objects: "application/json", "text/json", "text/javascript", "text/plain") as? Set<String>
           return tools
        }()
    }
    ```

- KVC
    + key必须和属性一一对应, 否则需要重写
    ```swift
    override func setValue(value: AnyObject?, forUndefinedKey key: String) {}
    ```
    + 在构造方法中使用KVC必须先调用super.init()
    + 基本数据类型属性必须初始化, 否则会报错

- 打印对象
    + 重写CustomStringConvertible协议中的description属性
    ```swift
    override var description: String{
        let keys = ["access_token", "expires_in", "uid"]
        let dict = dictionaryWithValuesForKeys(keys)
        return "\(dict)"
    }
    ```
    + 注意Swift2.0之前该属性在Printable协议中

- 归档解归档
    + 和OC一致
    ```swift
    // 写入文件时调用
    func encodeWithCoder(aCoder: NSCoder)
    {
        aCoder.encodeObject(access_token, forKey: "access_token")
        aCoder.encodeObject(expires_in, forKey: "expires_in")
        aCoder.encodeObject(uid, forKey: "uid")
    }
    // 从文件中读取出来时调用
    required init?(coder aDecoder: NSCoder)
    {
        access_token = aDecoder.decodeObjectForKey("access_token") as? String
        expires_in = aDecoder.decodeObjectForKey("expires_in") as! Int
        uid = aDecoder.decodeObjectForKey("uid") as? String
    }
    ```
    + 注意: 开发中谁最清楚怎么做, 方法就应该在谁里面

- 路径
    - `Documents`
        - 需要保存由"应用程序本身"产生的文件或者数据,例如:游戏进度、涂鸦软件的绘图
        - 目录中的文件会被自动保存在 iCloud
        - 注意:不要保存从网络上下载的文件,否则会无法上架!

    - `Caches`
        - 保存临时文件,"后续需要使用",例如:缓存图片,离线数据(地图数据)
        - 系统不会清理 `cache` 目录中的文件
        - 就要求程序开发时,"必须提供 `cache` 目录的清理解决方案"

    - `Preference`s
        - 用户偏好,使用 `NSUserDefault` 直接读写!
        - 如果要想数据及时写入磁盘,还需要调用一个同步方法

    - `tmp`
        - 保存临时文件,"后续不需要使用"
        - `tmp` 目录中的文件,系统会自动清理
        - 重新启动手机,`tmp` 目录会被清空
        - 系统磁盘空间不足时,系统也会自动清理

- 路径优化
    + 开发中经常需要往CachesDirectory/DocumentDirectory/TemporaryDirectory中存储数据
    + 为了方便拼接路径, 化繁为简应该对拼接实现进行封装

Swift
func docDir() -> String
{
// 1.拿到文件夹路径
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
// 2.拼接字符串
// 注意: pathComponents方法, 会按照字符串的/来切割字符串, 如果给定的字符串中没有/那么就直接返回给定的字符串
let temp = (self as NSString).pathComponents.last

    // 如果??前面有值, 那么??后面的代码不执行, 如果??前面没有值, 那么就执行??后面的代码
    let result = (path as NSString).stringByAppendingPathComponent(temp ?? "")

    // 3.返回结果
    return result
}

func cachesDir() -> String
{
    // 1.拿到文件夹路径
    let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
     let temp = (self as NSString).pathComponents.last
    // 2.拼接字符串
    let result = (path as NSString).stringByAppendingPathComponent(temp ?? "")

    // 3.返回结果
    return result
}

func tmpDir() -> String
{
    // 1.拿到文件夹路径
    let path = NSTemporaryDirectory()

     let temp = (self as NSString).pathComponents.last
    // 2.拼接字符串
    let result = (path as NSString).stringByAppendingPathComponent(temp ?? "")

    // 3.返回结果
    return result
}

- `??`
    + `??`前面不为`nil`,后面不执行
    + `??`前面为`nil`, 执行后面代码

- 性能优化
    + 多次从文件中加载授权模型返回的结果一致
    + 从文件中加载模型非常消耗性能
    + 解决方案: 利用静态变量保存, 只加载一次

- 自定义UICollection布局
    + 在显示cell之前会调用prepareLayout准备布局
    + 在prepareLayout方法中修改布局
```swift
class NewfeatureLayout: UICollectionViewFlowLayout {
    // 该方法会在显示cell之前调用
    override func prepareLayout() {
        // 修改layout属性
        itemSize = UIScreen.mainScreen().bounds.size
        scrollDirection = UICollectionViewScrollDirection.Horizontal
        minimumInteritemSpacing = 0
        minimumLineSpacing = 0

        // 设置collectionView
        collectionView?.showsHorizontalScrollIndicator = false
        collectionView?.showsVerticalScrollIndicator = false
        collectionView?.bounces = false
        collectionView?.pagingEnabled = true

    }
}
  • 监听cell是否完全显示
override func collectionView(collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath){
}
  • 获取当前显示cell的索引
swift let path = collectionView.indexPathsForVisibleItems()
  • Damping动画
/*
        第一个参数: 动画时长
        第二个参数: 延迟时长
        第三个参数: 震动幅度 0.0~1.0之间, 值越小震动越厉害
        第四个参数: 动画初始速度, 值越大开始就越快
        第五个参数: 附加选项
        第六个参数: 需要执行动画代码
        第七个参数: 动画执行完毕之后的回调
        */
        UIView.animateWithDuration(2.0, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 10, options: UIViewAnimationOptions(rawValue: 0), animations: { () -> Void in
                self.startButton.transform = CGAffineTransformIdentity
            }, completion: { (_) -> Void in
                XMGLog("动画执行完毕")
                self.startButton.userInteractionEnabled = true
        })
  • 获取用户信息
    • 保证方法单一性, 不要做过多复杂操作
    • 将主动权交给调用者
func loadUserInfo(finished: (dict: [String: AnyObject]?, error: NSError?)->())
    {

        // 断言: 用于程序员之间沟通
        // 断定access_token一定有值, 如果没有程序就会崩溃, 并且打印后面的message
        assert(access_token != nil, "必须有access_token才能使用该方法")
        assert(uid != nil, "必须有uid才能使用该方法")

        // 1.准备路径
        let path = "2/users/show.json"
        // 2.准备参数
        let parameters = ["access_token": access_token!, "uid": uid!]
        // 3.发送Get请求
        NetworkTools.shareInstance.GET(path, parameters: parameters, success: { (task, objc) -> Void in

            // 3.1.对服务器返回的数据进行安全校验
            guard let dict = objc as? [String: AnyObject] else
            {
                XMGLog("服务器没有返回数据")
                return
            }

            // 3.2从获取到的用户信息中取出昵称和头像
            self.avatar_large = dict["avatar_large"] as? String
            self.screen_name = dict["screen_name"] as? String

            // 3.3保存授权信息
//            self.saveUserAccount()
            finished(dict: dict, error: nil)

            }) { (task, error) -> Void in
                XMGLog(error)
                finished(dict: nil, error: error)
        }
    }
+ 用到闭包, 不管三七二十一先写上 `()->() ` 再做其它修改
  • 界面切换流程

  • 检查新版本

    • 思路
      • 获取当前
      • 获取以前
      • 进行比较
      • 更新以前
      • 返回结果
Swift
/// 判断是否有新版本
    private func isNewVersion() -> Bool
    {
        // 1.获取软件当前的版本号
        guard let currentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as? String else
        {
            return false
        }

        // 2.获取软件以前的版本号
        let defaults = NSUserDefaults.standardUserDefaults()
        let sandboxVersion = (defaults.objectForKey("xmg") as? String) ?? "0"

        // 3.利用软件当前的版本号和以前的版本号进行比较
        // 如果当前 > 以前  --> 新版本
        //  1.0   0.0
        if currentVersion.compare(sandboxVersion) == NSComparisonResult.OrderedDescending
        {
            NSLog("有新版本")
            // 4.如果有新版本就用当前的软件版本号更新以前的软件版本号
            defaults.setObject(currentVersion, forKey: "xmg")
//            defaults.synchronize() // iOS7以后不用写
            return true
        }

        NSLog("没有新版本")
        // 没有新版本
        return false
    }
  • 完善界面切换逻辑
    • 注意统一管理, 便于后期维护
    • 注意安全校验, 一般网络和通知都需要校验
// 发送通知, 切换到首页
 NSNotificationCenter.defaultCenter().postNotificationName(SYPChangeRootViewControllerNotification, object: self, userInfo: ["message": true])

 // 发送通知切换到欢迎界面
  NSNotificationCenter.defaultCenter().postNotificationName(SYPChangeRootViewControllerNotification, object: self, userInfo: ["message": false])

  // 监听通知切换界面
  @objc private func switchRootViewController(notice: NSNotification)
    {
//        NSLog(notice)
        // 1.取出userInfo中的message
        guard let info = notice.userInfo else
        {
            return
        }
        guard let flag = info["message"] as? Bool else
        {
            return
        }

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

推荐阅读更多精彩内容

  • 因为要结局swift3.0中引用snapKit的问题,看到一篇介绍Xcode8,swift3变化的文章,觉得很详细...
    uniapp阅读 4,410评论 0 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 1、开启公众号开发者模式 公众平台的技术文档目的为了简明扼要的交代接口的使用,语句难免苦涩难懂,甚至对于不同的读者...
    good7758阅读 1,516评论 0 1
  • “好啦,我们快过去吧!我看到你们学校门口旁好像有个接待处是你们文学院的!”爸爸拖起行李箱,催我和妈妈。 “爸爸可真...
    俐缘阅读 306评论 1 6
  • 金庸笔下的一部《笑傲江湖》,衍生了多少江湖风波、爱恨情仇。 1990年,老一辈导演胡金铨与新一代导演徐克、程小东联...
    青竹院阅读 1,577评论 2 12