iOS开发针对对Masonry下的FPS优化讨论

今天博客的内容就系统的讨论一下Masonry对FSP的影响,以及如何更好的使用Masonry。如果你对iOS开发足够熟悉的话,那么对Masonry框架应该不陌生。简单的说,Masonry的诞生让AutoLayout的使用更为优雅,让控件的布局更为方便。使用辩证的观点来看一个事物的话,凡事都有两面性,Masonry的使用也不例外。Masonry框架的使用不当会直接影响当UI的FPS。今天我们就来讨论一下在使用Masonry时的一些误区,看一下那些影响性能的使用方式。本篇博客我们依然会依托于Demo来叙述的一些东西。

之前写过一篇文章是专门来介绍Masonry框架的,并且对该框架的源码进行了相关解析,详细内容请移步于《iOS开发之Masonry框架源码解析》。

 

一、Demo综述

1.运行效果

先入为主,本篇博客的内容依然是依托于我们特意为本篇博客所打造的Demo的,首先我们先来看一下Demo运行起来是怎样的效果,通过Demo我们可以看到那些问题,以及这些问题是如何被解决的。下方就是我们本篇博客所涉及Demo的运行效果。

从下方的运行效果不难看出,我们是分了6种情况来观察和判断Masonry的各种使用方式对FPS的影响如何。上方通过六个SegmentControl可以去切换Cell的布局方式。当然每种布局方式所呈现出来的Cell是相同的。这样也是做实验时保持实验项改变其他项保持一致的原则。我们可以通过右下方FPS指示器来直观的感受一下FPS的变化趋势。下方这个FPS显示控件是从我们之前的Demo中拿过来的。之前的Demo也是关于FPS优化的,只不过是关于Cell高度动态计算的FPS优化,详情请移步于《iOS开发之多种Cell高度自适应实现方案的UI流畅度分析》。

下方Cell中所显示的数据时随机生成的,左边的Image也是随机取的。右边的Title和Detail都是NSAttributedString并且下方的有些Detail有可能为空。如果某一条的Detail为空,那么该条Detail下方的所有内容的布局上移。稍后会详细的介绍该Demo以及其中的技术点。

%title插图%num  %title插图%num

2、模拟网络请求

上面Cell中显示的数据是通过模拟网络数据来获取的,下方就是我们的模拟网络层的相关代码。毕竟是Demo,并且Demo的重点不在网络层上,下方就简单的写了一下,代码比较简单。就是一个单例+一个模拟数据随机生成的方法,然后把这个随机生成的数据通过Block回调到网络层的使用者上。具体代码如下所示:

%title插图%num

 

3、上述Cell的基类XBaseTableViewCell

上面每种Segment中的Cell都是一种独立的Cell类型,不过这些Cell有一个共同的父类。而这个父类就负责来处理这些Cell所共用的逻辑。下方的这个XBaseTableViewCell就是上述显示的所有Cell的基类。其中声明并初始化了Cell上的所有控件。并且提供了相关的设置值的方法。

从下方的代码中不难看出,有两个方法是需要子类进行重写的,一个是给控件添加布局的方法addLayoutSubviews, 另一个就是更新布局的方法updateLayoutSubviews方法。每个子类中都会对这两个方法进行重写并给出不同的布局方式。

%title插图%num

 

4、Segment中切换Cell的代码

下方是在相应的VC中的点击SegmentControl的逻辑代码, 点击不同的Segment会选择不同的Cell然后刷新TableView,代码比较简单就不做过多的赘述了。

%title插图%num

 

 

二、对上述各种的布局方式进行分析

接下来要做的事情就是分析一下每种布局方式对FSP的影响,下方会对不同的布局情况使用Instrument进行分析并看一下具体的数据。下方分别讨论了只使用Masonry的Update操作、Remake操作、先Make后Update、Frame操作以及先Make后Frame操作。

1、update

首先我们来看一下update操作。也就是使用update直接给控件赋值,这是比较偷懒的一种操作。因为在我们的Demo中在设置cell的值时会更新一些控件的UI布局,所有我们索性就直接使用Masonry的update,直接给控件添加约束。在Masonry中的update操作有个特点,就是update一个约束会先在已添加的约束数组中找到该约束,然后更新该约束,如果找不到就install添加相应的约束。从这个update的功能来看其效率是比较低的。

我可以先看一下代码实现,在子类XUpdateLayoutTableViewCell中,重写了addLayoutSubviewsupdateLayoutSubviews两个方法。在updateLayoutSubviews方法中,为所有的控件使用update的方式添加约束。下方这样写会在每次设置值的时候都会调用下方的updateLayoutSubviews方法,这样就会更新cell上的控件的所有布局,当然,不建议这样去做,因为这样会更新那些不需要更新的约束。之所以今天罗列出来,是因为在开发中下方的问题确实存在,也许是因为时间紧张,也许是因为其他原因导致的下方这种代码实现。

%title插图%num

 

我们先来使用Instruments跑一下上述的Demo,然后直观的感受一下该Demo的Core Animation的直观表现。下方就是我们将SegmentControl切换到Update时所对应的FPS数据。从下方的数据我们不难看出,直接用Update添加更新约束这种做法是比较影响FPS的。当然,Cell中还会使用到属性字符串,这个我们稍后会讨论一下的。

%title插图%num

 

我们可以来跑一下Update状态下的Time Profile。如下所示,从下方的结果中不难看出,在Cell更新数据时,有两块的操作比较耗时。一个是Masonry的update操作,另一个则是Label设置NSAttributedString的操作。因为我们使用的每个Label都会赋值一个属性字符串,这个是比较耗时的操作。还有一个要明确一点的是,属性字符串的创建和生成并不会占用多少时间,而属性字符串的赋值和渲染所占用的时间是比较多的,这一点从下方的Time Profile中也是不难看出的。

%title插图%num

 

2、remake

接下来我们在来看一下Remake操作,从下方的Core Animation的结果中不难看出,其所表现出来的效果还不如使用Update操作呢。下方的FPS比Update要低一些,这也与remake自身的操作有关系,remake从字面意思来看就是重新制作,如果之前已经添加过约束的话就先移除掉,然后再添加新的约束。

%title插图%num

 

下方是Remake所对应的Time Profile,从结果中我们可以看出布局更新占用了66.6%的耗时,而且33%的install耗时中uninstall占用了10%左右的开销。在Masonry中remake效率是*低的。稍后我们会继续进行讨论。

%title插图%num

 

3、make + update

讨论完update和remake, 我们来讨论一下使用Masonry的常规做法。就是使用make来初始化控件的布局,使用update来更新所需要更新的约束。因为代码比较简单,就不一一往上贴了,但是跑一下使用Instrument跑一下还是很有必要的。下方是make + update 的方式的Core Animation所跑出来的结果。但从下方的FSP结果来看,还是要比之前只使用update或者remake的效果要好一些的,不过下方的FPS还是不高,稍后我们会将下方的数据进行细化。

该部分的Time Profile就不跑了,因为设置值的时候我们依然采用的Update来更新的约束,只不过不是更新所有的约束,而是更新那些只需要更新的约束。因为更新的约束的量会少一些,所有FPS的表现效果会比之前更新所有的约束会更好一些。make + update的方式会是FPS稍微改善一些,但是从下方的图中我们可以看出,改善的并不是特别好。

%title插图%num

 

4、frame + frame

接下来,我们就不用Masonry来布局了,我们直接使用Frame布局。因为Autolayout*终仍然会转换为Frame布局的,很显然Frame布局在性能方面是优于Autolayout布局的。接下来我们就来使用Frame布局然后使用Frame更新。下方的FPS还算说得过去,不过还不是满格,其大部分原因就是因为NSAttrubitedString的原因了。

%title插图%num

 

我们可以看一下更新Frame的Time Profile,如下所示。从下方的截图中,我们不难看出update frame的时间占比只占到了2.5%,之前更新约束能占到60%左右,可以看到使用Frame布局的好处。从下方的分析结果中不难看出,现在影响FPS主要因素已经从更新布局转化到了AttributeString的设置。这也是上述FPS没有满格的原因。

%title插图%num

 

5、make + frame

Masonry的诞生就是为了方便控件布局的,Frame布局不够灵活,适配起来比较繁琐,所以才有了AutoLayout。不过虽然AutoLayout可以很方便的适配屏幕,可是其性能方面表现的不是特别好。那么我们可不可以将两者进行结合呢。也就是说使用make来初始化控件的布局,使用Frame来更新布局。当然这一过程不是简单的在设置值的时候更新一下Frame就可以的,因为在Cell设置值的时候去更新Frame是没用的,因为更新完Frame后,在渲染显示的时候,还是会按照AutoLayout的布局来显示的。我们需要做的是将Frame布局放到Autolayout布局之后,此处我们要做的就是将更新Frame的相关代码放到下一个Runloop中来执行。更新Frame代码如下:

%title插图%num

 

在cell中是make初始化控件布局,使用Frame更新布局,和Frame+Frame的方式差不多,只不过是使用Masonry布局时,在首屏加载的时候不如Frame布局,以后更新是一样的。下方是使用Masonry+Frame的形式的Core Animation的结果。效果虽然比上一部分会稍微差一些,但是*终的效果还是满Ok的。

%title插图%num

 

 

 三、总结

本篇博客只讨论Masonry的布局方式对FPS的影响,至于上述的NSAttributeString的问题不做过多赘述了。如果根据业务需要,有许多富文本的展示影响了FPS的话,那么可以采用其他的方式来进行优化,比如使用AsynDisplayKit所提供的相关Node进行显示等等。在博客的结尾,还是有必要做一个总结的。

下方是我们在代码中更为细化的数据,从数据中不难看出Remake的性能是*差的,所以我们在使用Masonry时尽量要少使用Remake。对控件的更新只一味的选择使用Update也不是一个好的选择,如果要使用Masonry框架还要对控件进行布局更新的话,*好是把那些不变的约束和需要更新的约束分开。使用make来添加相关约束,使用update来修改需要更新的约束。当然使用Frame布局的性能会好一些,不过布局过程过于繁琐不便于进行屏幕的适配。当然也可以使用Masonry进行布局使用Frame进行布局的更新,当然需要注意的是Frame布局更新的时机,需在Autolayout加载的时机后进行。

下方是进行了统一的数据统计,当然是针对本篇博客所对应的Demo的。下方表格中统计了一次更新cell布局所采用的不同方式的平均时间,从下方的数据中我们不难看粗Remake的更新布局用时*多,消耗了12+ms, 而Update所有的约束用时也是不少,一次更新布局使用了9+ms。而只更新需要更新的布局用时7+ms, 稍微要比更新所有的布局要好一些。当然直接修改Frame的用时*少,只用了0.06+ms的时间,从该数据可以直观的感受到Frame布局的效率性。

而右边还给出了一个属性字符串的创建和赋值的用时,其中我们可以看到,属性字符串的创建耗时并不是太多,而比较耗时的是属性字符串的赋值,每次赋值占用了0.7ms, 如果是10个的话,那么赋值时间就是7ms, 如果属性字符串的内容再复杂一些,那么用时肯定会比这个高。当然我们可以使用第三方提供的一些控件和方法将这部分时间给优化掉,这个可以放到以后再讨论。

今天的博客就到这儿吧,目的是在使用Masonry时要合理的进行使用,有必要时,可以使用Frame进行布局。

%title插图%num

 

iOS开发 – 在状态栏显示FPS,CPU和内存信息

前言
今天在用Instruments分析App的时候,总感觉看起来不太直观。到Github上找了找,发现几乎都是只显示FPS的,而且效果也不是我想要的。于是就自己写了个

源码地址
LHPerformanceStatusBar
效果

%title插图%num

字体颜色会根据阈值进行颜色变化,性能差的时候字体会变成红色,性能一般的时候会变成橘黄色,阈值可配。

集成
CocoaPod集成

pod LHPerformanceStatusBar

使用
– (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[LHPerformanceMonitorService run];
}

原理
FPS的计算
CoreAnimation有一个很好用的类CADisplayLink,这个类会在每一帧绘制之前调用,并且可以获取时间戳。于是,我们只要统计出,在1s内的帧数即可。

 

– (void)envokeDisplayLink:(CADisplayLink *)displayLink{
if (_lastTimestamp == -1) {
_lastTimestamp = displayLink.timestamp;
return;
}
_countPerFrame ++;
NSTimeInterval interval = displayLink.timestamp – _lastTimestamp;
if (interval < 1) {
return;
}
_lastTimestamp = displayLink.timestamp;
CGFloat fps = _countPerFrame / interval;
//…
}

内存和CPU信息的获取
CPU和内存的获取采用了mach头文件中的方法,调用了底层API,采用C方式来获取。

iOS计算FPS

原理:CADisplayLink是在每一帧绘制之前调用,并且可以获取时间戳。

1 – (void)viewDidLoad {
2     [super viewDidLoad];
3
4     _lastTimestamp = -1;
5     _frameCount = 0;
6     CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(calFPS:)];
7     [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
8 }
9
10 //计算1S内帧数
11 -(void)calFPS:(CADisplayLink *)displayLink
12 {
13     if(_lastTimestamp == -1){
14         _lastTimestamp = displayLink.timestamp;
15         return;
16     }
17
18     _frameCount++;
19
20     NSTimeInterval interval = displayLink.timestamp – _lastTimestamp;
21
22     if(interval < 1){
23         return;
24     }
25
26     _lastTimestamp = displayLink.timestamp;
27
28     CGFloat fps = _frameCount / interval;
29
30     NSLog(@”%f”, fps);
31
32     _frameCount = 0;
33 }

iOS测试fps方式

Measure Graphics Performance

Extensive use of graphics in your iOS app can make your app stand out from your competitors. But unless you use graphics resources responsibly, your app will slow down, reduce battery life, and look mediocre no matter how good the content you are trying to render. For optimal graphic performance:

  • Ensure that the frame rate of animations is sufficient for providing a fluid visual experience without negatively impacting app performance. 
  • Reduce the number of views your app uses. 
  • Reduce the use of opacity, such as in views that exhibit a translucent blur. If you need to use opacity, avoid using it over views that are updated frequently. Otherwise, energy cost is magnified, as both the background view and the translucent view must be updated whenever content changes. 
  • Draw to smaller portions of the screen—only the portions that are changing. To do this, use a href=”” needsToDrawRect: /a or a href=”” getRectsBeingDrawn:count: /a to identify the specific area to update, and pass the result to drawRect:
  • Eliminate drawing when your app or its content is not visible; for example, when your app is in the background, or when its content is obscured by other views, clipped, or offscreen. 
  • Eliminate drawing during window resizing. 

Measure Core Animation Graphics Performance in iOS

The Core Animation profiling template uses the Core Animation and Time Profiler instruments to measure your iOS app’s graphics and CPU performance. This template provides a quick and lightweight starting point for measuring the number of frames per second rendered by your app. It allows you to quickly see where your app renders fewer frames than expected. By correlating what you were doing at the time the sample was taken, you can identify areas of your code that need to be optimized.

NOTE

Use the Core Animation template to profile an app on a physical device. Profiling an app in the iOS Simulator will not produce true real-world results.

Do not use the Core Animation instrument to measure OpenGL ES performance.

To measure frame rate

  1. Connect your iOS device to your Mac.
  2. Launch Instruments.
  3. When the profiling template selection dialog appears, click Core Animation.
  4. Choose your iOS device and app from the target device and process lists.
  5. Click Choose to create a trace document.
  6. Click the Record button () in the toolbar or press Command-R to begin recording.
  7. Use your app normally.
  8. Click the Stop button () or press Command-R again when complete.
  9. Examine the collected data.

    The detail pane shows the frame rate for each recorded sample.

Correlate Interactions with Results

After capturing data with the Core Animation instrument, you may see spikes in the timeline pane where the frame rate of the app becomes appreciably better. In some cases, however, spikes can be caused by changing the device between landscape and normal orientation—and if you don’t know this, you might spend time trying to find what caused the performance increase. One way to correlate interactions with results is to insert flags into the timeline when you performed certain events. See Set Flags.

Debugging Options

Core Animation contains a number of useful debugging options in the display settings area of the inspector pane. You do not need to be running a trace to use these options on your iOS device.

  • Color Blended Layers. Shows blended view layers. Multiple view layers that are drawn on top of each other with blending enabled are highlighted in red. Reducing the amount of red in your app when this option is selected can dramatically improve your app’s performance. Blended view layers often cause slow table scrolling. 
  • Color Hits Green and Misses Red. Marks views in green or red. A view that is able to use a cached rasterization is marked in green. 
  • Color Copied Images. Shows images that are copied by Core Animation in blue. 
  • Color Immediately. Removes the 10 ms delay when performing color-flush operations. 
  • Color Misaligned Images. Places a magenta overlay over images where the source pixels are not aligned to the destination pixels. 
  • Color Offscreen-Rendered Yellow. Places a yellow overlay over content that is rendered offscreen. 
  • Color OpenGL Fast Path Blue. Places a blue overlay over content that is detached from the compositor. 
  • Flash Updated Regions. Colors regions on your iOS device in yellow when those regions are updated by the graphics processor. 

Measure OpenGL Activity in iOS

The OpenGL ES Analysis profiling template uses the OpenGL ES Analyzer and GPU Driver instruments to measure and analyze OpenGL ES activity in your iOS app in order to detect correctness and performance problems. It also recommends how to address found problems.

To measure OpenGL Activity

  1. Connect your iOS device to your Mac.
  2. Launch Instruments.
  3. When the profiling template selection dialog appears, click OpenGL ES Analysis.
  4. Choose your iOS device and app from the target device and process lists.
  5. Click Choose to create a trace document.
  6. Click the Record button () in the toolbar or press Command-R to begin recording.
  7. Use your app normally, exercising your OpenGL ES graphics code.
  8. After a few seconds of measurement, click the Stop button () or press Command-R again.
  9. Examine the collected data.

Errors are listed in the detail pane sorted by their severity. Red squares indicate the most severe problems, and orange triangles indicate less severe problems. A recommendation and the stack trace are displayed in the extended detail area of the inspector pane for the issue selected in the detail pane.

Find Graphics-Related Bottlenecks in iOS or OS X

The GPU Driver profiling template uses the GPU Driver and Time Profiler instruments to measure app performance and provides more than just the number of frames per second your app renders.

To look for graphics-related bottlenecks

  1. Launch Instruments.
  2. When the profiling template selection dialog appears, click GPU Driver.
  3. Choose your device and app from the target device and process lists.
  4. Click Choose to create a trace document.
  5. Click the Record button () in the toolbar or press Command-R to begin recording.
  6. Use your app normally.
  7. Click the Stop button () or press Command-R again when complete.
  8. Press Command-3 to display the extended detail area of the inspector pane.
  9. Examine the collected data.

The individual statistics displayed by the GPU Driver instrument in the timeline pane can be enabled and disabled in the record settings area of the inspector pane.

The detail pane displays all of the gathered information for a specific sample. Select a row of data to view the sample’s statistics in the extended detail area of the inspector pane.

Bottlenecks for an OpenGL app are usually GPU or CPU bottlenecks. GPU bottlenecks occur when the GPU forces the CPU to wait for information because the GPU has too much information to process. CPU bottlenecks occur when the GPU has to wait for information from the CPU before the GPU can process it. CPU bottlenecks can often be fixed by changing the underlying logic of your app to create a better overall flow of information to the GPU. Common bottlenecks include:

  • Geometry limited. If Tiler Utilization is high, examine your vertex shader processes. 
  • Pixel limited. If Rendered Utilization is high, examine your fragment shader processes. 
  • CPU limited. If Tiler Utilization and Rendered Utilization are both low, the performance bottleneck may not be in your OpenGL code. Examine your code’s overall logic.

Appium+Python3+iOS真机环境搭建

Appium 是一个自动化测试开源工具,支持 iOS 平台和 Android 平台上的原生应用,web 应用和混合应用。
1.环境配置
Mac系统自带的Python版本是2.7,当前要用新版,这里我选择的Python版本是3.5。下载安装。
删除掉mac自带的python2.7
sudo rm -R /System/Library/Frameworks/Python.framework/Versions/2.7
加上:
alias python=’/Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5’
保存退出,执行:
source ~/.bash_profile
2,把appium和selenium安装到python3.5的set-packages中
3,appium运行时jdk的安装
访问Oracle官网 http://www.oracle.com
jdk下载的地方https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html
下载安装jdk,打开Finder,可以在下图所示的路径中找到安装好的jdk 1.8.0_40.jdk
%title插图%num

Contents下的Home文件夹,是该JDK的根目录

%title插图%num

打开终端配置jdk环境变量:
如果你是*次配置环境变量,可以使用“touch .bash_profile” 创建一个.bash_profile的隐藏配置文件(如果你是为编辑已存在的配置文件,则使用”open -e .bash_profile”命令):
输入如下配置:

%title插图%num

JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home
PATH=$ JAVA_HOME/bin:$ PATH:.
CLASSPATH=$ JAVA_HOME/lib/tools.jar:$ JAVA_HOME/lib/dt.jar:.
export JAVA_HOME
export PATH
export CLASSPATH
然后保存关闭该窗口。
使用”source .bash_profile”使配置生效
输入 echo $ JAVA_HOME 显示刚才配置的路径
测试输入java -version
4,sdk环境变量的配置
在命令行输入 open -e .bash_profile , 打开.bash_profile文件,输入如下配置:
export ANDROID_HOME=/Library/Java/sdk
export PATH=$ {PATH}: $ {ANDROID_HOME}/tools
export PATH=$ {PATH}: $ {ANDROID_HOME}/platform-tools
export PATH=$ {PATH}: $ {ANDROID_HOME}/build-tools

export PATH=${PATH}:/Library/Java/sdk/platform-tools:/Library/Java/sdk/tools
%title插图%num

然后保存并关闭。
在命令行输入source .bash_profile ,使之生效。
5,ios的元素定位工具
a.Appium自带的Inspector工具
b.Macaca的app-inspector工具,具体地址:https://macacajs.github.io/app-inspector/cn/
c.Xcode自带的accessibility-inspector工具
具体地址:http://blog.csdn.net/icetime17/article/details/42211915
d.Appium团队提供的appium-ios-inspector
具体地址:https://github.com/mykola-mokhnach/Appium-iOS-Inspector
e.通过flex插件,具体地址:https://github.com/Flipboard/FLEX

ios-deploy时,终端构建失败

安装ios-deploy时,终端构建失败:
ios-deploy是一个从mac电脑把app打包到手机的工具
安装:sudo npm install -g ios-deploy –unsafe-perm=true

安装出现问题:
** BUILD FAILED **
The following build commands failed:
CompileC /usr/local/lib/node_modules/ios-deploy/build/ios-deploy.build/Release/ios-deploy-lib.build/Objects-normal/x86_64/libios_deploy.o /usr/local/lib/node_modules/ios-deploy/src/ios-deploy-lib/libios_deploy.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
(1 failure)
npm ERR! code ELIFECYCLE
npm ERR! errno 65
npm ERR! ios-deploy@1.11.0 preinstall: ./src/scripts/check_reqs.js && xcodebuild`
npm ERR! Exit status 65
npm ERR!
npm ERR! Failed at the ios-deploy@1.11.0 preinstall script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

解决方式:
需要把macOS,ios,xcode版本号升级为一致,因为用命令安装的时候默认安装*新的ios-deploy,而电脑的配置不相符:

升级之后执行
终端运行:sudo xcode-select –switch /Applications/Xcode.app/Contents/Developer
在执行:sudo npm install -g ios-deploy –unsafe-perm=true
open /Applications/Xcode.app/Contents/Developer

安装成功

appium+iOS元素的定位方式

查找元素的顺序,从快到慢:
查找元素的顺序,从快到慢:
ios_predicate >> accessibility_id >> class_name >>xpath
(论坛比较多的说法是class_name>>accessibility_id,在这里我们姑且认为它们的速度是一样的。)
1,ios_predicate:OS 的 UI 自动化中,使用原生支持的Predicate定位方式是*好,可支持元素的单个属性和多个属性定位,强烈推荐使用
driver.find_element_by_ios_predicate(“value == ‘ClearEmail’”)
driver.find_element_by_ios_predicate(“type == ‘’ AND value == ‘’)
https://www.jianshu.com/p/a6c2d72fe704
2,accessibility_id
替代以前的name定位方式,在 iOS 上,主要使用元素的label或name(两个属性的值都一样)属性进行定位,如该属性为空,也是不能使用该属性。
driver.find_element_by_accessibility_id(‘ClearEmail’)
3、class_name
使用元素的type属性定位,特别注意该属性的唯一性!class_name唯一的情况并不多,一般情况下用不上。
driver.find_element_by_class_name(‘XCUIElementTypeButton’)
4、xpath
由于 iOS 10开始使用的 XCUITest 框架原生不支持,定位速度很慢,所以官方现在不推荐大家使用,也有其他替代的定位方式可使用。
1)使用*对路径定位:
driver.find_element_by_xpath(’/XCUIElementTypeApplication/XCUIElementTypeButton’)
2)使用相对路径定位
driver.find_element_by_xpath(’//XCUIElementTypeButton’)
3)通过元素的索引定位
driver.find_element_by_xpath(’//XCUIElementTypeButton[index]’)
4.通过元素的属性定位
一种属性:
driver.find_element_by_xpath(”//className[@value=‘ClearEmail’]”)
两种属性:
driver.find_element_by_xpath(“//className[@value=‘ClearEmail’][@ visible =true]”)
部分属性(*强大):driver.find_element_by_xpath(“//className[contains(@value,‘ClearEmail’)]”)
5,iOSNsPredicateString
仅支持 iOS 10或以上,可支持元素的单个属性和多个属性定位,推荐使用。
一种属性:MobileBy.iOSNsPredicateString(“type == ‘XCUIElementTypeButton’”)
两种属性:MobileBy.iOSNsPredicateString(“type == ‘XCUIElementTypeButton’ AND label == ‘更多信息’”)

IOS学习之——CoreLocation定位的使用

1 在移动互联网时代,移动app能解决用户的很多生活琐事,比如
2 导航:去任意陌生的地方
3 周边:找餐馆、找酒店、找银行、找电影院
4
5 在上述应用中,都用到了地图和定位功能,在iOS开发中,要想加入这2大功能,必须基于2个框架进行开发
6 Map Kit :用于地图展示
7 Core Location :用于地理定位
8
9 2个热门专业术语
10 LBS :Location Based Service
11 SoLoMo :Social Local Mobile(索罗门)
12
13 CoreLocation框架使用前提
14 导入框架
15
16 导入主头文件
17 #import <CoreLocation/CoreLocation.h>
18
19 CoreLocation框架使用须知
20 CoreLocation框架中所有数据类型的前缀都是CL
21 CoreLocation中使用CLLocationManager对象来做用户定位
22
23 CLLocationManager
24
25 CLLocationManager的常用操作
26 开始用户定位
27 – (void)startUpdatingLocation;
28
29 停止用户定位
30 – (void) stopUpdatingLocation;
31
32 当调用了startUpdatingLocation方法后,就开始不断地定位用户的位置,中途会频繁地调用代理的下面方法
33 – (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
34 locations参数里面装着CLLocation对象
35
36
37 CLLocation
38
39 CLLocation用来表示某个位置的地理信息,比如经纬度、海拔等等
40 @property(readonly, nonatomic) CLLocationCoordinate2D coordinate;
41 经纬度
42
43 @property(readonly, nonatomic) CLLocationDistance altitude;
44 海拔
45
46 @property(readonly, nonatomic) CLLocationDirection course;
47 路线,航向(取值范围是0.0° ~ 359.9°,0.0°代表真北方向)
48
49 @property(readonly, nonatomic) CLLocationSpeed speed;
50 行走速度(单位是m/s)
51
52 用- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location方法可以计算2个位置之间的距离
53
54
55 CLLocationManager
56
57 @property(assign, nonatomic) CLLocationDistance distanceFilter;
58 每隔多少米定位一次
59
60 @property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;
61 定位精确度(越精确就越耗电)
62
63 CLLocationCoordinate2D
64
65 CLLocationCoordinate2D是一个用来表示经纬度的结构体,定义如下
66 typedef struct {
67         CLLocationDegrees latitude; // 纬度
68         CLLocationDegrees longitude; // 经度
69 } CLLocationCoordinate2D;
70
71 一般用CLLocationCoordinate2DMake函数来创建CLLocationCoordinate2D
72
73
74 用户隐私的保护
75
76 从iOS 6开始,苹果在保护用户隐私方面做了很大的加强,以下操作都必须经过用户批准授权
77 要想获得用户的位置
78 想访问用户的通讯录、日历、相机、相册等等
79
80 当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权
81
82 从iOS 8开始,用户定位分两种情况
83 总是使用用户位置:NSLocationAlwaysUsageDescription
84 使用应用时定位:NSLocationWhenInUseDescription
85
86 当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权
87
88
89 CLGeocoder
90
91 使用CLGeocoder可以完成“地理编码”和“反地理编码”
92 地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
93 反地理编码:根据给定的经纬度,获得具体的位置信息
94
95 地理编码方法
96 – (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
97
98 反地理编码方法
99 – (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;
100
101
102 CLGeocodeCompletionHandler
103
104 当地理\反地理编码完成时,就会调用CLGeocodeCompletionHandler
105 typedef void (^CLGeocodeCompletionHandler)(NSArray *placemarks, NSError *error);
106 这个block传递2个参数
107 error :当编码出错时(比如编码不出具体的信息)有值
108 placemarks :里面装着CLPlacemark对象
109
110 CLPlacemark
111
112 CLPlacemark的字面意思是地标,封装详细的地址位置信息
113 @property (nonatomic, readonly) CLLocation *location;
114 地理位置
115
116 @property (nonatomic, readonly) CLRegion *region;
117 区域
118
119 @property (nonatomic, readonly) NSDictionary *addressDictionary;
120 详细的地址信息
121
122 @property (nonatomic, readonly) NSString *name;
123 地址名称
124
125 @property (nonatomic, readonly) NSString *locality;
126 城市

iOS学习——自动定位

*近在项目中需要做自动定位功能,就是你在参加会议通过扫描二维码签到的时候自动定位并将你的定位信息在签到中上传,这样可以避免我们进行假签到。在这个功能中,主要用到的是系统自带的定位模块,首先我们是需要配置定位功能的参数,然后当我们定位成功时调用特定的方法进行相应操作就可以了,当然,在定位失败时我们也可以进行相应的操作,这些都有对应的一些回调方法,我们只需要重写对应的回调方法就可以实现对应的功能了。

首先,我们用到的系统自带的定位模块是: <CoreLocation/CoreLocation.h> ,定位成功/失败的方法都是通过代理 CLLocationManagerDelegate 来完成的,所以我们在需啊哟进行定位的类中也需要继承自代理,并实现对应的一些方法才能成功完成回调。在定位功能的调用中,主要分为两步:1、定位参数的配置;2、代理回调方法的实现

一 定位参数的配置

定位参数的配置主要是通过系统的定位管理器CLLocationManager进行配置,参数配置的几个要点主要是设置代理,设置寻址精度,然后开启定位。当然,这些参数的配置的前提是我们的手机已经对我们的项目进行了地理位置的授权,所以我们再配置之前需要对当前定位权限进行判断,通过 [CLLocationManager locationServicesEnabled] 方法可以直接得到定位权限是否开启,如果有对应的权限,我们就可以进行对应的配置信息,否则我们无法进行对应的配置,这时候我们可以弹出一个对话框进行提示用户如何开启定位权限,在我们的项目中因为不需要进行提示,所以没有对应的代码模块。

- (void)viewDidLoad {
    [super viewDidLoad];

    //配置定位信息
    [self configLocation];
}

- (void)configLocation{
    self.place = @"";
    //判断定位权限是否打开
    if ([CLLocationManager locationServicesEnabled]) {
        locationManager = [[CLLocationManager alloc] init];
        locationManager.delegate = self;
        //设置寻址精度
        locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        locationManager.distanceFilter = 100.0f;
        //开始定位
        [locationManager startUpdatingLocation];
    } else {
        //如果定位权限没有打开,可以进行相应的提示
        //具体代码省略
    }
}

 二 代理方法的回调

在前面我们说过了,我们的类需要继承CLLocationManagerDelegate代理,并实现其中的一些方法才能在定位成功/失败时能够正确地调用对应的代理方法。主要用到的代理方法有两个,一个是成功时调用,一个是失败时调用,具体代码如下。在定位失败时,我们首先可以弹框提示定位失败的原因,接着我们可以选择重新定位,或者我们也可以选择限制连续定位失败多少次后就不再进行定位了,具体需要实现的方法大家可以根据需要进行配置和开发。

#pragma mark - CLLocationManagerDelegate
//定位成功的回调,在这里我们配置我们的位置信息
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{
    [locationManager stopUpdatingLocation];
    CLLocation *currentLoc = [locations lastObject];
    CLGeocoder *geoCoder = [[CLGeocoder alloc] init];
    //利用经纬度进行反编译获取位置信息
    [geoCoder reverseGeocodeLocation:currentLoc completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
        if (placemarks.count > 0) {
            //获取当前定位信息
            CLPlacemark *placeMark = [placemarks firstObject];
            if (placeMark) {
                self.place = [NSString stringWithFormat:@"%@%@%@%@",placeMark.administrativeArea,placeMark.locality,placeMark.subLocality,ZYIsNullOrEmpty(placeMark.thoroughfare)?@"":placeMark.thoroughfare];
            }
        }
    }];
}

//定位失败时调用
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
    [locationManager startUpdatingLocation];
}

IOS自动化定位方式

元素属性的介绍
type:元素类型,与className作用一致,如:XCUIElementTypeStaticText
value: 一般不用
name:元素的文本内容,可用作 AccessibilityId定位方式,如:测试420班级群
label:*大多数情况下,与 name 作用一致
enabled:元素是否可点击,一般值为true或者false
visible:元素是够可见,一般值为true或者false

1.findElementByIosNsPredicate

这个定位方式需要用java-client -5.0.版本,4.x的版本没有这个定位方式

driver.findElementByIosNsPredicate(“value ==’11′”).sendKeys(“AXPC11@ett.com”);

driver.findElementByIosNsPredicate(“name == ‘login_default_icon'”).click();

driver.findElementByIosNsPredicate(“value ==’请输入登录密码'”).sendKeys(“1111”);

driver.findElementByIosNsPredicate(“name == ‘login_default_icon'”).click();

driver.findElement(By.id(“登录”)).click();

仅支持 iOS 10或以上,可支持元素的单个属性和多个属性定位,推荐使用。
一种属性:MobileBy.iOSNsPredicateString(“type == ‘XCUIElementTypeButton'”)
两种属性:MobileBy.iOSNsPredicateString(“type == ‘XCUIElementTypeButton’ AND label == ‘更多信息'”)
具体 iOSNsPredicate语法结构可查看官方文档,或查看的这个帖子:iOS 定位方式 iOSNsPredicateString 详解。

2.

Appium 的定位方式种类
我说的定位方式都是基于我自己亲测过,没使用或比较少用的就在这里不列举了,如有错误的地方,请多多包涵!常用的定位方式(仅限 Android 和 iOS 两种系统)有 className、id、xpath、AccessibilityId、AndroidUIAutomator、iOSNsPredicateString、iOSClassChain、IosUIAutomation等

className
使用元素的className属性定位,支持:Android 和 iOS,推荐使用。
MobileBy.className(“XCUIElementTypeButton”)

id
使用元素的Resource Id属性定位,支持:Android,仅支持 Android 4.3或以上,推荐使用。反正我没有在 iOS 用过,大家有正确使用过的例子,可以分享一下。
MobileBy.id(“package.name:id/android”)

xpath
支持:Android 和 iOS。但由于 iOS 10开始使用的 XCUITest 框架原声不支持,定位速度很慢,所以官方现在不推荐大家使用,也有其他替代的定位方式可使用。
1.使用*对路径定位,如截图所显示的 xpath 路径
MobileBy.xpath(“className/className/className/className”)
2.使用相对路径定位
MobileBy.xpath(“//className”)
3.通过元素的索引定位
MobileBy.xpath(“//className[index]”)
4.通过元素的属性定位
一种属性:MobileBy.xpath(“//className[@label=’更多信息’]”)
两种属性:MobileBy.xpath(“//className[@label=’更多信息’][@isVisible=’1′]”)
部分属性(*强大):MobileBy.xpath(“//className[contains(@label,’更多’)]”)

AccessibilityId
替代以前的name定位方式,推荐使用。
在 Android 上,主要使用元素的content-desc属性,如该属性为空,不能使用此定位方式。
在 iOS 上,主要使用元素的label或name(两个属性的值都一样)属性进行定位,如该属性为空,如该属性为空,也是不能使用该属性。
MobileBy.AccessibilityId(“更多信息”)

AndroidUIAutomator
仅支持 Android 4.2或以上,可支持元素的单个属性和多个属性定位,推荐使用。
一种属性:MobileBy.AndroidUIAutomator(“new UiSelector().text(\”发送\”)”)
两种属性:MobileBy.AndroidUIAutomator(“new UiSelector().text(\”发送\”).clickable(true)”)
元素的所有属性都可用做定位,功能非常强大,且速度很快。

iOSNsPredicateString
仅支持 iOS 10或以上,可支持元素的单个属性和多个属性定位,推荐使用。
一种属性:MobileBy.iOSNsPredicateString(“type == ‘XCUIElementTypeButton'”)
两种属性:MobileBy.iOSNsPredicateString(“type == ‘XCUIElementTypeButton’ AND label == ‘更多信息'”)
具体 iOSNsPredicate语法结构可查看官方文档,或查看的这个帖子:iOS 定位方式 iOSNsPredicateString 详解。

iOSClassChain
仅支持 iOS 10或以上,这是 github 的 Mykola Mokhnach 大神开发,仅限在 WebDriverAgent 框架使用,用于替代 xpath 的,但使用一阵子后,感觉灵活性没有 xpath 和 iOSNsPredicate 好,应该还不完善吧。具体使用方法,请见:https://github.com/appium/appium-xcuitest-driver/pull/391 。
MobileBy.iOSClassChain(‘XCUIElementTypeWindow[1]/XCUIElementTypeOther[1]/XCUIElementTypeOther[1]/XCUIElementTypeNavigationBar[1]/XCUIElementTypeOther[1]/XCUIElementTypeButton[2]’)

IosUIAutomation
仅支持 iOS 9.3或以下,是 iOS 旧框架 UIAutomation 的定位方式,现在基本上很少使用,这个定位类型同样可使用 iOS 谓词进行定位,详细可参考:iOSNsPredicate

总结:
以上这个多定位方式,很少说全部用完。根据我的经验,推荐使用:Android:AndroidUIAutomator > className = id = AccessibilityId > xpath。iOS:iOSNsPredicateString > className = AccessibilityId