• switch语法很强大,很灵活,支持任何类型,比如字符串、范围、管道等等。而且不用break语句

刚刚介绍的Category定义了七种主场景,实际开发需求中有时候需要对Category进行微调整,我们发现这个接口还有两个参数Mode和Options。

打开 Xcode 运行

菜单栏选择目标设备:

图片 1选择目标设备

Scheme 选择 WebDriverAgentRunner:

图片 2选择
WebDriverAgentRunner

最后运行 Product -> Test:

图片 3运行Product
-> Test

一切正常的话,手机/模拟器上会出现一个无图标的 WebDriverAgent
应用,启动之后,马上又返回到桌面。这很正常不要奇怪。

此时控制台界面可以看到设备的 IP 地址:

图片 4设备的
IP 地址

通过上面给出的 IP地址 和端口,加上/status合成一个url地址。例如
JSON
输出,说明 WDA 安装成功了。

此时打开

UI 图层,方便写测试脚本用。

图片 5Inspector

– guard关键词使用使语句更简单明了

if #available(iOS 10, macOS 10.12, *) { // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS} else { // Fall back to earlier iOS and macOS APIs}
  • 函数可以返回多个值

func minMax(array: [Int]) -> (min: Int, max: Int) { var currentMin = array[0] var currentMax = array[0] for value in array[1..<array.count] { if value < currentMin { currentMin = value } else if value > currentMax { currentMax = value } } return (currentMin, currentMax)}
  • 函数参数可以设置默认值

func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) { // If you omit the second argument when calling this function, then // the value of parameterWithDefault is 12 inside the function body.}someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
  • 可变参数...

func arithmeticMean(_ numbers: Double...) -> Double { var total: Double = 0 for number in numbers { total += number } return total / Double(numbers.count)}arithmeticMean(1, 2, 3, 4, 5)
  • inout 可以修改外部变量。与c语言指针有点类似

func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA
  • 函数可以与属性变量一样对待。可以传递、取值等
  • 闭包与Block类似

{ (parameters) -> return type in statements}

*@escaping@autoclosure修饰闭包@escaping使闭包在函数执行结束之前不会被执行。

var completionHandlers: [() -> Void] = []func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler)//闭包没有执行,而是加到数组中了}

@autoclosure简化了闭包的代用,不过代码会变的难懂,苹果建议尽量不使用

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]func serve(customer customerProvider: () -> String) { print("Now serving \(customerProvider}serve(customer: { customersInLine.remove } )// Prints "Now serving Alex!"

使用之后:

// customersInLine is ["Ewa", "Barry", "Daniella"]func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider}serve(customer: customersInLine.remove//大括号没有啦// Prints "Now serving Ewa!"
  • 枚举功能很强大,很灵活,可以枚举的类型非常多,比如stringcharacterinteger

enum Barcode { case upc(Int, Int, Int, Int) case qrCode}var productBarcode = Barcode.upc(8, 85909, 51226, 3)switch productBarcode {case .upc(let numberSystem, let manufacturer, let product, let check): print("UPC: \(numberSystem), \(manufacturer), \, \case .qrCode(let productCode): print("QR code: \(productCode).")}

enum Planet: Int{}enum ASCIIControlCharacter: Character {}enum CompassPoint: String {}
  • indirect表示枚举中使用了自己

enum ArithmeticExpression { case number indirect case addition(ArithmeticExpression, ArithmeticExpression) indirect case multiplication(ArithmeticExpression, ArithmeticExpression)}

indirect enum ArithmeticExpression { case number case addition(ArithmeticExpression, ArithmeticExpression) case multiplication(ArithmeticExpression, ArithmeticExpression)}
  • struct&&class``struct传递是值传递;class是引用传递

In Swift, many basic data types such as String, Array, and Dictionary
are implemented as structures. This means that data such as strings,
arrays, and dictionaries are copied when they are assigned to a new
constant or variable, or when they are passed to a function or method.

  • ===和!==用来比较对象
  • lazy懒加载,使用的时候再加载
  • 仅读属性。将get、set移除,直接返回,可以用来实例单例

struct Cuboid { var width = 0.0, height = 0.0, depth = 0.0 var volume: Double { return width * height * depth }}
  • mutating用来修改struct和enum。因为二者是不能不能被自己的实例对象修改属性变量

struct Point { var x = 0.0, y = 0.0 mutating func moveBy(x deltaX: Double, y deltaY: Double) { x += deltaX y += deltaY }}var somePoint = Point(x: 1.0, y: 1.0)somePoint.moveBy(x: 2.0, y: 3.0)print("The point is now at (\(somePoint.x), \(somePoint.y))")// Prints "The point is now at "**注意let不能修改**let fixedPoint = Point(x: 3.0, y: 3.0)fixedPoint.moveBy(x: 2.0, y: 3.0)// this will report an error

enum TriStateSwitch { case off, low, high mutating func next() { switch self { case .off: self = .low case .low: self = .high case .high: self = .off } }}var ovenLight = TriStateSwitch.lowovenLight.next()// ovenLight is now equal to .highovenLight.next()// ovenLight is now equal to .off
  • classstatic都可修饰类方法,class修饰的可以被子类重写
  • 脚标(Subscripts)。可以类、结构体、枚举定义脚标从而快速访问属性等。

subscript(index: Int) -> Int { get { // return an appropriate subscript value here } set { // perform a suitable setting action here }}
  • final可以防止被子类继承。
  • deinit 只有类有,当类销毁时会被回调。
  • ?是否为nil;??设置默认值
  • 异常处理

do { try expression statements} catch pattern 1 { statements} catch pattern 2 where condition { statements}

try!明确知道不会抛出异常。

  • defer代码块执行后必须执行的代码

func processFile(filename: String) throws { if exists { let file = open defer { close } while let line = try file.readline() { // Work with the file. } // close is called here, at the end of the scope. }}
  • is判断是否某个类型;as?as!转换至某个类型
  • Extensions与oc的category类似

extension SomeType: SomeProtocol, AnotherProtocol { // implementation of protocol requirements goes here}
  • Protocols

protocol SomeProtocol { // protocol definition goes here}

使用

struct SomeStructure: FirstProtocol, AnotherProtocol { // structure definition goes here}

protocol SomeProtocol { var mustBeSettable: Int { get set }//有get和set方法 var doesNotNeedToBeSettable: Int { get }//只有get方法,不能单独设置,只能在初始化的时候设置。}
  • 检查是否遵循了某个协议

    • is遵循了返回true,否则返回false
    • as?遵循了正常转换,否则为nil
    • as!遵循了正常转换,否则抛出错误
  • 泛型;通常使用TUV等大写字母表示。

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // function body goes here}
  • weakunowned(与oc的unsafe_unretained类似)解决循环应用的问题

weak var tenant: Person?
  • 访问控制权限(open > public > interal > fileprivate >
    private)

    • private

      private
      访问级别所修饰的属性或者方法只能在当前类里访问。(注意:Swift4
      中,extension 里也可以访问 private 的属性。)

    • fileprivate

      fileprivate 访问级别所修饰的属性或者方法在当前的 Swift)

    • internal(默认访问级别,internal修饰符可写可不写)

      internal
      访问级别所修饰的属性或方法在源代码所在的整个模块都可以访问。如果是框架或者库代码,则在整个框架内部都可以访问,框架由外部代码所引用时,则不可以访问。如果是
      App 代码,也是在整个 App 代码,也是在整个 App 内部可以访问。

    • public

      可以被任何人访问。但其他 module 中不可以被 override 和继承,而在
      module 内可以被 override 和继承。

    • open

      可以被任何人使用,包括 override 和继承。

  • AVAudioSessionCategoryRecord,只支持音频录制。不支持播放。
  • AVAudioSessionCategoryPlayAndRecord,支持音频播放和录制。音频的输入和输出不需要同步进行,也可以同步进行。需要音频通话类应用,可以使用这个
    Category。
  • AVAudioSessionCategoryAudioProcessing,只支持本地音频编解码处理。不支持播放和录制。
  • AVAudioSessionCategoryMultiRoute,支持音频播放和录制。允许多条音频流的同步输入和输出。(比如USB连接外部扬声器输出音频,蓝牙耳机同时播放另一路音频这种特殊需求)

脚本运行

# 解锁keychain,以便可以正常的签名应用,PASSWORD="YourPassword"security unlock-keychain -p $PASSWORD ~/Library/Keychains/login.keychain# 获取设备的UDID,用到了之前的 libimobiledeviceUDID=$(idevice_id -l | head -n1)# 真机运行测试xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination "id=$UDID" test# 模拟器运行测试#xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination "platform=iOS Simulator,name=iPhone X" test

脚本运行完成后,同样手机/模拟器上会出现一个无图标的 WebDriverAgent
应用,启动之后,马上又返回到桌面。此时终端会输出 IP 地址和端口。

最近一年一直在做IPC
Camera的iOS客户端开发。和音频打交道,必须要弄清楚AVAudioSession。先看下苹果的官方图:

安装 python(本例中的外挂程序使用 python3)

脚本语言 python 用来编写模拟的用户操作。

brew install python3

/* set session category and mode with options */- setCategory:(NSString *)category mode:(NSString *)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError API_AVAILABLE, watchos, tvos;

微信跳一跳最近很火,外挂代练什么的也越来越多。作为一只程序猿,对外挂的原理产生了强烈的好奇心,于是埋头研究了一阶段,注意到了
WebDriverAgent 这套 Facebook 出品的自动化测试框架。

遍历:

需要注意一下,选择支持在静音键切到静音状态以及锁屏键切到锁屏状态下仍然可以播放音频
Category 时,必须在应用中开启支持后台音频功能,详见
UIBackgroundModes。

本文为 ReinhardHuang 原创,著作权归作者所有。

如需转载请联系作者,并取得作者的明示同意后方可转载。

let greeting = "Hello, world!"let index = greeting.index ?? greeting.endIndexlet beginning = greeting[..<index]// beginning is "Hello"

图片 6Audio
Session

设备连接和弹窗处理

App 可能弹出一些弹框,比如通知、相机的权限请求,或者是 App
给用户的某些提示,总体上来讲,这些弹框的出现无法预估。因此需要对于弹框进行统一处理。

# -*- coding: utf-8 -*-import wdaimport timebundle_id = 'your_app_bundle_id_here'c = wda.Client('http://localhost:8100')s = c.session(bundle_id)def alert_callback: btns = set([u'不再提醒', 'OK', u'知道了', 'Allow', u'允许']).intersection(session.alert.buttons if len == 0: raise RuntimeError("Alert can not handled, buttons: " + ', '.join(session.alert.buttons session.alert.click(lists.set_alert_callback(alert_callback)
  • Dictionary可以遍历,并且支持移除操作
  • AVAudioSessionCategoryAmbient,只支持音频播放。这个
    Category,音频会被静音键和锁屏键静音。并且不会打断其他应用的音频播放。

  • AVAudioSessionCategorySoloAmbient,这个是系统默认使用的
    Category,只支持音频播放。音频会被静音键和锁屏键静音。和AVAudioSessionCategoryAmbient不同的是,这个会打断其他应用的音频播放

  • AVAudioSessionCategoryPlayback,只支持音频播放。你的音频不会被静音键和锁屏键静音。适用于音频是主要功能的APP,像网易云这些音乐app,锁屏后依然可以播放。

安装 WebDriverAgent

WebDriverAgent 是 Facebook 推出的一款 iOS
移动测试框架,能够支持模拟器以及真机。

WebDriverAgent 在 iOS 端实现了一个 WebDriver server ,借助这个 server
我们可以远程控制 iOS
设备。你可以启动、杀死应用,点击、滚动视图,或者确定页面展示是否正确。

从 github 克隆 WebDriverAgent 的源码。

git clone https://github.com/facebook/WebDriverAgent.git

运行初始化脚本,确保之前已经安装过 Carthage。

cd WebDriverAgent./Scripts/bootstrap.sh

脚本完成后可以打开工程文件,根据自己的开发者证书对
bundleid、证书等信息做下配置。

运行 WebDriverAgent 相当于在你的目标设备启动了一个服务器,它接收来自 WDA
客户端的脚本请求并执行,实现启动、杀死应用,点击、滚动视图等操作。

运行 WebDriverAgent 有两种方式,一种是打开 Xcode
运行,一种是使用脚本运行。

case "a", "A":case 1..<5:case (-2...2, -2...2)case let  where x == y
  • Interrupts non-mixable apps audio:是否打断不支持混音播放的APP
  • Silenced by the Silent switch:是否会响应手机静音键开关
  • Supports audio input:是否支持音频录制
  • Supports audio output:是否支持音频播放

安装 libimobiledevice

libimobiledevice
是一个使用原生协议与苹果iOS设备进行通信的库。通过这个库我们的 Mac OS
能够轻松获得 iOS 设备的信息。

brew install --HEAD libimobiledevice

使用方法:

# 查看 iOS 设备日志idevicesyslog# 查看链接设备的UDIDidevice_id --list# 查看设备信息ideviceinfo# 获取设备时间idevicedate# 获取设备名称idevicename# 端口转发iproxy XXXX YYYY# 屏幕截图idevicescreenshot

发表评论

电子邮件地址不会被公开。 必填项已用*标注