服务器

Git 使用总结

1. Git简介

Git的诞生确实是一个有趣的故事,我们知道,当年Linus创建了开源的Linux,从此,Linux系统不断发展,现在已经成为最大的服务器系统软件了(请不要傻傻分不清Linus和Linux)。

但是随着Linux的不断壮大,就需要各种版本控制了,起初Linus带着他的小弟们使用的是BitKeeper(商业版本控制系统),之后呢由于某种原因BitKeeper的公司不让他们使用了,于是Linus自己花了两周时间写出了Git并且开源了(BitKeeper已哭晕在厕所),阿弥陀佛,幸亏BitKeeper不让Linus他们用了,要不然我们现在也不会有这么好用的Git了,博主更不会在这写这篇博文了。

之后的岁月里,渐渐有了github,coding等一些可以使用git存储的网站,Git的江湖地位变得无可替代了,如果你是个开发者却还不会使用Git那就太out了。

这里先引用一张图解释Git

工作原理:

  • Workspace:工作区,执行git add *命令就把改动提交到了暂存区,执行git pull命令将远程仓库的数据拉到当前分支并合并,执行git checkout [branch-name]切换分支
  • Index:暂存区,执行git commit -m '说明' 命令就把改动提交到了仓库区(当前分支)
  • Repository:仓库区(或本地仓库),执行git push origin master提交到远程仓库,执行git clone 地址将克隆远程仓库到本地
  • Remote:远程仓库,就是类似github,coding等网站所提供的仓库

:实际操作命令和上述命令会有所不同,这里这是解释清楚命令和仓库的关系。

1.1 Git 术语

仓库(Repository)

一个仓库包括了所有的版本信息、所有的分支和标记信息。在Git中仓库的每份拷贝都是完整的。仓库让你可以从中取得你的工作副本。

分支(Branches)

一个分支意味着一个独立的、拥有自己历史信息的代码线(code line)。你可以从已有的代码中生成一个新的分支,这个分支与剩余的分支完全独立。默认的分支往往是叫master。用户可以选择一个分支,选择一个分支执行命令git checkout branch.

标记(Tags)

一个标记指的是某个分支某个特定时间点的状态。通过标记,可以很方便的切换到标记时的状态,例如2009年1月25号在testing分支上的代码状态

提交(Commit)

提交代码后,仓库会创建一个新的版本。这个版本可以在后续被重新获得。每次提交都包括作者和提交者,作者和提交者可以是不同的人

修订(Revision)

用来表示代码的一个版本状态。Git通过用SHA1 hash算法表示的id来标识不同的版本。每一个 SHA1 id都是160位长,16进制标识的字符串.。最新的版本可以通过HEAD来获取。之前的版本可以通过”HEAD~1”来获取,以此类推。

1.2忽略特定的文件

可以配置Git忽略特定的文件或者是文件夹。这些配置都放在.gitignore文件中。这个文件可以存在于不同的文件夹中,可以包含不同的文件匹配模式。

比如.gitignore内容可以如下:

忽略某文件
npm-debug.log
忽略文件夹
dist/
node_modules/
.idea/

同时Git也提供了全局的配置,core.excludesfile。

忽略之后的文件或是文件夹Git就不去提交里面的内容了。

1.3 使用.gitkeep来追踪空的文件夹

Git会忽略空的文件夹。如果你想版本控制包括空文件夹,根据惯例会在空文件夹下放置.gitkeep文件。其实对文件名没有特定的要求。一旦一个空文件夹下有文件后,这个文件夹就会在版本控制范围内。

1.4 配置

# 显示当前的Git配置
$ git config --list
# 编辑Git配置文件,只是配置用户信息的话直接看下面两行命令即可
$ git config -e [--global]
# 设置提交代码时的用户信息,是否加上全局--global自行决定,一般是直接设置全局的。另外用户邮箱需要注意最好使用gmail,QQ也可以,需要和你远程仓库保持一致不然你的contribution是不会被记录在远程仓库的
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"

Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。

个人觉得git是需要认真学的,虽然是个工具但不学习很容易把自己弄糊涂,希望这篇博客可以在某些时候帮到您,让您大概理解git的工作原理并把基本命令串起来。那么下面就说一下Git重要的基本命令吧。

2.Git安装

Mac 自带git客户端,可以通过如下命令查看git 版本信息

git --version
3.创建仓库
# 在当前目录创建一个文件夹
$ mkdir [project-name]
# 在当前目录新建一个Git代码库
$ git init
# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]
# 下载一个项目和它的整个代码历史(各个分支提交记录等)
$ git clone [url]

git init后会出现.git文件夹,里面有配置文件,如果没有git bash里面输入ls -lah就可以看到了

3.1 在非空目录下 git clone 项目

在非空目录下 git clone 项目时会提示错误信息:

fatal: destination path '.' already exists and is not an empty directory.

解决办法:

1. 进入非空目录,假设是 /dir/jk1
2. git clone --no-checkout https://github.com/HJThink/test.git tmp
3. mv tmp/.git .   
4. rmdir tmp
5. git reset --hard HEAD

关于如何关联Git和远程仓库,比如Coding,github等,可以看这两篇文章:

Git链接到自己的Github Coding帮助中心

4.提交文件
4.1首次推送
# 添加当前目录的所有文件到暂存区
$ git add *
# 提交暂存区到仓库区
$ git commit -m [message]
# 为远程Git更名为origin
$ git remote add origin git@github.com:abcd/tmp.git
# 推送此次修改,这是首次推送需要加上-u,之后推送就可以直接git push  origin master,origin是远程Git名字,这个可以自己定义,不过一般是用origin罢了,master是默认的分支,如果不在master分支提交需要写清楚分支名称
$ git push -u origin master

首次推送成功后可以看下下面的命令:

# 添加指定文件到暂存区
$ git add [file1] [file2] ...
# 添加指定目录到暂存区,包括子目录
$ git add [dir]
# 添加当前目录的所有文件到暂存区
$ git add *
# 添加每个变化前,都会要求确认
对于同一个文件的多处变化,可以实现分次提交
$ git add -p
# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...
# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]
# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]
# 提交暂存区到仓库区
$ git commit -m [message]
# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]
# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a
# 提交时显示所有diff信息
$ git commit -v
# 使用一次新的commit,替代上一次提交
如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]
# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...
# 提交更改到远程仓库
$ git push origin master
# 拉取远程更改到本地仓库默认自动合并
$ git pull origin master
# Git pull 强制覆盖本地文件
git fetch --all  
git reset --hard origin/master 
git pull

如果我们只是维护自己的小项目的话,上面的命令已经够用了,自己一个人在master分支想咋折腾就咋折腾

5.分支

但如果是多人协作的话,git的魅力就开始提现出来了,每个人有自己的一个分支,各自在自己的分支上工作互不干扰。具体的看这:Git教程-创建合并分支

# 列出所有本地分支
$ git branch
# 列出所有远程分支
$ git branch -r
# 列出所有本地分支和远程分支
$ git branch -a
# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]
# 新建一个分支,并切换到该分支
$ git checkout -b [branch]
# 新建一个分支,指向指定commit
$ git branch [branch] [commit]
# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]
# 切换到指定分支,并更新工作区
$ git checkout [branch-name]
# 切换到上一个分支
$ git checkout -
# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]
# 合并指定分支到当前分支,如果有冲突需要手动合并冲突(就是手动编辑文件保存咯),然后add,commit再提交
$ git merge [branch]
# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]
# 删除分支
$ git branch -d [branch-name]
# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]
6.标签

标签的作用主要是用来做版本回退的,关于版本回退,这也是Git的亮点之一,起到了后悔药的功能·

# 列出所有tag
$ git tag
# 新建一个tag在当前commit
$ git tag [tag]
# 新建一个tag在指定commit
$ git tag [tag] [commit]
# 删除本地tag
$ git tag -d [tag]
# 删除远程tag
$ git push origin :refs/tags/[tagName]
# 查看tag信息
$ git show [tag]
# 提交指定tag
$ git push [remote] [tag]
# 提交所有tag
$ git push [remote] --tags
# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]
7.后悔药

想一下在你写完N个文件代码后,commit到了本地仓库,突然发现整个应用崩溃了!咋整?Git给了我们吃后悔药的机会:

$ git checkout [file]
# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]
# 恢复暂存区的所有文件到工作区
$ git checkout .
# 回退到上一个版本,在Git中,用HEAD表示当前版本
$ git reset --hard HEAD^
# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]
# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard
# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]
# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]
# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]
# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]
# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop

这个时候标签的作用就体现出来了,因为commit号太冗长了,记起来太麻烦有了标签我们相当于自定义了commit号

8. 文件信息
# 显示当前分支的版本历史
$ git log
# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat
# 搜索提交历史,根据关键词
$ git log -S [keyword]
# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s
# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature
# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]
# 显示指定文件相关的每一次diff
$ git log -p [file]
# 显示过去5次提交
$ git log -5 --pretty --oneline
# 显示所有提交过的用户,按提交次数排序
$ git shortlog -sn
# 显示指定文件是什么人在什么时间修改过
$ git blame [file]
# 显示暂存区和工作区的差异
$ git diff
# 显示暂存区和上一个commit的差异
$ git diff --cached [file]
# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD
# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]
# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"
# 显示某次提交的元数据和内容变化
$ git show [commit]
# 显示某次提交发生变化的文件
$ git show --name-only [commit]
# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]
8.其它命令

git blame filepath
git blame清楚的记录某个文件的更改历史和更改人,简直是查看背锅人的利器,filepath是需要查看的文件路径

git status
显示有变更的文件

git reflog
显示当前分支的最近几次提交

参考文章:

iOS 项目开发管理

项目文件管理
  • 项目文件结构
  • 项目文件权限

    • AppDelegate.h 、 AppDelegate.m
    • Info.plist
    • Podfile 、 Pods
    • 其他第三方配置文件(支付、分享等)
  • 项目代码规范
项目时间评估
项目进度跟进
项目质量控制
项目架构设计
开发团队分享

iOS7 判断应用的麦克风权限是否打开

判断是否允许使用麦克风7.0新增的方法requestRecordPermission

第一次调用这个方法的时候,系统会提示用户让他同意你的app获取麦克风的数据

其他时候调用方法的时候,则不会提醒用户

而会传递之前的值来要求用户同意

[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
    if (granted) {
        // 用户同意获取数据
        NSLog(@"麦克风打开了");
    } else {
        // 可以显示一个提示框告诉用户这个app没有得到允许?
        NSLog(@"麦克风关闭了");
    }
}];

手动下载XCode 文档和模拟器

缘起

由于众所周知的原因,也就是 Apple 的云真的很烂,App Store 还能通过 DNS 等手段加速更新。对于 XCode 简直就是噩梦,挂不挂都是一个样子,非常慢。不幸中之大幸,虽然 XCode 本身下载很慢,但是可以手动下载好之后,通过 XCode 安装。

文档

手动下载文档可以通过以下几步解决,via stackoverflow

  • 通过这个 地址 找到需要下载的文件的路径
<!-- START OS X doc set -->
<dict>
  <key>fileSize</key>
  <integer>931959772</integer>
  <key>identifier</key>
  <string>com.apple.adc.documentation.OSX</string>
  <key>name</key>
  <string>OS X 10.11.4 Documentation</string>
  <key>source</key>
  <string>https://devimages.apple.com.edgekey.net/docsets/20160321/031-52211-A.dmg</string>
  <key>userInfo</key>
  <dict>
    <key>ActivationPredicate</key>
    <string>$XCODE_VERSION >= '7.3'</string>
    <key>Category</key>
    <string>Documentation</string>
    <key>IconType</key>
    <string>IDEDownloadablesTypeDocSet</string>
    <key>InstallPrefix</key>
    <string>$(HOME)/Library/Developer/Shared/Documentation/DocSets</string>
    <key>InstalledIfAllReceiptsArePresentOrNewer</key>
    <dict>
      <key>com.apple.pkg.10.9.OSXDocset</key>
      <string>10.9.0.0.1.1458364023</string>
    </dict>
    <key>RequiresADCAuthentication</key>
    <false/>
    <key>Summary</key>
    <string>My description of content</string>
  </dict>
  <key>version</key>
  <string>1014.5</string>
</dict>
<!-- END OS X doc set -->

下载 source 节点对应的内容,在这个示例中也就是 这个 ,可以通过第三方的下载工具,比如 asia2 下载。

  • 按照 identifier string + - + version string + .dmg 的格式重命名文件,在这个示例中也就是 com.apple.adc.documentation.OSX-1014.5.dmg
  • 把重命名后的文件放到 ~/Library/Caches/com.apple.dt.Xcode/Downloads/ 中,如果没有 Downloads 文件夹就创建一个, 如果 Downloads 中有后缀为 dvtdownloadableindex 的文件,全部删除
  • 删除 ~/Library/Developer/Shared/Documentation/DocSets 中对应的 docset
  • XCodePreferences/Download 中下载对应的文档,XCode 会校验刚才复制过去的文件进行安装

模拟器

  • 打开 XCodePreferences/Download 中下载模拟器
  • 打开 Console.app,清空日志
  • XCode 中取消下载
  • Console.app 中会看到取消的日志,其他包含完整的下载地址
  • 通过 asia2 等第三方工具下载刚才地址中的文件
  • 把下载好的文件复制到 ~/Library/Caches/com.apple.dt.Xcode/Downloads 中,如果没有 Downloads 文件夹就创建一个, 如果 Downloads 中有后缀为 dvtdownloadableindex 的文件,全部删除
  • XCode 中安装刚才下载的模拟器

如果需要删除不需要的模拟器,可以在

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs 中直接删除

文章转载自 : 手动下载 XCode 文档和模拟器

iOS 牛顿摆动画实现

网上有看到牛顿摆的的实现说明,但一直没找到Demo,所以想自己实现下,顺便给他人一个参考。

思路

说下牛顿摆的大致运动过程

根据牛顿摆的原理,中间是不动得,只有两边在动

两边运动是一个以这条线的上方位原点,长为半径,然后做半圆运动

运动模式是先快后慢

当左边的摆下来的时候,右边的开始向上摆动,右边的摆下来的时候,左边的开始向上摆动,一直循环下去

这样的话,我们用CAShapeLayer来进行画图,然后用CAAnimation来实现上述的运动过程

流程

  • 整体用CAShaepLayer + CAAnimation实现上述效果
  • 图形全是画出来
  • 划中间的四条线
  • 划下边的四个圆
  • 划左边的线
  • 划左边的圆
  • 划右边的线
  • 划右边的圆
  • 最后划上边的横线
  • 加阴影
  • 做动画

实现

1. 画线
  • 1.1 全局变量

做成全局变量,方便后边使用

由于上边的大横线是不用动得,所以可以位局部变量

还有一个问题就是,如果直接用[self.layer subLayers]来取值的话,会取到多一些其他的layer,之前自己添加的layer是subLyaer的第一个,现在貌似是第三个,默认多了两个,这个具体原因不详,自己创建一个数组,来存放所有用到的layer,动画结束后,移除他们

动画结束后,需要回调一个block来做一些事情,下边会说到

//自身的宽高
CGFloat _height;
CGFloat _width;

//左边的竖线,左边的圆,左边的旋转路径
CAShapeLayer * _leftLine;
CAShapeLayer * _leftCircle;
CGMutablePathRef  _leftPath;

//右边的竖线,右边的圆,右边的旋转路径
CAShapeLayer * _rightLine;
CAShapeLayer * _rightCircle;
CGMutablePathRef  _rightPath;

//左边的动画
CABasicAnimation * _leftBaseAnimation;
CABasicAnimation * _rightBaseAnimation;

//右边的动画
CAKeyframeAnimation * _leftKeyframeAnimation;
CAKeyframeAnimation * _rightKeyframeAnimation;

//动画结束调用的block
void(^animationFinishBlock)(CAAnimation * animation);

//存放所有图层的数组
NSMutableArray  * _array;
  • 1.2 初始化

初始化宽,高

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {

        //初始化
        _height = self.frame.size.height;
        _width = self.frame.size.width;

        _array = [[NSMutableArray alloc]init];


    }
    return self;
}
  • 1.3 创建中间的四个横线和圆

因为在初始化的时候设置的宽高都是100,所以,循环创建中间者四个视图,使他们的位置依次排列,然后放在中间

然后添加到self.layer上

同样也添加到数组中

至于怎么算,额..数学不太好,自己琢磨琢磨把

-(void)creatLayer {
    for (int i = 0; i < 6; i++) {

        if (i >=1 && i<=4 )
        {
            CAShapeLayer * layer = [self creatFourLineX:i*10+25 andY:10];
            CAShapeLayer * layer2 = [self creatRoundLayerX:i*10+25 andY:70];
            [self.layer addSublayer:layer];
            [self.layer addSublayer:layer2];
            [_array addObject:layer];
            [_array addObject:layer2];
        }


    }
}
  • 1.4 创建四个线
-(CAShapeLayer *)creatFourLineX:(CGFloat)x andY:(CGFloat)y {
    CAShapeLayer * layer = [CAShapeLayer layer];
    //首先,根据传递过来的参数,布局,然后设置宽位2 高为70
    layer.frame = CGRectMake(x, y, 2, 70);
    //创建路径
    CGMutablePathRef path = CGPathCreateMutable();
    //移动到 (0,0)的位置
    CGPathMoveToPoint(path, nil, 0, 0);
    //然后话一条60长度的线
    CGPathAddLineToPoint(path, nil, 0, 60);
    //设置layer的路径位划的路径
    layer.path = path;
    //填充颜色,这里的颜色要转化位CGColor
    layer.strokeColor = [UIColor colorWithRed:0.188  green:0.188  blue:0.216 alpha:1].CGColor;
    //设置线宽
    layer.lineWidth = 2;
    //设置lineCape(不知道怎么说了)就是那个线的端点的样式,这里是圆形,
    layer.lineCap = kCALineCapRound;
    //然后设置下阴影
    [self setShadow:layer];
    //返回layer
    return layer;
}
  • 1.5 创建四个圆
-(CAShapeLayer *)creatRoundLayerX:(CGFloat)x andY:(CGFloat)y {
    CAShapeLayer * layer = [CAShapeLayer layer];
    //设置位置,我们的圆是半径位5的圆,所以宽度是10就够了
    layer.frame = CGRectMake(x, y, 10, 10);

    //然后绘制路径
    CGMutablePathRef path = CGPathCreateMutable();
    //参数依次是
    //1. 路径
    //2. 变换
    //3. 圆心的x
    //4. 圆心的y
    //5. 起始角度
    //6. 结束角度
    //7. 是否顺时针
    //关于这个,大家自己体会下就知道,画图嘛,画出来什么样子看看是最清楚的
    CGPathAddArc(path, nil, 0, 0, 5, 0, M_PI*2, YES);
    //然后设置路径
    layer.path = path;

    //然后填充颜色,这里和上边的`layer.strokeColor`不一样,上边的`layer.strokeColor`这是是边框的颜色,也就数画笔的颜色
    //而这个`layer.fillColor`则是填充的颜色

    layer.fillColor = [UIColor colorWithRed:0.404  green:0.404  blue:0.404 alpha:1].CGColor;
    //然后设置下阴影
    [self setShadow:layer];

    return layer;
}
  • 1.6 画左边的线

这里大致说下anchorPoint 这个是锚点,所谓锚点就是类似你把一张纸,用图钉固定在了墙上,当不太紧的时候,纸是可以旋转的,旋转的中心就是锚点

锚点和position都可以改变这个layer的位置,具体细节大家可以去这里查看

由于我们在动画的时候,会对左边的线进行旋转,而且是围绕者顶部开始旋转的,所以我们把锚点设为(0,0),这样的话,我们旋转的时候,就以(0,0)为中心点,进行旋转

根据位置不同,我们这是了position的anchorPoint,然后和上边一样,画一条60长的线,同样设置一下相关的属性

-(void)creatLeftLine {
    _leftLine = [CAShapeLayer layer];
    _leftLine.frame = CGRectMake(25, 10, 100, 100);

    _leftLine.position = CGPointMake(25, 10);
    _leftLine.anchorPoint = CGPointMake(0,0);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, 0, 0);
    CGPathAddLineToPoint(path, nil, 0, 60);
    _leftLine.strokeColor = [UIColor colorWithRed:0.188  green:0.188  blue:0.216 alpha:1].CGColor;
    _leftLine.lineWidth = 2;
    _leftLine.lineCap = kCALineCapRound;
    _leftLine.path = path;
    [self setShadow:_leftLine];
    [self.layer addSublayer:_leftLine];
    [_array addObject:_leftLine];

}
  • 1.7 画左边的圆

和上边类似,我们也要画一个圆,这里我们设置一下frame和position,使我们的圆的中心,就在线的下边,这样的话,我们在做动画的时候,从视觉效果来说,是一起的

→_→ 其实是两个

-(void)creatLeftRound {
    _leftCircle = [CAShapeLayer layer];
    _leftCircle.position = CGPointMake(25, 70);
    _leftCircle.frame = CGRectMake(20, 65, 10, 10);
    CGMutablePathRef path = CGPathCreateMutable();

    CGPathAddArc(path, nil, 5, 5, 5, 0, M_PI*2, YES);

    _leftCircle.path = path;
    _leftCircle.fillColor = [UIColor colorWithRed:0.404  green:0.404  blue:0.404 alpha:1].CGColor;
    [self setShadow:_leftCircle];

    [self.layer addSublayer:_leftCircle];
    [_array addObject:_leftCircle];

}
  • 1.8 画右边的线

同样和上边类似,要围绕上边进行旋转,所以,要设置下锚点,然后和相关属性

锚点很重要,锚点很重要,锚点很重要,重要的事要说三遍,说三遍,三遍,遍

-(void)creatRightLine {
    _rightLine = [CAShapeLayer layer];
    _rightLine.frame = CGRectMake(75, 10, 100, 100);

    _rightLine.position = CGPointMake(75, 10);
    _rightLine.anchorPoint = CGPointMake(0,0);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, 0, 0);
    CGPathAddLineToPoint(path, nil, 0, 60);
    _rightLine.strokeColor = [UIColor colorWithRed:0.188  green:0.188  blue:0.216 alpha:1].CGColor;
    _rightLine.lineWidth = 2;
    _rightLine.lineCap = kCALineCapRound;
    _rightLine.path = path;
    [self setShadow:_rightLine];
    [self.layer addSublayer:_rightLine];
    [_array addObject:_rightLine];

}
  • 1.9 画右边的圆

和上边是一样的,要是再多的话,我就封装啦,别逼我发自拍

-(void)creatRightRound {
    _rightCircle = [CAShapeLayer layer];
    _rightCircle.position = CGPointMake(75, 70);
    _rightCircle.frame = CGRectMake(70, 65, 10, 10);

    CGMutablePathRef path = CGPathCreateMutable();

    CGPathAddArc(path, nil, 5, 5, 5, 0, M_PI*2, YES);

    _rightCircle.path = path;
    _rightCircle.fillColor = [UIColor colorWithRed:0.404  green:0.404  blue:0.404 alpha:1].CGColor;

    [self setShadow:_rightCircle];
    [self.layer addSublayer:_rightCircle];
    [_array addObject:_rightCircle];

}
  • 1.10 设置阴影

调用了这么多次,终于出现了,设置下圆角,阴影的颜色,偏移量和 … 这个不知道怎么说,自己体会一下吧

-(void)setShadow:(CALayer *)layer {
    layer.cornerRadius = 5;
    layer.shadowColor = [UIColor blackColor].CGColor;
    layer.shadowOffset = CGSizeMake(5, 3);
    layer.shadowOpacity = 3.0f;
}
  • 1.11 画最上边的线

类似,类似,类似 →_→

-(void)reatTopLineLayer {
    CAShapeLayer * topLine = [CAShapeLayer layer];

    CGMutablePathRef path = CGPathCreateMutable();

    CGPathMoveToPoint(path, nil, 10, 10);
    CGPathAddLineToPoint(path, nil, 90, 10);
    topLine.path = path;
    topLine.strokeColor = [UIColor colorWithRed:0.831  green:0.529  blue:0.086 alpha:1].CGColor;
    topLine.lineWidth = 5;
    topLine.lineCap = kCALineCapRound;
    [self setShadow:topLine];
    [self.layer addSublayer:topLine];
    [_array addObject:topLine];

}

!!!!!终于,终于画完了,封装,封装,不然会累死

2 开始动画
  • 2.1 左边的动画

终于开始动画了,先来大致说一下,CAAnimation中,有CABasicAnimation,有CAKeyframeAnimation,还有CAGroupAnimation

一般这几个够用了,他们都有keyPath属性

当是在看的时候,发现这是个字符串对象,尼玛,字符串,我知道这是个毛啊,网上扒了几篇博客,也没发现什么规律

后来,后来,终于得到了一本秘籍,可以拯救世界的秘籍,然后我就基本上知道了这货应该怎么填

其实在CAAnimation,几乎所有的属性都是可以动画的,位置,颜色,等等,都可以改变,想怎么动,动什么属性,就写什么属性

比如这里的左边的线,我们要旋转,Z 轴的旋转,那就写呗transform.rotation.z,嗯,就是这货

然后就是持续时间,这里是0.4s

旋转的角度呢,这里有fromeValue和toValue,开始,结束,想怎么写,怎么写

这里从0转到到,π/8的位置, π是180°,π/2是90° π/4是45°,π/8是 22.5°,嗯,体育老师教的数学看来还够用

_leftBaseAnimation.timingFunction 这个货是设置运动的模式的,是先快后慢,还是先慢后快,还是一开始慢后来加速,然后在减速…这个曲线可以自定义

这里用系统的,因为是向上摆动,所以一开始比较快,然后减速到0

然后_leftBaseAnimation.autoreverses这个是设置是否完成动画后反向在执行一遍,我们还要回来啊,妥妥的YES

_leftBaseAnimation.fillMod 这个无所谓了,我们最终还要回到起点

然后就是把这个动画添加到_leftLine上 ,后边的key可以不设置,不影响

-(void)leftAnimation {
    //leftLine

    _leftBaseAnimation = [CABasicAnimation animation];
    _leftBaseAnimation.keyPath = @"transform.rotation.z";
    _leftBaseAnimation.duration = 0.4;
    _leftBaseAnimation.fromValue = [NSNumber numberWithFloat:0];
    _leftBaseAnimation.toValue = [NSNumber numberWithFloat:M_PI_4/2];
    _leftBaseAnimation.timingFunction =[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
    _leftBaseAnimation.autoreverses = YES;
    _leftBaseAnimation.delegate = self;
    _leftBaseAnimation.fillMode = kCAFillModeForwards;
    [_leftLine addAnimation:_leftBaseAnimation forKey:@"leftBaseAnimation"];

    //leftCircle

    //因为这里要使圆球,按照一个曲线与运动, CAKeyframeAnimation正好满足我们的需求
    //先创建一个路径,画一个22.5°的圆弧
    _leftPath = CGPathCreateMutable();
    CGPathAddArc(_leftPath, nil, 25, 10, 60, M_PI_2,M_PI_2+M_PI_4/2, NO);
    _leftKeyframeAnimation = [CAKeyframeAnimation animation];
    //自己本身要运动,所以肯定是position了,还记得上边设置的时候,position的位置要设为竖线的一端,这就是原因,这样才能按照曲线运动,
    _leftKeyframeAnimation.keyPath = @"position";
    //计算模式,可以不写,对我们的动画没有影响
    _leftKeyframeAnimation.calculationMode = kCAAnimationCubic;
    //设置动画的路径,然后小球就会跟着动
    _leftKeyframeAnimation.path = _leftPath;
    //持续时间是0.4s
    _leftKeyframeAnimation.duration = 0.4f;
    //运动模式,先快后慢
    _leftKeyframeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    //结束之后,在反过来继续运行
    _leftKeyframeAnimation.autoreverses = YES;
    //基本没什么卵用
    _leftKeyframeAnimation.fillMode = kCAFillModeForwards;
    //设置代理,监听动画结束
    _leftKeyframeAnimation.delegate = self;
    //这里设置一下value方便动画结束之后可以检测到,是这个动画
    [_leftKeyframeAnimation setValue:@"left" forKey:@"left"];
    //添加动画
    [_leftCircle addAnimation:_leftKeyframeAnimation forKey:@"leftKeyframeAnimation"];

}
  • 2.2 右边的动画

基本上是一样的,就是旋转的角度不一样,一个向左,一个向右,参照上边的注释即可

-(void)rightAnimation {
    //RightLine

    _rightBaseAnimation = [CABasicAnimation animation];
    _rightBaseAnimation.keyPath = @"transform.rotation.z";
    _rightBaseAnimation.duration = 0.4;
    _rightBaseAnimation.fromValue = [NSNumber numberWithFloat:0];
    _rightBaseAnimation.toValue = [NSNumber numberWithFloat:-M_PI_4/2];
    _rightBaseAnimation.timingFunction =[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
    _rightBaseAnimation.autoreverses = YES;
    _rightBaseAnimation.fillMode = kCAFillModeForwards;
    _rightBaseAnimation.delegate = self;
    [_rightLine addAnimation:_rightBaseAnimation forKey:@"rightBaseAnimation"];

    //RightCircle

    _rightPath = CGPathCreateMutable();
    CGPathAddArc(_rightPath, nil, 75, 10, 60, M_PI_2,M_PI_2-M_PI_4/2, YES);
    _rightKeyframeAnimation = [CAKeyframeAnimation animation];
    _rightKeyframeAnimation.keyPath = @"position";
    _rightKeyframeAnimation.calculationMode = kCAAnimationCubic;
    _rightKeyframeAnimation.path = _rightPath;
    _rightKeyframeAnimation.duration = 0.4f;
    _rightKeyframeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    _rightKeyframeAnimation.autoreverses = YES;
    _rightKeyframeAnimation.fillMode = kCAFillModeForwards;
    _rightKeyframeAnimation.delegate = self;
    [_rightKeyframeAnimation setValue:@"right" forKey:@"right"];
    [_rightCircle addAnimation:_rightKeyframeAnimation forKey:@"rightKeyframeAnimation"];
}
3 控制动画的运行

大致流程是这样的

  • 我们应该这样处理动画
  • 先开始左边的动画
  • 左边的动画完成之后,也就是摆上去之后,在摆下来
  • 开始右边的动画
  • 右边的动画摆上去,在摆下来之后
  • 在开始左边的动画
  • 3.1 现在在.h文件中写两个方法

一个开始动画,一个结束动画

名字写的不好,随便吧

#import <UIKit/UIKit.h>

@interface LoaddingAnimation : UIView


-(void)showAnimation;

-(void)hideAnimation;

@end
  • 3.2 开始动画

我们在开始动画的方法中,创建所有必须得layer,然后开始做动画

还记得我们一开始的时候,定义的那个block么,现在就要排上用场了

我们会这么做,一开始就是一个空白的视图,我们调用showAnimation的时候,创建,然后开始动画

结束的时候,我们把所有layer全部移除

-(void)showAnimation {

    [self creatLeftLine];
    [self creatLeftRound];
    [self creatLayer];

    [self creatRightLine];
    [self creatRightRound];
    [self reatTopLineLayer];

    [self leftAnimation];


    //为了防止Block中循环引用,我们要这么处理

    //    [_rightKeyframeAnimation setValue:@"right" forKey:@"right"];
     //    [_leftKeyframeAnimation setValue:@"left" forKey:@"left"];
     //还记得我们上边这两句么,这样的话,我们就可以监听到到底是那个动画完成了
     //因为我们是在动画结束之后调用的,所以按照上边的逻辑,我们就在检测到左边完成的时候
     //让右边去动画
     //同样,右边完成之后,让左边去动画
    __weak LoaddingAnimation * load = self;

    animationFinishBlock = ^(CAAnimation * animation){

        if ([[animation valueForKey:@"left"] isEqualToString:@"left"]) {
                //检测到左边结束后,开始右边的动画        
            [load rightAnimation];
        }else if([[animation valueForKey:@"right"] isEqualToString:@"right"])
        {
                //检测到右边动画的时候,开始左边的动画
            [load leftAnimation];
        }
    };

}
  • 3.3 结束动画

当结束动画的时候,block什么都不干

还记得我们一开始声明的数组么,我们把所有的layer都添加进去了

现在我们就可以在动画结束之后,把他们移除了

-(void)hideAnimation {
    NSLog(@"结束动画");

    animationFinishBlock = ^(CAAnimation * animation){

    };

    for (CALayer * layer in _array) {

        [layer removeFromSuperlayer];
    }

}
  • 3.4 动画结束的代理方法
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {

    animationFinishBlock(anim);

}

动画结束后,我们调用这个block就行了,其实相当于下边的两种情况

  • 3.4.1 显示动画的时候
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
     if ([[anim valueForKey:@"left"] isEqualToString:@"left"]) {
            [load rightAnimation];
        } else if([[anim valueForKey:@"right"] isEqualToString:@"right"]) {
            [load leftAnimation];
        }
}
  • 3.4.2 隐藏动画的时候
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {

}

这个应该不难理解,按照上边的逻辑,就应该是这个样子的

好了,基本上就完成了,但离目标还有一点

Demo

秘籍传送门

原文链接: iOS开发 ----- 加载动画之牛顿摆的实现

UITableViewCell 在iOS8下自适应

在使用 table view 的时侯经常会遇到这样的需求:table view 的 cell 中的内容是动态的,导致在开发的时候不知道一个 cell 的高度具体是多少,所以需要提供一个计算 cell 高度的算法,在每次加载到这个 cell 的时候计算出 cell 真正的高度。

在 iOS 8 之前

没有使用 Autolayout 的情况下,需要实现 tableView delegate 的

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

方法,在这个方法中计算并返回 cell 的高度。

通过查看这个 delegate 方法的文档后,可以知道,在每次 reload tableview 的时候,程序会先计算出每一个 cell 的高度,等所有高度计算完毕,确定了 tableview 的总的高度后,才开始渲染视图并显示在屏幕上。这意味着在显示 table view 之前需要执行一堆的计算,并且这是在主线程中进行的,如果计算量太大程序就很有可能出现卡顿感。比如: table view 的数据有上千条,或者计算高度的代码中还要先获取图片再根据图片计算高度,这些操作都是非常慢的。

如果在 cell 中使用了 autolayout,在计算 cell 高度时会更麻烦。

为什么不能等滚动到某个 cell 的时候,再调用计算这个 cell 高度的 delegate 呢?原因是 tableview 需要获得它的内容的总高度,用这个高度去确定滚动条的大小等。直到 iOS 7 UITableViewDelegate中添加了新的 API

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

这个方法用于返回一个 cell 的预估高度,如果在程序中实现了这个方法,tableview 首次加载的时候就不会调用heightForRowAtIndexPath 方法,而是用 estimatedHeightForRowAtIndexPath 返回的预估高度计算 tableview 的总高度,然后 tableview 就可以显示出来了,等到 cell 可见的时候,再去调用heightForRowAtIndexPath 获取 cell 的正确高度。

通过使用estimatedHeightForRowAtIndexPath 这个 Delegate 方法,解决了首次加载 table view 出现的性能问题。但还有一个麻烦的问题,就是在 cell 没有被加载的时候计算 cell 的高度。这种计算实际上是必须的,然而在 iOS 8 开始,你可能可以不用再写这些烦人的计算代码了!

iOS 8 的魔法

在 iOS 8 中,self size cell 提供了这样一种机制:cell 如果有一个确定的宽度/高度,autolayout 会自动根据 cell 中的内容计算出对应的高度/宽度。

TableView 中的 cell 自适应

要让 table view 的 cell 自适应内容,有几个要点:

  • 设置的 AutoLayout 约束必须让 cell 的 contentView 知道如何自动延展。关键点是 contentView 的 4 个边都要设置连接到内容的约束,并且内容是会动态改变尺寸的。
  • UITableView 的 rowHeight 的值要设置为 UITableViewAutomaticDimension
  • 和 iOS 7 一样,可以实现 estimatedHeightForRowAtIndexPath 方法提升 table view 的第一次加载速度。
  • 任何时候 cell 的 intrinsicContentSize 改变了(比如 table view 的宽度变了),都必须重新加载 table view 以更新 cell。
例子

在 Xcode 中新建一个项目,在 storyboard 中创建一个 UITableViewController 的 IB,创建一个如下样子的 cell:

这个 cell 中有 3 个元素,其中

==imageView 的 autoLayout 约束为:==

  • imageView 左边离 contentView 左边 5
  • imageView 上边离 contentView 上边 5
  • imageView 的 width 和 height 为 33
  • imageView 下边离 contentView 下边大于等于 5(为了防止内容太少,导致 cell 高度小于图片高度)

==titleLabel 的 autoLayout 约束为:==

  • titleLabel 左边离 imageView 右边 8
  • titleLabel 上边和 imageView 上边在同一只线上
  • titleLabel 右边离 contentView 右边 8
  • titleLabel 下边离 description 上边 1
  • titleLabel 的高度小于等于 21,优先级为 250

==descriptionLabel 的约束为:==

  • descriptionLabel 左边和 titleLabel 左边在同一直线上
  • descriptionLabel 上边离 titleLabel 1
  • descriptionLabel 下边离 contentView 下边 1
  • descriptionLabel 右边离 contentView 右边 8

然后在这个 IB 对应的 UITableViewController 中加载一些数据进去,显示效果如图:

实现这个效果,我除了设置了 autoLayout,还设置了 tableView 的 rowHeight = UITableViewAutomaticDimension,然后就是这样了。一点计算 cell 高度的代码都没有!!我连 heightForRowAtIndexPath都不用实现,真的是….爽出味啊!所以如果已经在开发 iOS 8 Only 的应用了一定要用autolayout,把烦人的计算交给 autolayout 去吧。

CollectionView 中的 cell 自适应

在 collection view 中也能让 cell 自适应内容大小,如果 UICollectionView 的 layout 是一个 UICollectionViewFlowLayout,只需要将 layout.itemSize = ... 改成 layout.estimatedItemSize = ...。 只要设置了 layout 的 estimatedItemSize,collection view 就会根据 cell 里面的 autolayout 约束去确定cell 的大小。

原理:
  1. collection view 根据 layout 的 estimatedItemSize 算出估计的 contentSize,有了 contentSize collection view 就开始显示
  2. collection view 在显示的过程中,即将被显示的 cell 根据 autolayout 的约束算出自适应内容的 size
  3. layout 从 collection view 里获取更新过的 size attribute
  4. layout 返回最终的 size attribute 给 collection view
  5. collection 使用这个最终的 size attribute 展示 cell

总结

对于已经在开发 iOS 8 only 的应用,真的可以删除heightForRowAtIndexPath中那些繁重的计算代码了!以前需要写大量计算的代码现在都可以通过拖拖 IB 上的 UI 控件就可以实现了,当然首先你要会 autolayout。让 autolayout 帮我们完成所有的工作吧。

参考

喜欢就分享给朋友们吧!

源码地址 : UITableViewCellAutolayout

iOS 列表加载圆角图片性能分析

在iOS的世界里,圆角无处不在,而且必须存在。因为圆角是符合人类视觉安全体验的,圆角让人觉得舒适,而方角在潜意识层次是具有伤害体验的,因为尖尖的东西总是有可能对人造成伤害的,所以我们更喜欢圆角。在我之前的文章中讲过,在iOS的中设置圆角是非常容易的一件事情,这也体现出苹果也是非常重视圆角这件事情的。

圆角虽好,但如果使用不当,它就是你的帧数杀手,特别当它出现在滚动列表的时候。下面来看圆角如何毁掉你的流畅度的。

实测

layer.cornerRadius

我创建了一个简单地UITableView视图,为每个cell添加了2个UIImageView实例,且为UIImageView实例进行如下设置

aImageView.layer.cornerRadius = aImageView.frame.size.width/2.0;  
aImageView.layer.masksToBounds = YES;

运行截图如下:

你们猜,现在滚动的帧率是多少。

已经跌至45帧每秒,这个帧率已经让人感觉到不那么顺滑了,如果低于40帧每秒,普通用户就会察觉明显的不流畅了。当我把cell的UIImageView实例增加至四个

现在帧率已经低于30帧每秒了

这个帧率如果出现在首屏,足以引领你的app进入垃圾级别的体验了。 现在我把UIImageView实例的size调的小一些。

平均帧率提高了大概3帧每秒。

在这里视图和圆角的大小对帧率并没有什么大的影响,数量才是伤害的核心输出啊。

layer.mask

之前有的文章说通过layer.cornerRadius和layer.mask设置圆角并没有什么差异,事实真的是这样的吗?我如下设置了圆角:

CAShapeLayer *layer = [CAShapeLayer layer];  
UIBezierPath *aPath = [UIBezierPath bezierPathWithOvalInRect:aImageView.bounds];  
layer.path = aPath.CGPath;  
aImageView.layer.mask = layer;

得到的帧率如下:

竟然只有20帧每秒了,比layer.cornerRadius还少了8帧!!!所以layer.cornerRadius实现圆角的性能是要比layer.mask要高很多。

maskView

iOS的UIView多了一个maskView方法,不过这个东西和layer.mask的表现也差不多。

原理

上面拖慢帧率的原因其实都是Off-Screen Rendering(离屏渲染)的原因。离屏渲染是个好东西,但是频繁发生离屏渲染是非常耗时的。

Off-Screen Rendering

离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。由上面的一个结论视图和圆角的大小对帧率并没有什么卵影响,数量才是伤害的核心输出啊。可以知道离屏渲染耗时是发生在离屏这个动作上面,而不是渲染。为什么离屏这么耗时?原因主要有创建缓冲区和上下文切换。创建新的缓冲区代价都不算大,付出最大代价的是上下文切换。

==上下文切换==

上下文切换,不管是在GPU渲染过程中,还是一直所熟悉的进程切换,上下文切换在哪里都是一个相当耗时的操作。首先我要保存当前屏幕渲染环境,然后切换到一个新的绘制环境,申请绘制资源,初始化环境,然后开始一个绘制,绘制完毕后销毁这个绘制环境,如需要切换到On-Screen Rendering或者再开始一个新的离屏渲染重复之前的操作。 下图描述了一次mask的渲染操作。

一次mask发生了两次离屏渲染和一次主屏渲染。即使忽略昂贵的上下文切换,一次mask需要渲染三次才能在屏幕上显示,这已经是普通视图显示3陪耗时,若再加上下文环境切换,一次mask就是普通渲染的30倍以上耗时操作。问我这个30倍以上这个数据怎么的出来的?当我在cell的UIImageView的实例增加到150个,并去掉圆角的时候,帧数才跌至28帧每秒。虽然不是甚准确,但至少反映mask这个耗时是无mask操作的耗时的数十倍的。

应对

那么如何应对这个问题呢?不要在滚动视图使用cornerRadius或者mask。如果你非要作死怎么办呢?那么这样也可以拯救你:

self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;

这样大部分情况下可以马上挽救你的帧数在55帧每秒以上。shouldRasterize = YES会使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存,当然要在视图内容不改变的情况下。

除了上面非要作死的人外,大家还是采取预先生成圆角图片,并缓存起来这个方法才是比较好的手段。预处理圆角图片可以在后台处理,处理完毕后缓存起来,再在主线程显示,这就避免了不必要的离屏渲染了。

另外也有在图片上面覆盖一个镂空圆形图片的方法可以实现圆形头像效果,这个也是极为高效的方法。缺点就是对视图的背景有要求,单色背景效果就最为理想。

总结

  • 实现圆角cornerRadius要比mask高效很多。
  • Rasterize在大部分情况下极大减少GPU工作。在有空间的情况下,大部分情况下缓存总能帮到你,不是吗?
  • 后台预处理图片也能很简单帮上你很大的忙。
  • 使用png图片(在iOS9下有优化)

转自:小心别让圆角成了你列表的帧数杀手

initWithFrame 和 initWithCoder

当我们所写的程序里没用用Nib文件(XIB)时,用代码控制视图内容,需要调用initWithFrame去初始化

- (id)initWithFrame:(CGRect)frame
{
    if (self =[super initWithFrame:frame]) {
        // 初始化代码
    }
    return self;
}

用于视图加载nib文件,从nib中加载对象实例时,使用 initWithCoder初始化这些实例对象

- (id)initWithCoder:(NSCoder*)coder
{
    if (self =[super initWithcoder:coder]) {
        // 初始化代码
    }
    return self;
}

移除所有子视图,无需循环只需要一句代码

[view.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];

使用Reveal 分析iOS APP UI

Reveal作为分析APP UI的利器,确实非常好用,用来查看任意UI布局也很方便

Reveal配置
  • 首先从Reveal官网(https://revealapp.com/) 下载APP并安装,把Reveal拖到Applications目录。(建议使用Reveal 1.6.3版本)
  • 打开Reveal,找到Reveal--Help--Show Reveal Library in Finder--iOS Library目录下的libReveal.dylib,默认路径为:/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib
  • 打开Xcode,找到 View → Navigators → Show Breakpoint Navigator
  • 在左下角点击“+”号,选择Add Symbolic Breakpoint
    Add Symbolic Breakpoint
  • Symbol框里填写UIApplicationMain
  • 点击Add Action按钮,并且确保Action选项是Debugger Command
  • 复制以下文本填写在Add Action下面的文本框。
    对于 iOS targets
expr (Class)NSClassFromString(@"IBARevealLoader") == nil ? (void *)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/RevealServer.framework/RevealServer", 0x2) : ((void*)0)

对于 tvOS targets:

expr (Class)NSClassFromString(@"IBARevealLoader") == nil ? (void *)dlopen("/Applications/Reveal.app/Contents/SharedSupport/tvOS-Libraries/RevealServer.framework/RevealServer", 0x2) : ((void*)0)

上面两个代码中的路径请自行替换成Reveal的动态库的路径,对于iOS Library,是上面(2)我们找到的默认路径/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib

tvOS请自行替换。

如下图所示:

勾选 Automatically continue after evaluating actions 选项

  • 右键点击刚刚创建的断点,并且选择 Move Breakpoint To → User
    如下图所示:


现在可以重新运行Xcode,并且在Reveal 选择需要查看的目标APP。

延长Reveal试用时间

对于官网下载的Reveal,默认给予的是30天的使用限制,2.0版本只有15天的使用限制。
我们可以通过以下方式,延长使用时间:

  • 退出Reveal
  • 输入以下命令
killall cfprefsd
rm -rf ~/Library/Preferences/com.ittybittyapps.Reveal.plist
  • 重启Mac (可以省略)

这样我们的Reveal 试用时间就又恢复到了30天。