不能要求用户安装其他应用的客户端。比如第三方分享,未安装客户端时,应使用网页登录;无法使用网页登录时(如微信),隐藏分享入口。
If some features require signing in, provide a valid demo account username and password.
If there are special configurations to set, include the specifics. If features require an environment that is hard to replicate or require specific hardware, be prepared to provide a demo video or the hardware.
Apps or metadata that mentions the name of any other mobile platform will be rejected
Apps with placeholder text will be rejected (描述中提到与应用内容和功能无关信息会被拒。)
App names in iTunes Connect and as displayed on a device should be similar, so as not to cause confusion(应用在iTunes Connect与设备上显示的名称应该类似,否则会造成混淆。)
Small and large App icons should be similar, so as to not to cause confusion(不同尺寸的icon要一致,否则会造成混淆。)
Apps that provide Push Notifications without using the Apple Push Notification (APN) API will be rejected(不使用Apple Push Notification(APN) API提供消息推送的应用会被拒。)
Apps that send Push Notifications without first obtaining user consent will be rejected(在首次推送消息之前未取得的用户允许的应用会被拒。)
If some features require signing in, provide a valid demo account username and password.
If there are special configurations to set, include the specifics. If features require an environment that is hard to replicate or require specific hardware, be prepared to provide a demo video or the hardware.
iPhone Apps must also run on iPad without modification, at iPhone resolution, and at 2X iPhone 3GS resolution
Apps that include undocumented or hidden features inconsistent with the description of the App will be rejected
FIFA 2015 入门技巧
最近公司多了一个 XBOX 游戏机,老板带过来的。经常在公司的 XBOX 上玩 FIFA 2015。打得多了就有了一点心得,然后在网上搜了一些技巧。
直塞
直塞是最简单有效的进攻方法了。通过直塞能直接撕开对手防线,创造进攻机会,也是我最常用的进球方式。
直塞的方法是按 Y 键,地面直塞;LB+Y 空中直塞。
直塞的要点是力度、方向,还有最重要的时机。直塞时要注意前方球员不能处在越位位置。要看到本方球员启动向前冲了再传,不然传球了之后没有人接。地面直塞相对来说效果更好,但是如果传球线路有对方球员或传球方向太直时,选择空中直塞会比较好。
任意球
落叶球
朝着人墙发,一直按住上,然后按 B,力度大约45%~50%,看距离,远的话大一些。适用于近距离任意球,18~25码,大概就是禁区往外一点点,很有效的进球方式。
弧线球
球弧线从人墙边过。视角挪到球门一角,一直按住RB和右上(或者左上,朝着球门方向),然后按 B,力度大约60%。适用于25~40码。
过人
彩虹过人
按住 LT,按几下 RB,然后按右摇杆“下上上”
回旋
前进时按右摇杆右下或左下两次
护球键
LT,按住盘带或者需要时点几下。
拔球
右摇杆朝着要拔的方向按。
cocoapods 安装和使用
安装
使用 ruby 的 gem 命令即可下载安装:1
2$ sudo gem install cocoapods
$ pod setup
如果 gem 太老可能会有问题,使用以下命令升级:1
$ sudo gem update --system
ruby 默认软件源在国外,速度较慢,更新一下 ruby 的源,使用如下代码将官方的 ruby 源替换成国内淘宝的源:1
2
3gem sources --remove https://rubygems.org/
gem sources -a https://ruby.taobao.org/
gem sources -l
使用 CocoaPods 的镜像索引
所有的项目的 Podspec 文件都托管在https://github.com/CocoaPods/Specs。第一次执行pod setup时,CocoaPods 会将这些podspec索引文件更新到本地的 ~/.cocoapods/目录下,这个索引文件比较大,有 80M 左右。所以第一次更新时非常慢。
一个叫 akinliu 的朋友在 gitcafe 和 oschina 上建立了 CocoaPods 索引库的镜像,因为 gitcafe 和 oschina 都是国内的服务器,所以在执行索引更新操作时,会快很多。如下操作可以将 CocoaPods 设置成使用 gitcafe 镜像:1
2
3pod repo remove master
pod repo add master https://gitcafe.com/akuandev/Specs.git
pod repo update
将以上代码中的 https://gitcafe.com/akuandev/Specs.git 替换成 http://git.oschina.net/akuandev/Specs.git 即可使用 oschina 上的镜像。
使用 CocoaPods
使用时在项目根目录中新建一个名为 Podfile 的文件,在里面指定项目需要的依赖库。以下是我在项目中使用的 Podfile。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22pod 'Masonry' # 用于手写 autolayout
pod 'RSKImageCropper' # 图片裁剪
pod 'YYImage' # 图片编解码,支持 webp, apng, gif, png, jpeg 等格式
pod 'YYCache' # 缓存框架
pod 'JSPatch' # 热补丁修复
pod 'CocoaLumberjack' # 打印日志,可分等级颜色
pod 'TYAttributedLabel' # 加强版 Label,用来显示图文混排的内容
# ---- 闭源第三方库
pod 'UMengAnalytics-NO-IDFA' # 友盟统计
pod 'NewRelicAgent' # new relic,主要用来做性能统计
pod 'JPush' # 极光推送
# ---- shareSDK
# 主模块(必须)
pod 'ShareSDK3', '3.1.4'
# UI模块(非必须,需要用到ShareSDK提供的分享菜单栏和分享编辑页面需要以下1行)
pod 'ShareSDK3/ShareSDKUI'
# 平台SDK模块(对照一下平台,需要的加上。如果只需要QQ、微信、新浪微博,只需要以下3行)
pod 'ShareSDK3/ShareSDKPlatforms/QQ'
pod 'ShareSDK3/ShareSDKPlatforms/SinaWeibo'
pod 'ShareSDK3/ShareSDKPlatforms/WeChat'
编辑完 Podfile 后,执行1
pod install
现在,你的所有第三方库都已经下载完成并且设置好了编译参数和依赖,只需要打开项目的.xcworkspace 就能使用 Podfile 里添加的第三方库了。
以后添加新库时,执行一次 pod install即可。
如果要更新库,执行 pod update。
以下是 Podfile 常用语法1
2
3
4
5
6
7
8
9
10
11
12
13
14# 库版本号相关
pod 'AFNetworking' # 不显式指定依赖库版本,表示每次都获取最新版本
pod 'AFNetworking', '2.0' # 只使用2.0版本
pod 'AFNetworking', '> 2.0' # 使用高于2.0的版本
pod 'AFNetworking', '>= 2.0' # 使用大于或等于2.0的版本
pod 'AFNetworking', '< 2.0' # 使用小于2.0的版本
pod 'AFNetworking', '<= 2.0' # 使用小于或等于2.0的版本 pod 'AFNetworking', '~> 0.1.2' # 使用大于等于0.1.2但小于0.2的版本
pod 'AFNetworking', '~>0.1' # 使用大于等于0.1但小于1.0的版本
pod 'AFNetworking', '~>0' # 高于0的版本,写这个限制和什么都不写是一个效果,都表示使用最新版本
# 指定仓库 git 地址
pod 'TYAttributedLabel', :git => 'https://github.com/jayi/TYAttributedLabel.git'
# 按 commit 指定仓库版本
pod 'TYAttributedLabel', :commit => 'ffae09'
关于 Podfile.lock
当你执行pod install之后,除了 Podfile 外,CocoaPods 还会生成一个名为Podfile.lock的文件,Podfile.lock 应该加入到版本控制里面,不应该把这个文件加入到.gitignore中。因为Podfile.lock会锁定当前各依赖库的版本,之后如果多次执行pod install 不会更改版本,要pod update才会改Podfile.lock了。这样多人协作的时候,可以防止第三方库升级时造成大家各自的第三方库版本不一致。
Git的撤消操作 - 重置, 签出 和 撤消
Git提供了多种修复开发过程中错误的方法. 方法的选择取决于你的情况: 包含有错误的文件是否提交了(commited); 如果已经提交了, 那么你是否已把有错误的提交与其它人共享这也很重要.
修复未提交文件中的错误(重置)
如果你现在的工作目录(work tree)里搞的一团乱麻, 但是你现在还没有把它们提交; 你可以通过下面的命令, 让工作目录回到上次提交时的状态(last committed state):
$ git reset --hard HEAD
这条件命令会把你工作目录中所有未提交的内容清空(当然这不包括未置于版控制下的文件 untracked files). 从另一种角度来说, 这会让”git diff” 和”git diff –cached”命令的显示法都变为空.
如果将 HEAD 替换成某个结点的 hash 值,会强制将当前分支指向那个结点。这个方法可以用来:
- 清除已提交的 commit
- 快速更新
git reset –soft, –mixed, –hard 关系如下图
如果你只是要恢复一个文件,如”hello.rb”, 你就要使用 git checkout
$ git checkout -- hello.rb
这条命令把hello.rb从HEAD中签出并且把它恢复成未修改时的样子.
以上命令分别对应 sourcetree 下的 reset to commit 和 reset 文件操作。
开发过程中建议先把可以正常工作或确定的代码 stage,这样清理代码时只需要清理 unstaged 的代码就可以了。
修复已提交文件中的错误
如果你已经做了一个提交(commit),但是你马上后悔了, 这里有两种截然不同的方法去处理这个问题:
- 创建一个新的提交(commit), 在新的提交里撤消老的提交所作的修改. 这种作法在你已经把代码发布的情况下十分正确.
- 你也可以去修改你的老提交(old commit). 但是如果你已经把代码发布了,那么千万别这么做; git不会处理项目的历史会改变的情况,如果一个分支的历史被改变了那以后就不能正常的合并.
创建新提交来修复错误
创建一个新的,撤消(revert)了前期修改的提交(commit)是很容易的; 只要把出错的提交(commit)的名字(reference)做为参数传给命令: git revert就可以了; 下面这条命令就演示了如何撤消最近的一个提交:
$ git revert HEAD
这样就创建了一个撤消了上次提交(HEAD)的新提交, 你就有机会来修改新提交(new commit)里的提交注释信息.
你也可撤消更早期的修改, 下面这条命令就是撤消“上上次”(next-to-last)的提交:
$ git revert HEAD^
在这种情况下,git尝试去撤消老的提交,然后留下完整的老提交前的版本. 如果你最近的修改和要撤消的修改有重叠(overlap),那么就会被要求手工解决冲突(conflicts), 就像解决合并(merge)时出现的冲突一样.
git revert 其实不会直接创建一个提交(commit), 把撤消后的文件内容放到索引(index)里,你需要再执行git commit命令,它们才会成为真正的提交(commit).
修改提交来修复错误
如果你刚刚做了某个提交(commit), 但是你又想马上修改这个提交; git commit 现在支持一个叫–amend的参数,它能让你修改刚才的这个提交(HEAD commit). 这项机制能让你在代码发布前,添加一些新的文件或是修改你的提交注释(commit message).
git commit –amend 建议只用在修改未上传到服务器的提交的注释。
如果你在老提交(older commit)里发现一个错误, 但是现在还没有发布到代码服务器上. 你可以使用 git rebase命令的交互模式, “git rebase -i”会提示你在编辑中做相关的修改. 这样其实就是让你在rebase的过程来修改提交.
Git 分支管理
确认当前所在分支
进行分支操作前,请先确认当前工作分支,避免出现误操作。
- 使用 git branch 命令,见下文
- souretree 左侧边栏 BRANCHES 下,加粗的是当前分支
rebase 与 merge
- merge 是直接将两个分支汇合。
- rebase 是将当前分支在要合并的分支上重新演化。工作流程如下:
- 先找到当前分支与要合并分支的分叉点
- 对分叉点后的每个提交,计算它与前一点的差异代码, 在要合并分支上应用并提交(这一步有可能产生冲突)
- 删除当前分支的到分叉点的所有记录
- 将当前分支指针指向第2步得到的演化结果
合并前的状态
git merge origin/master
git rebase origin/master。算出 b1 到 a1的 差异代码,应用到 a2 上,master 指针指向新的 b1,删除旧的 b1。
远程分支
$ git pull
如果设置了某个分支用于跟踪某个远程仓库的分支,此命令可拉取数据,并自动合并对应分支。
默认情况下 git clone 命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支
使用git pull 时,请加上 –rebase,保持线性历史记录
pull 前的状态
git pull 等价于 git merge origin/master,执行后产生了分叉
git pull –rebase,等价于 git rebase origin/master,前当前所有提交记录在 origin/master 上重演,保持线性历史。
$ git fetch [remote-name]
从远程仓库抓取数据到本地。不指定远程仓库名时默认为origin。此命令仅拉取数据,并不自动合并。
$ git push [remote-name] [branch-name]
将本地分支推送到远程仓库。
如果在你推数据前,已经有其他人推送了若干更新,那你的推送操作就会被驳回。你必须先把他们的更新抓取到本地,合并到自己的项目中,然后才可以再次推送。
$ git push origin serverfix
推送本地serverfix分支到远程仓库origin的serverfix分支。
$ git push origin serverfix:awesomebranch
推送本地serverfix分支到远程仓库origin的awesomebranch。
$ git push origin :serverfix
删除远程仓库serverfix分支。
$ git branch
列出所有分支。* 所指的是 HEAD 所指向的分支,即当前工作分支。
$ git checkout -b serverfix origin/serverfix
根据远程仓库origin的serverfix分支新建本地serverfix分支。
$ git checkout --track origin/serverfix
效果同上。
$ git branch testing
在当前 commit 对象上新建一个分支指针。仅建立分支,但不切换。
HEAD指针总是指向当前正在工作的分支。
$ git checkout testing
切换到testing分支。
$ git checkout -b iss53
新建iss53分支,并切换到此分支,相当于:
$ git branch iss53
$ git checkout iss53
$ git branch -d hotfix
删除hotfix分支。-D为强制删除。
Git使用流程建议
Git 新建、切换分支速度很快,可以同时建多个分支进行不同功能的开发,调试。
何时使用 rebase 何时使用 merge?
- git pull 使用–rebase。本地分支操作用 merge。当master上有很多 commit 时,rebase 冲突的概率会很大,应当避免这种情况。这种情况更合理的做法是切换到一个新的分支,在新的分支开发。
- 使用 sourcetree 进行本地分支的 merge 时,不要选择 Rebase instead of merge (最后一项)
什么时候需要新建分支?
- 需要多次提交时,或者改动较多时,新建分支开发。
- 简单的 Bug 修复,或者只需要一两次提交小功能点,可以直接在本地的主干上开发。
同步流程
- 本地没有更新时,直接 git pull –rebase 即可与远程分支同步。
- 在本地主干上开发,commit 到本地后,git pull –rebase 与远程分支同步,最后 push。
- 在新分支上开发结束,要提交到远程分支时,先将 master 同步到最新,再将新分支 merge 到 master,最后 push。
- 在新建的分支开发,需要与最新代码同步时,可以git merge origin/master。这种情况会造成较混乱提交历史记录,少用。
Mac terminal vim 不能使用系统剪贴板解决方法
Mac 系统自带的 vim 编译时没加clipboard选项,不能使用系统剪贴板。
可以使用 brew的 macvim:
1 | $ brew install macvim --with-override-system-vim $ brew linkapps |
如果 vimrc 添加了1
set clipboard=unnamed
则可以直接使用系统剪贴板而不用加前缀”*
Java 8 新特性 - lambda表达式, stream
lambda 表达式
匿名函数,简化代码。
简化前:1
2
3
4
5
6
7
8
9Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
for (String item : list) {
System.out.println(item);
}
使用 lambda 表达式简化后:1
2list.sort((a, b) -> b.compareTo(a));
list.forEach(System.out::println);
函数接口
定义:函数接口是只有一个抽象方法的接口,用于作为Lambda表达式的类型.
以下6个是 java 定义的6个常用函数接口
| 函数名 | 参数 | 返回类型 | 示例 |
|---|---|---|---|
| Predicate |
T | boolean | 判断合格 |
| Consumer |
T | void | 日志记录 |
| Function |
T | R | 获取对象成员 |
| Supplier |
None | T | 工厂方法 |
| UnaryOperator |
T | T | 小写转大写 |
| BinaryOperator |
(T,T) | T | 两数之和 |
Stream
- Stream是Java8中重要的特性之一,它是一组对集合的操作方法,使得程序员得以站在更高的抽象层次上对集合进行操作
- Stream中提供并行操作,使得对集合进行一些并行操作变得更加容易
示例
给字符串列表加前缀,并且转为数组,期间不能修改原来列表。
通常:1
2
3
4
5List<String> cityList = ....;
String[] cityArrays = new String[cityList.size()];
for (int i = 0; i < cityList.size(); i++) {
cityArrays[i] = pre + cityList.get(i);
}
使用Stream:1
2
3
4List<String> cityList = ....;
String[] cityArray = cityList.stream()
.map(city -> pre + city) // 惰性求值
.toArray(String[]::new); // 及早求值
使用Stream简化了代码,也使得业务流程更加清晰。
惰性求值是一个中间过程,它的返回值还是Stream。
及早求值是一个结束过程,它的返回值是相应的结果类型。
惰性求值一般表示Stream流还在继续,因此一条流中可以有多个惰性求值,而及早求值一般表示一条Stream流结束,因此一条流中最多只有一次及早求值。
注意:如果一条Stream调用链中,没有及早求值,该调用链实际是不执行的。
stream 方法
构建流对象的方式
Stream.of()
1
2
3
4
5
6public class StreamBuilders {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c", "d", "e", "f", "g");
stream.forEach(p -> System.out.println(p));
}
}
Collection.stream()
1
2
3
4
5
6
7public class StreamBuilders {
public static void main(String[] args) {
List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f", "g");
Stream<String> stream = strings.stream();
stream.forEach(p -> System.out.println(p));
}
}Stream.generate()
1
2
3
4
5
6
7
8public class StreamBuilders {
public static void main(String[] args) {
Stream<Double> stream = Stream.generate(() -> {
return Math.random();
}).limit(5);
stream.forEach(p -> System.out.println(p));
}
}使用 iterate()
1
2
3
4
5
6public class StreamBuilders {
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(10, i -> i + 10).limit(5);
stream.forEach(i -> System.out.println(i));
}
}使用 CharSequence.chars()
1
2
3
4
5
6public class StreamBuilders {
public static void main(String[] args) {
IntStream stream = "ABCDEFG_abcdefg".chars();
stream.forEach(p -> System.out.println(p));
}
}
转换流为集合
stream.collect(Collectors.toList()) 把 Stream 转换为 List
1
2
3
4
5
6
7
8public class StreamBuilders {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = list.stream();
List<Integer> oddNumbersList = stream.filter(i -> i % 2 != 0).collect(Collectors.toList());
System.out.print(oddNumbersList);
}
}stream.collect(Collectors.toSet()) 把 Stream 转换为 Set
1
2
3
4
5
6
7
8public class StreamBuilders {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = list.stream();
List<Integer> oddNumbersSet = stream.filter(i -> i % 2 != 0).collect(Collectors.toSet());
System.out.print(oddNumbersSet);
}
}stream.toArray(EntryType[]::new) 把 Stream 转换为数组
1
2
3
4
5
6
7
8public class StreamBuilders {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = list.stream();
Integer[] oddNumbersArray = stream.filter(i -> i % 2 != 0).toArray(Integer[]::new);
System.out.print(oddNumbersArray);
}
}
核心的流操作
我们先构建一个字符串集合,下面的例子都会基于这个集合进行操作。1
List<String> provinceNames = Arrays.asList("湖南", "湖北", "河南", "河北", "广东", "广西", "北京", "南京");
中间操作
中间操作返回流对象,所以你可以把多个中间操作串成一行。
filter()
- filter 方法接受一个断言对象,用来过滤流中的元素。
1
2
3
4provinceNames.stream().filter((p) -> p.startsWith("湖")).forEach(p -> System.out.print(p + ", "));
// 输出 :
// 湖南, 湖北,
- filter 方法接受一个断言对象,用来过滤流中的元素。
map()
- 这个中间操作使用给定的函数把流中的每个元素转换为另一个对象。下面的例子把每个字符串转化为另一个字符串(末尾多了一个人字)。但是你也可以把每个对象转换为其它类型的对象。
1
2
3
4provinceNames.stream().map(p -> p + "人, ").forEach(p -> System.out.print(p));
// 输出 :
// 湖南人, 湖北人, 河南人, 河北人, 广东人, 广西人, 北京人, 南京人,
- 这个中间操作使用给定的函数把流中的每个元素转换为另一个对象。下面的例子把每个字符串转化为另一个字符串(末尾多了一个人字)。但是你也可以把每个对象转换为其它类型的对象。
sorted()
- sorted 会对流中的元素进行排序,默认按自然序进行排序,除非你指定一个自定义的比较器(Comparator) 。
1
2
3
4provinceNames.stream().sorted().map(p -> p + "人, ").forEach(p -> System.out.print(p));
// 输出 :
// 北京人, 南京人, 广东人, 广西人, 河北人, 河南人, 湖北人, 湖南人,
- sorted 会对流中的元素进行排序,默认按自然序进行排序,除非你指定一个自定义的比较器(Comparator) 。
需要注意的是 sorted 仅仅对流中的元素进行排序,而不会影响后面的集合,集合中的元素排序保持不变。
末端操作
末端操作返回某一类型的结果,而不是流对象。
forEach()
- 该方法帮助迭代流中的元素并在元素上执行一些操作。这些操作可以是 lambda 表达式或者方法引用。
1
provinceNames.stream().forEach(System.out::println);
- 该方法帮助迭代流中的元素并在元素上执行一些操作。这些操作可以是 lambda 表达式或者方法引用。
collect()
- collect() 方法用来从流中抽取元素然后保存到一个集合或者数组中。
1
2
3
4List<String> provinceNamesOrdered = provinceNames.stream().sorted().collect(Collectors.toList());
System.out.print(provinceNamesOrdered);
// 输出 :
// [北京, 南京, 广东, 广西, 河北, 河南, 湖北, 湖南]
- collect() 方法用来从流中抽取元素然后保存到一个集合或者数组中。
match()
- 各种匹配操作用来检查流中的元素是否指定断言条件。所有的匹配操作都是末端操作,它们返回布尔结果。
1
2
3
4
5
6
7
8
9
10
11
12
13boolean matchedResult = provinceNames.stream().anyMatch((s) -> s.startsWith("湖"));
System.out.println(matchedResult);
matchedResult = provinceNames.stream().allMatch((s) -> s.startsWith("湖"));
System.out.println(matchedResult);
matchedResult = provinceNames.stream().noneMatch((s) -> s.startsWith("湖"));
System.out.println(matchedResult);
// 输出 :
// true
// false
// false
- 各种匹配操作用来检查流中的元素是否指定断言条件。所有的匹配操作都是末端操作,它们返回布尔结果。
count()
- count 末端操作返回流中元素个数。
1
2
3
4long totalMatched = provinceNames.stream().count();
System.out.println(totalMatched);
// 输出 : 8
- count 末端操作返回流中元素个数。
reduce()
- 该末端操作使用给定的函数对流中的元素进行归约。它是这样一个过程:每次迭代,将上一次的迭代结果(第一次时为 identity 的元素,如没有 identity 则为流中的第一个元素)与下一个元素一同执行一个二元函数。在 reduce 操作中,identity 是可选的,如果使用,则作为第一次迭代的第一个元素使用。归约的结果保存在 Optional 中。
1
2
3
4
5Optional<String> reduced = provinceNames.stream().reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// 输出 :
// 湖南#湖北#河南#河北#广东#广西#北京#南京
- 该末端操作使用给定的函数对流中的元素进行归约。它是这样一个过程:每次迭代,将上一次的迭代结果(第一次时为 identity 的元素,如没有 identity 则为流中的第一个元素)与下一个元素一同执行一个二元函数。在 reduce 操作中,identity 是可选的,如果使用,则作为第一次迭代的第一个元素使用。归约的结果保存在 Optional 中。
短路操作
流操作通常会在流中满足某一断言的所有元素上进行操作,有时我们希望在迭代过程中遇到匹配的元素时就终止操作,在外部迭代中,你需要写 if-else 代码块,在内部迭代中,有现成的方法可以使用,下面是两个这样的方法的示例:
anyMatch()
- 该操作只要遇到满足断言条件的元素就会返回 true,在此之后就不再处理任何其它的元素了。
1
2
3
4boolean matched = provinceNames.stream().anyMatch((s) -> s.startsWith("河"));
System.out.println(matched);
// 输出 : true
- 该操作只要遇到满足断言条件的元素就会返回 true,在此之后就不再处理任何其它的元素了。
findFirst()
- 该操作会返回流中的第一个元素,然后就不再处理其它元素了。
1
2
3
4String firstMatchedName = provinceNames.stream().filter((s) -> s.startsWith("广")).findFirst().get();
System.out.println(firstMatchedName);
// 输出 : 广东
- 该操作会返回流中的第一个元素,然后就不再处理其它元素了。
Optional 接口
Optional 被定义为一个简单的容器,其值可能是null或者不是null
在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null
在Java 8中,不推荐你返回null而是返回Optional
1 | String temp = "temp"; |
将电话号码列表转化为以逗号隔开的字符串,如果为空则抛出异常1
2
3String phoneNumber = subList.stream()
.reduce((a, b) -> a + "," + b)
.orElseThrow(Exception::new);
3D Touch 开发 入门
图标快捷菜单
iOS 9 新增了桌面图标快捷菜单的功能(Home Screen Quick Actions)。用户按压桌面图标能弹出几个选项,进行某些快捷操作。
iOS 9 下每个应用最多能显示4个桌面快捷选项。系统优先显示静态快捷选项,从上往下。如果未超出限制,且应用有定义动态快捷选项,显示动态快捷选项。
静态快捷选项
在应用的 Info.plist 顶层中新增 UIApplicationShortcutItems 数组。
1 | <key>UIApplicationShortcutItems</key> |
静态定义快速在运行时常用的key:
key | 说明
— | —
UIApplicationShortcutItemType | (必须使用) 用来区分与其他快速选项的分类
UIApplicationShortcutItemTitle | (必须使用) 快速选项显示的标题
UIApplicationShortcutItemSubtitle | 快速选项显示的子标题
UIApplicationShortcutItemIconType | 图片类型由系统提供
UIApplicationShortcutItemIconFile | 自定义的图标
UIApplicationShortcutItemUserInfo | 附加信息
动态快捷选项
指定[UIApplication sharedApplication].shortcutItems。
动态快捷选项必须启动应用且设置shortcutItems后,才能生效。
1 | - (void)setupApplicationShortcutItems |
快捷选项操作
在AppDelegate.m文件中添加
application:performActionForShortcutItem:completionHandler:
代理方法,根据UIApplicationShortcutItem的type属性和之前在info.plist设置UIApplicationShortcutItemType对应的在值来判断,用户点击的是哪个快速选项。这个方法在接近激活应用时就会调用,除了
-application:willFinishLaunchingWithOptions:
or
-application:didFinishLaunchingWithOptions
returns NO.
这种情况。
在-application:didFinishLaunchingWithOptions
中可以通过UIApplicationLaunchOptionsShortcutItemKey键来获取当前接收快速选项的UIApplicationShortcutItem对象,来处理用户的意图。
Peek 和 Pop
这个功能是一套全新的用户交互机制,在使用3D Touch时,ViewController中会有如下三个交互阶段:
- 提示用户这里有3D Touch的交互,会使交互控件周围模糊
- 继续深按,会出现预览视图
- 通过视图上的交互控件进行进一步交互
peek 和 pop 的核心主要是 iOS9 上新增的以下几个代理方法:
- registerForPreviewingWithDelegate:sourceView:
- previewingContext:viewControllerForLocation:
- previewingContext:commitViewController:
- previewActionItems
下面结合几段代码看看 peek 和 pop 的用法。
假设现在有两个 ViewController,MasterViewController 和 DetailViewController,MasterViewController 上有一个 TableView,点击上面的 Cell 进入 DetailViewController。
首先,MasterViewController 需要调用注册接口以支持3D touch,指定接收3D touch 事件的 view 和处理 peek 和 pop 的代理 。
1 | // 判断设备是否支持3D touch |
如果考虑得周全一点,应该在识别到用户关闭了 3D touch 时,调用unregisterForPreviewingWithContext:注销 3D touch。
接下来,在 MasterViewController 上需要实现以下代理。
1 | // Pop 事件代理, 重按时进入 DetailViewController |
通常,在弹出预览窗口后,可以上滑,弹出菜单,进行某些快捷操作。前提是预览的 ViewController 实现了-previewActionItems,指定了peek 快捷操作。如果没实现该代理,则没有快捷菜单,弹出预览后滑动没有效果,放开就消失了。
快捷菜单可以通过 UIPreiveActionGroup 组织成多个层级,详情见代码。
以下是 DetailViewController 的 previewActionItems 示例。
1 | - (NSArray<id<UIPreviewActionItem>> *)previewActionItems { |
UITouch
UITouch 新增两个用于 3d touch 的字段: force 和 maximumPossibleForce。这些属性可以让你侦测并响应APP接收的UIEvent触摸压力。
skiplist 原理和实现
skiplist 又称跳跃表,是一个数据节构,可以实现红黑树类似的功能,插入或删除元素后保持有序。插入和删除的复杂度都是O(log n)。
skiplist 除了具有红黑树的功能外,还能支持从下标取元素的操作(类似skipList[10]这样的访问)。
skiplist 基于概率均衡,不保证平衡性(但通常效果都不错)。它相对于传统的平衡树优势在于原理更简单,代码实现上更简洁高效。
原理
跳跃表可以看作是传统单链表的扩展。有序的单链表插入和删除的复杂度都是O(n)的。
跳跃表对有序单链表的每个节点进行了扩展。在每个节点原来有一个指向下一节点的指针的基础上,新增一系列指针,让第二个指针指向当前节点往后数第2个节点,第三个指针指向当前节点住后数第4个节点…以此类推,第n个指针指向当前点后的第$2^{n-1}$个节点。如下图如示。实际实现时,是基于随机算法,步长不是完全精确的为$2^{n-1}$。

这样一来,插入、删除或者查找节点时,从上往下,先用上层的指针确定范围,再往下一层,减小步长,继续查找,缩小范围,直到确定位置。
通过对单链表扩展,新增一系列步长指数增长的指针,跳跃表插入、删除、查找的复杂度优化到了O(log n)。
实现
接下来看看跳跃表的 java 实现。
数据结构
首先需要一个数据结构存储每个节点。1
2
3
4
5
6
7
8private class Node<T> {
T element; // 该节点的标识,通常为 string
Integer score; // 该节点的值,作为排序依据
Node<T>[] backward; // 前驱
Node<T>[] level; // 后继
// 跳跃跨度,表示当前节点与level[i]中间有多少个结点
int[] span = new int[MAX_LEVEL];
}
然后是整个跳跃表类。1
2
3
4
5
6
7public class SkipList<E> {
final static int MAX_LEVEL = 32; // 最大层数,32表示能存2^32个元素
private Node<E> head; // 链表头指针,头指针和尾指针不存数据
private Node<E> tail; // 链表尾指针
public int level; // 当前跳跃表层数
public int size; // 链表元素个数
}
查询
查询对应位置的元素
在维护好跳跃表的前提下,查询跳跃表中给定位置的元素是很容易的。
- 从 head 结点开始,level = MAX_LEVEL - 1 开始遍历
- 如果跳到 next[level] 后超出查询的下标,level 减1,节点的下标由遍历时前驱的 span 之和计算得来;
- 否则跳到 next[level] 节点;
- 重复2,3步,直到level=-1.
1 | public Node<E> findEntry(int index) { |
查询某个元素的下标
和第一步查询下标对应的元素类似,上面是把 index 减到0,这边让 index 从0开始加,直到跳到链表中与要查询元素值一样的时候结束。最后得到的 index 便是需要的结果。
1 | private int getIndex(Node<E>node) { |
插入元素后更新 span
如果只是插入更新 level 0 指针,这样就退化成简单的双向链表了。我们还需要结新加入的结点随机赋予高度,并更新 span 值。
更新 span 的主要思想是,新加入节点在 level i 的 span 值为在 level i 的前驱(backward[i])的 span 值减去新节点同前驱的 index 之差。与此同时,更新前驱的 span 值。
1 | public void updateSpanAdd(Node<E> node) { |
删除元素
删除前更新 span
与插入操作不同,删除元素时需要先更新 span,对要删除结点所有 level 上的前驱,对应的 span 减1。
1 | public void updateSpanRemove(Node<E> node) { |
从跳跃表中删除元素
查找到要删除元素的位置,从链接中删除。更新每个 level 上的前驱后继关系和前驱的 span 值。
1 | public boolean remove(E element, int score) { |
Xcode升级后插件失效修复办法
1 | find ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins -name Info.plist -maxdepth 3 | xargs -I{} defaults write {} DVTPlugInCompatibilityUUIDs -array-add `defaults read /Applications/Xcode.app/Contents/Info.plist DVTPlugInCompatibilityUUID` |