码农的世界程序员技术栈

唉,我去!撸手表的心得来了

2019-02-09  本文已影响225人  何晓杰Dev

这篇只是简单的总结,过年期间还天天写代码的,估计不多吧[捂脸]。不得不说,在 Apple Watch 的开发中,的确踩了很多坑,也有必要总结一下,防止以后再踩。

一、InterfaceController 的命名

原则上,在 Watch 上新建 InterfaceController 时,应当使用新建向导,而且建完类后的类名不能更改,否则 storyboard 内认不出这个类,也无法绑定。如果硬要改类名的情况,改后的类名必须以 InterfaceController 为结尾,否则 storyboard 也不认。

二、简易的 Table API

Watch 上的 API 都非常的简单,WKInterfaceTable 也是非常简单的,没有 dataSource 也没有 delegate。对于绑定数据,直接使用以下代码就可以轻松搞定:

table.setNumberOfRows(list.count, withRowType: "Cell")
for i in list.indices {
    let r = table.rowController(at: i) as! RowController
    r.label.setText(list[I])
}

由于 Watch 的性能问题,尽可能不要添加太多的 rows,否则你会发现绘制时变得很卡。

同样的,对于 table 内 row 的点击处理,也非常的简单,直接覆写 func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) 这个方法就可以了。

三、push controller 传值

这一点与 iOS 的处理差异非常大,iOS 内可以依自己的需要在目标 Controller 内建立变量,并且直接赋值,而在 Watch 里,有固定的做法:

pushController(withName: "InterfaceController", context: obj)
override func awake(withContext context: Any?) {
    super.awake(withContext: context)
    // 此处的 context 就是 pushController 传来的值
}

所以通常不会传一些复杂的东西,对于要传递一大堆数据的情况,必须把那些数据拼装起来,当然我个人的喜好是拼成 json 串。

四、引用第三方库

首先有一点必须明确,就是 Watch 上的程序,必须开启 bitcode,否则将无法安装在真实设备上。这也就要求被引用的库也可以开启 bitcode。有一些上古时代的老库可能就没有办法折腾了。

对于可以开启 bitcode 的库,均可以直接添加 Watch 的 Target 以使得库可以在 Watch 上使用。在将一个 for iOS 的库变为 for Watch 时,需要注意的是,在 Build Phases 内必须重新添加要编译的目标文件,新建 Target 的时候并不会带入这些文件。

另外,由于我平时喜欢使用 CodeTyphon 来进行跨平台开发,也特地说明一下,到目前为止,CodeTyphon 还无法在 Watch 上工作,因为缺少 armv7k 架构的工具链,在网上找了一圈也没有相关的资料。

五、方法过期

在 Watch 中,对 API 版本有严格的要求,比如说 sendSynchronousRequest 这类方法,在 iOS 中标记为过期但是依然可用,然而在 Watch 中,是没有这样的方法的,这就要求我们把过期的东西全部去掉。

自己的代码容易修,而第三方的代码库就没那么好运了,在实际开发中,光是修过期代码就花费了不少时间。依然用网络请求来举例子,曾经的代码是:

func request(_ url: String) -> String { ... }
thread {
    let ret = request(url)
    ...
}

而现在就要改为:

func request(_ url: String, _ callback:(String) -> Void) { ... }
request(url) { ret in
    ... 
}

涉及到此的所有代码结构都得改一下了。

六、HTTP 请求和文件下载

众所周知的,苹果一直在建议 https 请求,在 Watch 的开发中依然遇到了这样的问题,发起一个 http 请求将被拒绝,此时需要修改 Info.plist,需要注意的是,只能对 Extension 下的 plist 进行修改,加入 App Transport Security Settings 配置,若是对 Watch Target 进行修改,则会报一个配置不被支持的错误。这个错误一开始也害我误以为在 Watch 上不允许 http 请求。

另外对于文件下载,Watch 上有一些很奇怪的限制,如以下代码:

(Data(contentsOf: URL(string: url)!) as NSData).write(toFile: path, atomically: true)

看起来没毛病 ,在模拟器上也能够正常运行,但是在真机上,怎么都下载不到文件,经过尝试,在真机上需要将代码改成这样:

let req = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: req) { data, resp, err in
    (data! as NSData).write(toFile: path, atomically: true)
}
task.resume()

七、Dictionary 的大坑

这个问题不知是从哪里来的,经过分析感觉可能是 swift 的 bug,在解析 json 的过程中,使用以下代码:

let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableLeaves) as! Dictionary<String, Any>
let obj = json["data"] as! Dictionary<String, Any>
result.imageId = obj["imageid"] as! Int

坏就坏在最后一句代码,前面的 imageId 是 Int 类型,后面理所当然的要将 Any 转为 Int。但是这段代码在不同的情况下会产生不同的类型,经过测试有以下两种:

(lldb) po type(of: obj["imageid"])
__NSCFNumber
(lldb) po type(of: obj["imageid"])
NSTaggedPointerString

换言之,如果产生了下面那种类型,强转成 Int 就会报错。为了解决这个(不知道什么时候会发生的)问题,不得不写了一个扩展来解决:

extension Dictionary {
    
    func string(_ key: Key) -> String {
        let v = self[key]
        let ret = "\(v!)"
        return ret
    }
    
    func int(_ key: Key) -> Int {
        let v = self[key]
        let s = "\(v!)"
        var ret = 0
        if (s != "") {
            ret = Int(s)!
        }
        return ret
    }
}

然后把上面的代码改成:

result.imageId = obj.int("imageid")

八、连接真机

正常的流程是 Watch 和 iPhone 配对,然后 iPhone 插上 Mac(苹果硬件全家桶真贵!),然后手表上会提示是否信任该电脑,选择信任就可以了。

但是我当时手残了,不小心点了不信任,然后发现 xcode 怎么都没办法把 app 装到手表上,不论怎么重插都不再提示“是否信任”,这个情况需要按以下步骤进行:

1. 从 Mac 上拔下 iPhone
2. cd ~/Library/Developer/Xcode/watchOS DeviceSupport
3. rm -fr *
4.  重启手表
5. 将 iPhone 插到 Mac 上

此时在手表上就会重新出现是否信任的选项。


好了,就总结到这里吧,其实手表的开发还是挺简单的,毕竟屏幕小,那块 CPU 也不是十分强劲,做不了太多的事情,只能一切从简。

文中所述的代码均是从我的实际项目中分离而来,需要参看项目的请移步 Github,最终实现的效果就是这样啦:

watchapp.gif
上一篇下一篇

猜你喜欢

热点阅读