美文网首页
Cocoa Programming for OS X: Web

Cocoa Programming for OS X: Web

作者: su3 | 来源:发表于2017-02-17 02:01 被阅读0次

    已将书中代码改为 Swift 3 版本。

    Web Services APIs

    • URL 描述协议地址
    • URLRquest 把 URL 包装起来,设置 http 方法(GET, PUT, POST 等等)
    • 配置好 URLRequest 之后,用 URLSession 来创建连接。
    • 当你把 URLRequest 交给 URLSession 之后,它会返回一个 URLSessionTask,它包装了相应的请求,你用这个 task 开始和停止连接。
    • 当 URLSessionTask 完成后你得到服务端的相应,你需要把相应的内容解析成结构化的数据。

    Project

    1. 创建 RanchForecast 项目。

    2. 打开 MainMenu.xib,删除 Window。

    3. 创建 MainWindowController.swift,修改方法

       override var windowNibName: String?{
           return "MainWindowController"
       }
      
    4. 修改 AppDelegate.swift 实现自己的单窗口应用

           class AppDelegate: NSObject, NSApplicationDelegate {
           var mainWindowController: MainWindowController?
      
           func applicationDidFinishLaunching(_ aNotification: Notification) {
           
           let mainWindowController = MainWindowController()
           mainWindowController.showWindow(self)
           self.mainWindowController = mainWindowController
               }
           }
      

    Run,确认窗口显示。

    Model

    创建数据模型 Course.swift

        import Cocoa
        
        class Course: NSObject {
            let title: String
            let url: URL
            let nextStartDate: Date
            
            init(title: String, url: URL, nextStartDate: Date) {
                self.title = title
                self.url = url
                self.nextStartDate = nextStartDate
                super.init()
            }
        }
    

    创建 ScheduleFetcher.swift 负责请求网络接口,初始化为默认设置

    class ScheduleFetcher {
    
    let session: URLSession
    
    init() {
        let config = URLSessionConfiguration.default
        session = URLSession(configuration: config)
    }
    }
    

    继续之前思考一下:用 ScheduleFetcher 初始化一个请求并取回数据时是异步的,当请求发出之后,得到结果时代码会被呼叫。有几个方式可以实现

    1. 委托模式,可以这样

       protocol ScheduleFetcherDelegate{
           func scheduleFetcher(fetcher: SchduleFetcher, didFetchCourses: [Courses])
           func scheduleFetcher(fetcher: SchduleFetcher, didFailWithError: NSError)
       }
      

    这需要加一个 delegate 属性给 ScheduleFetcher,还需要一个方法开始抓取。

    1. NSNotificationCenter 可以但不优雅。通知适合同时发给多个监听者的时候。通知没法通过 userInfo 提供类型安全的对象。

    2. Completion Handler,事件句柄。这是一个非常好的选择。完成句柄适合一次性的回调。我们将用事件句柄的模式,它与它被呼叫的地方非常相像。

    enums 提供了一个意义简明的方式来协助值定义,我们可以依赖编译器和它的类型检查来保证清晰。

        class ScheduleFetcher {
         enum FetchCoursesResult {
        case success([Course])
        case failure(NSError)
    }
    

    由于 enums 定义在 ScheduleFetcher 中,它作为嵌套类型被引用,在内部可以直接访问 FetchCoursesResult,在外部必须包含类名 ScheduleFetcher.FetchCoursesResult。

    func fetchCoursesUsingCompletionHandler(completionHandler: @escaping (FetchCoursesResult) -> (Void)) {
        let url = URL(string: "https://bookapi.bignerdranch.com/courses.json")!
        let request = URLRequest(url: url)
        
        
        
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            var result: FetchCoursesResult
            
            if data == nil {
                result = .failure(error as! NSError)
            }
            else{
                print("Received \(data!.count) bytes", terminator:"\n")
               result = .success([]) //还没实现解析,先传个空值
            }
            
            OperationQueue.main.addOperation({ 
                completionHandler(result)
            })
            
        }
        
        task.resume()
    }
    

    在 windowDidLoad() 呼叫它。添加一个 daynamic 数组,稍后将绑定一个 array controller 到这个数组上,dynamic 这个关键词使得这个属性兼容数据绑定。

    override func windowDidLoad() {
        super.windowDidLoad()
        
        let fetcher = ScheduleFetcher()
        dynamic var courses: [Course] = []
    
        fetcher.fetchCoursesUsingCompletionHandler { (result) -> (Void) in
            switch result{
            case .success(let courses):
                print("Got courses: \(courses)", separator: "", terminator: "\n")
                self.courses = courses
            case .failure(let error):
                print("Got error: \(error)", separator: "", terminator: "\n")
                NSAlert(error: error).runModal()
                self.courses = []
            }
        }
    }
    

    URLSession, HTTP status codes, error

    制造一个 error 做测试

    JSON 解析

        func courseFromDictionary(courseDict: NSDictionary) -> Course? {
        let title = courseDict["title"] as! String
        let urlString = courseDict["url"] as! String
        let upcomingArray = courseDict["upcoming"] as! [NSDictionary]
        let nextUpcomingDict = upcomingArray.first!
        let nextStartDateString = nextUpcomingDict["start_date"] as! String
        
        let url = URL(string: urlString)!
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let nextStartDate = dateFormatter.date(from: nextStartDateString)!
        
        return Course(title: title, url: url, nextStartDate: nextStartDate)
    }
    

    解析数据,返回数组。这里的的 JSONSerialization 与 Swift 2 有很大不同。

        func coursesFromData(data: Data) -> [Course] {
        do {
            let topLevelDict = try JSONSerialization.jsonObject(with: data, options: []) as! [String:Any]
            let courseDicts = topLevelDict["courses"] as! [NSDictionary]
            var courses: [Course] = []
            for courseDict in courseDicts {
                if let course = courseFromDictionary(courseDict: courseDict) {
                    courses.append(course)
                }
            }
            return courses
        } catch let error as NSError {
            print(error)
            return []
        }
    }
    

    用户界面

    1. 打开 MainWindowController.xib,点 window ,在 inspector 中修改窗口标题。
    2. 添加 table view 到 window 中。Content Mode: View Based. 2 columns。
    3. 拖一个 Date Formatter 到日期栏的 cell 上,设置为 Medium

    下面开始做绑定

    1. 拖一个 Array Controller 到画布上(window之外的空白区域)。
    2. binding inspector 面板中:Content Array: File's Owner, Model Key Path: courses
    3. 选择 table view 做绑定:Content: Array Controller;Controller Key: arrangedObjects;Selection Indexes: Array Controller 并且 Controller Key: selectionIndexes;
    4. 最后绑定值。选中 table view cell(像一个缩小的slide的图标),Value:Table Cell View;Model Key Path: objectValue.nextStartDate。title 栏的 cell,Value: bind to Table Cell View,Model Key Path: objectValue.title

    Run.

    打开网址

    双击事件只能用代码设置。拖两个 IBOutlet 到 MainWindowController.swift 中

    @IBOutlet weak var tableView: NSTableView!
    @IBOutlet var arrayController: NSArrayController!
    

    为 table view 设置双击事件的执行目标

     override func windowDidLoad() {
        super.windowDidLoad()
        
        tableView.target = self
        tableView.doubleAction = #selector(openClass(sender:))
    

    用默认浏览器打开网址

    func openClass(sender: AnyObject) {
        if  let course = arrayController.selectedObjects.first as? Course {
            NSWorkspace.shared().open(course.url)
        }
    }

    相关文章

      网友评论

          本文标题:Cocoa Programming for OS X: Web

          本文链接:https://www.haomeiwen.com/subject/jhrkwttx.html