G20骑行杭州至黄山计划

出行概要

活动时间:2016.9.2–2016.9.4

活动目的:骑游,赏景

道路状况:平坦路面为主

出发时间:2016.9.2

回程时间:2016.9.4

路程:320KM+

活动强度:一般

活动费用:含路费计划400以内

联系人:Tweiwei497435786[微信]

地图

行者路书编号:773161

Markdown preferences pane

路线

Day1 杭州西溪湿地-临安-黄山休宁县(200KM)

9.2 周五6:30(杭州西溪湿地(花蒋路文二西路交叉路口))集合出发,预计下午9:00左右到达,选择宾馆住宿。

说明:沿路会经过(安徽浙江交界处,坡山村仙境,徽州区,屯溪老街等),临安段至安徽交界处路况优良,风景优美。

路况:良好

杭州天气:阵雨

黄山天气:多云

Day2 黄山休宁县-宏村-黄山风景区(实际骑行120KM+徒步黄山3小时)

9.3 周六6:30 集合出发,预计下午1:00左右到达汤口镇景点入口,休整后徒步3小时到达山顶,晚上山顶露营。

说明:沿路会经过西递古村,宏村风景区。第二天时间较充足,可以游玩后,下午进入黄山风景区,爬山顶露营。

路况:良好

黄山天气:多云

Day3 黄山游玩-乘大巴车回程

9.4 周日早起看日出,游玩黄山后,下午4点左右在汤口镇乘坐大巴回杭州黄龙(时长3小时),预计7点左右到杭州。

黄山天气:中雨转阵雨

杭州天气:

预计费用

名称 费用 备注
住宿 未知 自带帐篷
餐费 3 * 50 = 150 餐费包含水
景区门票 免费 黄山风景区,宏村…
车费 110 黄山风景区汤口到杭州黄龙大巴费用,不包含自行车费用
其它费用 未知 包含个人消费等
总计 300 当前费用为大概统计,具体依据消费而定

注意事项

1.黄山景区物价较贵,建议自备干粮,山顶早晚温差较大,建议备足衣物。

2.如果遇期间下雨,本活动自动取消,以上天气间隔误差较大,以实际日期天气为准,会实时更新。

3.期望大家能够在出游之前提出更多的建议,将会根据相关建议做出修改。让大家都能有一个开心,快乐的美好骑行游玩时间。

4.戴好头盔手套与能量补给;需要带基本衣物,手电,防雨装备,防晒,基本药物等。

5.本次出行采取露营方式,需要自备帐篷。

免责声明

1.本活动是自发的,有一定的危险性,参加者须对自己的安全负责。活动中当由于意外事故或急性疾病等不可预测因素造成身体损伤时,团队的其他成员会尽全力救助,但不承担任何法律和经济责任,望能理解

2.任何参与本活动的人均视为同意此免责声明

参考

1.黄山最佳观日出地点有哪些?

2.遗世独立 | 日思夜想的悠悠黄山,如痴如醉的大美徽州

React Native环境搭建

安装

Homebrew

Homebrew, Mac系统的包管理器,用于安装NodeJS和一些其他必需的工具软件。

1
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

在Max OS X 10.11(El Capitan)版本中,homebrew在安装软件时可能会碰到/usr/local目录不可写的权限问题。可以使用下面的命令修复:

1
sudo chown -R `whoami` /usr/local

Node

Node使用Homebrew来安装Node.js.在安装完成了Node后,会自动安装node的包管理工具npm

1
brew install node

React Native的命令行工具(react-native-cli)

React Native的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。

1
npm install -g react-native-cli

如果你看到EACCES: permission denied这样的权限报错,那么请参照上文的homebrew译注,修复/usr/local目录的所有权:

1
sudo chown -R `whoami` /usr/local

推荐安装的工具

Watchman

Watchman是由Facebook提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能(packager可以快速捕捉文件的变化从而实现实时刷新)。

1
brew install watchman

Nuclide

Nuclide(此链接需要科学上网)是由Facebook提供的基于atom的集成开发环境,可用于编写、运行和 调试React Native应用

译注:我们更推荐使用WebStorm或Sublime Text来编写React Native应用。

创建React - Native Project

React-native-cli创建项目

1
2
3
react-native init AwesomeProject(项目名称)
cd AwesomeProject
react-native run-ios

你也可以在Nuclide中打开AwesomeProject文件夹 然后运行,或是双击ios/AwesomeProject.xcodeproj文件然后在Xcode中点击Run按钮。

  • 使用你喜欢的编辑器打开index.ios.js并随便改上几行.
  • 在iOS Emulator中按下⌘-R就可以刷新APP并看到你的最新修改!

React-native-cocoaPods

1.进入项目根目录,创建node的package.json

1
2
3
npm init
npm install react-native --save-dev
npm install react --save-dev

执行以上命令后,会在跟目录生成package.json文件,以及node_modules文件

2.进入podfile中添加如下:

1
2
3
4
5
6
7
8
pod ‘React’,:path => './node_modules/react-native', :subspecs => [
'Core',
'RCTImage',
'RCTNetwork',
'RCTText',
'RCTWebSocket',
# 添加其他RN库
]

3.在根目录下创建index.ios.js文件,具体的内容可以参看方式一的自动生成的内容。

4.进入server服务,进入node_modules/react-native/local-cli/server,执行

1
npm start

启动服务器,当看到控制台中出现:React packager ready表示启动服务成功

5.在info.plist中添加 AppTransport Security Settings 在其下再添加 Allow Arbitrary Loads选择为 yes,修改网络可以使用http请求

6.在需要的地方加入如下代码:

1
2
3
4
5
6
7
8
9
NSURL *jsCodeLocation;

jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"startRN"
initialProperties:nil
launchOptions:nil];
[self.view addSubview:rootView];

7.启动模拟器在使用RN的地方,即可看到效果.

参考

1.React Native 中文网搭建开发环境

2.ReactNative-Cocoapods-Swift-Project项目搭建

Git Hooks

Git 本身可以调用自定义的挂钩脚本,其中有两组:客户端和服务器端。客户端挂钩用于客户端的操作,如提交和合并。服务器端挂钩用于 Git 服务器端的操作,如接收被推送的提交。

进入当前的项目的目录,执行如下命令,会看到git下面hooks包含的hook操作

1
vim .git/hooks/

Git的挂钩(Hook)主要包含:

  • applypatch-msg.sample*
  • commit-msg.sample*
  • post-update.sample*
  • pre-applypatch.sample*
  • pre-commit.sample*
  • pre-push.sample*
  • pre-rebase.sample*
  • prepare-commit-msg.sample*
  • update.sample*
  • ……

有了这些hooks,我们可以对git过程操作进行hook操作,其中在以下一些场景中会有实际的应用:

1.ClangFormat代码格式化其中对pre-commit进行了提交代码到git之前的hook操作,可以检查代码的格式是否符合一定的规范.

2.当然我们在进行图形化工具进行checkout,post-merge之后进行post-checkout,post-merge的hook操作,添加脚本自动执行pod的pod update自动操作省去手动pod库文件的操作。

3.我们提交代码到主分之的时候,先做pre-merge的hook操作,检查是否能够通过编译,通过编译的情况下才能合并到主分之。

4……

问题

1.示例的名字都是以 .sample 结尾,如果你想启用它们,得先移除这个后缀

2.对文件我们需要增加执行权限chmod +x “文件名称”

参考

1.Git Community Book 中文版

2.自定义 Git - Git 钩子

3. linux文件权限查看及修改-chmod ——入门的一些常识

JSPatch

上架后的 应用 可能会遇到的一些突发状况 , 未测出的Crash、临时改点小需求 , 等等 , 我们总不能每次因为一点小改动就重新提交一次 App Store , 先不说 App Store 的审核时间 , 频繁的让用户去更新应用 , 用户也会烦的 。使用这篇文章所讲的来实现动态更新是再合适不过了

动态更新方案

  • JSPatch (代码量少,强大持续有人维护,js语言,appstore审核可通过)
  • alibaba/wax(阿里巴巴主推)
  • WaxPatch
  • 脚本
  • ….

基础原理

JSPatch用iOS内置的JavaScriptCore.framework作为JS引擎,但没有用它JSExport的特性进行JS-OC函数互调,而是通过Objective-C Runtime,从JS传递要调用的类名函数名到Objective-C,再使用NSInvocation动态调用对应的OC方法。

使用

方式一

按照JSPatch Platform进行下载SDK,按照指示接入SDK。简单方便无需要考虑后台搭建,安全等问题,达到一定的用户日活量需要收费。

方式二

JSPatch下载代码,或者通过pod管理方式引入JSPatch文件。如果只是在本地调用测试js代码是否作用,只需要调用如代码,并在jsPatchTest.js中加入测试代码:

1
2
3
4
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"jsPatchTest" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine startEngine];
[JPEngine evaluateScript:script];

如上仅仅是本地js代码测试,发布线上我们需要有自己的后台下发脚本,每次客户端启动app非实时或者becomeActivity实时检测最新的hotfix的脚本,根据当前的用户app的版本和hotfix的版本,用户自动请求下载脚本后保存在本地。在这一过程中可能会被劫持造成不可控的安全问题,为此我们需要在后台进行MD5或者RSA等加密,移动端进行解密操作,确保安全问题。

比较

Github 开源的是 JSPatch 核心代码,使用完全免费自由,若打算自己搭建后台下发 JSPatch 脚本,可以直接使用 github 上的核心代码,与 JSPatch 平台上的 SDK 无关。JSPatch 平台的 SDK 在核心代码的基础上增加了向平台请求脚本/传输解密/版本管理等功能,只用于这个平台。

问题

1.定义为ivar的成员,无法获取,特别注意。属性的访问都是通过方法调用的形式。所以代码中不要使用ivar定义成员。

2.注意写代码的时候能够拆分的代码,尽量简洁干净。避免hotfix的时候,需要重写大量的内容。

3.JSPatch获取OC class名称的方法,用于OC判读是否属于某一个类

1
2
obj.__clsDeclaration //返回的是一个字符串,通过比较改字符串与类名的字符串是否相等可以判断obj的类型。 应用于新版的JSPatch
obj.__clsName //对于使用老版本的JSPatch需要使用该字段来获取class名称

hotfix代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
defineClass("BBNewMainView", {
// instance method definitions
reloadDynamicBanners:function() {
require('BBSquareAdsView,BBAdsCollectionView');
var adsCollectionViewJS = self.adsCollectionView();
var squareAdsView = self.squareAdsView();//定义为ivar的成员,无法获取,特别注意。属性的访问都是通过方法调用的形式
var adsTop = adsCollectionViewJS.frame().y;//注意该地方获取origin.y的方式,相关文档上面存在错误
var adsHeight = adsCollectionViewJS.frame().height;
var adsBottom = adsTop + adsHeight;
squareAdsView.setFrame({x:0, y:parseInt(adsBottom), width:0, height:0});//注意这里parseInt(adsBottom),转换为int类型的
self.ORIGreloadDynamicBanners(); //添加ORIG前缀表示调用oc中的原始方法
}
})

参考

1.JSPatch官网

2.JSPatch的Github链接

3.OC转换为JSPatch(不是万能的)

4.JSPatch实现原理详解

5.iOS应用架构谈 动态部署方案

Code Snippets

code snippets可以提供一些代码片段的操作,可以节省输入需要的时间。类似于系统的输入字母后的提示效果,点击确定后自动填充。根据下面的步骤可以实现一个简单的snippets。甚至你可以创建自己snippets的library

步骤

1.输入你想要的snippets,如下(对代理的内容进行编写snippets,防止开发人员写错属性及节省时间):

1
@property (nonatomic, weak) id<BBMartshowDetailDelegate> martshowDetailDelegate;

2.然后单击并按住代码块,直到文本光标变为箭头光标。接着将代码块拖放到code snippet library中,然后松开鼠标,弹出视图即使snippets内容,修改title(snippet的名称),Commpletion Short(快捷数据的内容),Platform,Language等。如下图的展示,我在这里取得名字property delegate(snippets中的名称),Completion Shortcut为pd(快捷输入提示),Completion Scopes设置为Class Interface Methods表示只在.h中才会起作用:

General preferences pane

3.修改文本框的内容,如下(<#content#>)表示可以替换的内容:

1
@property (nonatomic, weak) id<<#BBMartshowDetailDelegate#>> <#mde>#;

4.在.h的文件的属性声明的地方输入pdxcode将会自动提示,点击enter确认即可。或者通过选中snippets拖入代码中也可以。

5.当然我们也可以修改当前的snippets,只需要双击snippets,然后edit即可。

下面会通过gif图展示该如何操作:

General preferences pane

iOS Runtime

iOS Runtime简称运行时,对于一个iOS开发者来说深刻理解它至关重要,可以毫不夸张的说Runtime是OC这门语言的灵魂。那么什么是运行时呢?接下来这篇文章会向你详细讲解,以及如何使用。

什么是Runtime

  • 简单回顾一下C语言的顺序执行的特性:对于C语言,函数的调用在编译的时候会决定调用哪个函数。编译完成后直接顺序的执行,没有任何的二义性。
  • Runtime就是系统在运行的时候的一些机制,其中最主要的是消息转发的机制。OC的函数调用实际就是消息发送的过程,即使这个函数并未实现,只要申明过就不会报错。只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
  • OC中一切都被设计成一个对象,当一个类A被初始化为一个实例a,a是一个对象,实际上类A本质上也是一个对象。
  • 代码在程序运行过程中都会被转化成runtime的C代码执行,例如:[target doSomething];会被转化成objc_msgSend(target,@selector(doSomething));的方式发送消息。

OC中类的定义

打开objc.h文件,会发现关于类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;//OC中类的定义,一个对象

/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;//OC中类的实例定义
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;//OC中类的实例

typedef struct objc_selector *SEL;//方法

typedef id (*IMP)(id, SEL, ...); //方法的指针

runtime.h中会发现objc_class实际上是一个结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;//指针,实例的isa指向类对象,类对象的isa指向元类

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;//指向父类
const char *name OBJC2_UNAVAILABLE;//类名称
long version OBJC2_UNAVAILABLE;//类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info OBJC2_UNAVAILABLE;//一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size OBJC2_UNAVAILABLE;//该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;//用于存储每个成员变量的地址
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;//与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache OBJC2_UNAVAILABLE;//指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;//协议列表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

以及其它等定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;//方法的描述

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;//实例变量的描述

/// An opaque type that represents a category.
typedef struct objc_category *Category;//类别的描述

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;//属性的描述

typedef struct objc_object Protocol;//协议的描述

...

获取列表

有时候会有这样的需求,我们需要知道当前类中每个属性的名称(比喻字典转模型的操作,避免后台传递null的问题,检查字典的Key和模型对象的属性名字不匹配问题,swizzing的操作等),在此可以通过runtime的一系列的方法获取类的信息(属性列表,方法的名称列表,成员变量列表,协议列表)等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}

//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}

//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}

//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}

方法调用

让我们看一下方法调用在运行时的过程

如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。

1.首先,在相应操作的对象中的cache缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。

2.如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行。并且将method放置cache中

3.如果没找到,去父类指针所指向的对象中执行1,2.

4.以此类推,如果一直到根类还没找到,转向拦截调用。

5.如果没有重写拦截调用的方法,程序报错。

  • 重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了。
  • 如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。

拦截调用

在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢。

拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。

1
2
3
4
5
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
  • 第二个方法和第一个方法相似,只不过处理的是实例方法。
  • 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
  • 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。

动态添加方法

重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?有一个办法是根据传进来的SEL类型的selector动态添加一个方法。如下图:

General preferences pane

关联对象

就是给对象添加内容,假如有如下场景:现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性,现在你能想到的就是继承或者采用分类的方式去做,这样是否太麻烦。这时我们可以通过runtime的属性关联来实现

设置内容:

1
2
3
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showRulePage:)];
objc_setAssociatedObject(tapGR, "ruleUrl", ruleUrl, OBJC_ASSOCIATION_COPY_NONATOMIC);
[messageLabel addGestureRecognizer:tapGR];

objc_setAssociatedObject的四个参数:

1.id object给谁设置关联对象。

2.const void *key关联对象唯一的key,获取时会用到。

3.id value关联对象。

4.objc_AssociationPolicy关联策略,有以下几种策略(retainCount):

1
2
3
4
5
6
7
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};

获取内容:

1
2
3
- (void)showRulePage:(UITapGestureRecognizer *)tapGr {
NSString *ruleUrl = objc_getAssociatedObject(tapGr, "ruleUrl");
}

objc_getAssociated

1.id object获取谁的关联对象。

2.const void *key根据这个唯一的key获取关联对象。Object的两个参数

方法交换

方法交换,顾名思义,就是将两个方法的实现交换。例如,将A方法和B方法交换,调用A方法的时候,就会执行B方法中的代码,反之亦然。这是参考Mattt大神在NSHipster上的文章自己写的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#import "UIViewController+swizzling.h"
#import <objc/runtime.h>

@implementation UIViewController (swizzling)

//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (void)load{
//方法交换应该被保证,在程序中只会执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

//获得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己实现的将要被交换的方法的selector
SEL swizzSel = @selector(swiz_viewWillAppear:);
//两个方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method swizzMethod = class_getInstanceMethod([self class], swizzSel);

//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//如果成功,说明类中不存在这个方法的实现
//将被交换方法的实现替换到这个并不存在的实现
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否则,交换两个方法的实现
method_exchangeImplementations(systemMethod, swizzMethod);
}

});
}

- (void)swiz_viewWillAppear:(BOOL)animated{
//这时候调用自己,看起来像是死循环
//但是其实自己的实现已经被替换了
[self swiz_viewWillAppear:animated];
NSLog(@"swizzle");
}

@end

基本的讲解已经完成,下一章会着重讲解swizzle的相关内容。

参考

1.Objective-C总Runtime的那点事儿(一)消息机制

getenv函数,获取环境变量的值

有的时候,想要在自己的机器当前公用的工程测试特定情况的内容,提交代码之后不想影响到其它开发人员,拉取新的代码后不影响自己的工程,那么通过配置环境变量的方式将会解决该问题,getenv()用来取得参数环境变量envvar的内容。

getenv()函数

参数envvar为环境变量的名称,如果该变量存在则会返回指向该内容的指针。环境变量的格式为envvar=value。getenv函数的返回值存储在一个全局二维数组里,当你再次使用getenv函数时不用担心会覆盖上次的调用结果

返回值: 执行成功则返回指向该内容的指针,找不到符合的环境变量名称则返回NULL。如果变量存在但无关联值,它将运行成功并返回一个空字符串,即该字符的第一个字节是null。

在iOS中如何使用该函数,如下图(之前有讲过,修改改配置文件默认gitignore会忽略掉,所以在本地修改是无法提交的也就是不会影响其它开发):

General preferences pane

方法使用

如果要使用,则在环境变量中定义相应的变量,并且赋值如上图中,否则不会产生影响。

1
2
3
4
5
6
7
8
9
10
11
+ (BOOL)boolValueByKey:(NSString*)key default:(BOOL)defaultValue
{
BOOL value;
char* valueChar = getenv([key UTF8String]);
if(valueChar != nil && valueChar != 0){//如果为nil,并且不为null,则读取配置文件,否则默认设置
value = [[NSString stringWithFormat:@"%s", valueChar] boolValue];
} else {
value = defaultValue;
}
return value;
}
1
2
3
if ([self boolValueByKey:@"test" default:NO]) {
NSLog(@"TESTTEST");
}

去掉Xcode工程相关警告

在我们的项目中,通常使用了大量的第三方代码,这些代码可能很复杂,我们不敢改动他们,可是作者已经停止更新了,当sdk升级或者是编译器升级后,这些遗留的代码可能会出现许许多多的警告,又或者我们自己部分的代码出现了警告,那么我们能够有办法将这些警告清理掉么?以下讲的的一些方法可能比较有效的帮助你解决问题。

第三方代码

最直接,最一劳永逸,最安全的方式,直接找到警告的那段代码,改为不警告.这个方式,最安全.可是它有一个问题,就是,当我们很多文件都有这种类型的警告的时候,我们就需要改动很多很多的源码了, 对于不是我们写的源码,使用这种方法侵入性太强,显然就不太可取了。

1.使用编译器提供的宏来操作,如下面我们经常看到的:

1
2
3
4
5
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
//写在这个中间的代码,都不会被编译器提示-Wdeprecated-declarations类型的警告
dispatch_queue_tcurrentQueue =dispatch_get_current_queue();
#pragma clang diagnostic pop

或者

1
2
3
#ifndef __clang_analyzer__
// Code not to be analyzed
#endif

具体的警告对应的类型我们可以查看Which Clang Warning Is Generating This Message,当然也可以通过查看具体的警告信息,选择Reveal in Log可以查看到是哪种“-Wno-shorten-64-to-32”的警告,但是这种改法需要更改大量的地方。

2.关闭某一个指定文件制定类型的警告,可以选择工程具体的target->Build Phases->Compile Sources找到具体的文件,双击后填写需要忽略的类型,类似忽略不使用arc-fno-objc-arc我们可以对这个特定的文件加入-Wno-shorten-64-to-32表示忽略该中类型的警告。这种工作量还是比较大。

3.关闭工程中指定类型的警告,target->Build Settings->Other Warning Flags 加入-Wno-shorten-64-to-32表示忽略工程中该类型的所有警告。这种类型会将当前工程自己部分代码也忽略掉。

4.在我们使用podfile管理第三方的文件cocoapods的时候,我们可以在引用库的的时候加入inhibit_warnings => true

1
pod 'XGPush',             '2.3',     :inhibit_warnings => true

当前工程代码

如果是我们工程中,那么建议最好全部按照最新的要求进行全部修改,为了避免出现警告,可以采取一些有效的措施。

1.我们可以将int的类型的都定义成NSInteger,float定义成CGFloat的类型,避免在不同的armv中出现警告的问题。

2.我们可以将target->Build Settings->Treat Warnings as Errors(将警告视错误)防止开发人员提交警告。

参考

1.is-it-possible-to-suppress-xcode-4-static-analyzer-warnings

2.performselector-may-cause-a-leak-because-its-selector-is-unknown/7073761#7073761

3.ignore-all-warnings-in-a-specific-file-using-llvm-clang

iOS Crash

iOS崩溃问题一直是令开发人员痛苦的事情,那么如何高效的定位崩溃问题并有效的解决呢?下面所讲解的一些方式,都能够有效解决崩溃问题。当前归类三种情况,开发人员开发崩溃,测试内测崩溃,使用者线上崩溃。

开发崩溃定位

1.当前xcode处于开发模式的时候,使用僵尸调测或者使用LLDB,GDB命令调测,打印崩溃信息,具体选择如下图,选择LLDB调测模式(属于xcode当前自带的内容),如果要选择GDB调测模式,需要下载相关GDB 相关的插件,根据崩溃内存地址内容,使用LLDB,GDB命令可以轻松定位相关的信息,在edit scheme中同时需要勾选Diagnostics的Run中Objective-c (Enable Zombie Objects,Malloc Stack)中显示内存地址。

2.使用全局断点检测当前xcode运行出现的崩溃信息,对于当前的崩溃位置断点会自动定位。对于没有成功定位的时候,可以结合方式1定位相关的bug

3.如果是Xcode7以上,在edit sheme中需要够炫Diagnostics的Run选项中的Runtime Sanitization的Enable Address Sanitizer,出现崩溃将会自动定位。

4.message sent to deallocated instance 0x6d564f0类似的这种崩溃问题,可以通过LLDB的命令(bt或者info malloc-history 0x6d564f0)定为道崩溃信息。当然我们也可以通过打开”活动监视器”找到对应的PID(进程ID)和当前的崩溃地址在终端输入,可以定为到详细的当前崩溃对象的完整的生命周期。

1
sudo malloc_history 50127 0x6d564f0

内测崩溃定位

1.内测版本具体的某一个设备崩溃了,可以将该设备交与开发人员,开发人员通过xcode到处设备中的crash崩溃信息,即可查看崩溃信息,分析后定为

2.内测版本中也可通过集成第三方的崩溃捕捉工具进行

  • fir的BugHD平台CI过程上传ipa同时上传每个版本的dsym文件
  • 也可以可以通过Crashlytics,友盟这些第三方的平台进行(友盟不可以上传dsym)
  • Bugtags,超级强大的bug管理系统,方便测试人员提交bug并且高效的管理,可以直接使用手机操作提交bug,但是这个是要收费的。

线上崩溃定位

1.对于需要发布的项目,可以事先在程序中将如(Crashlytics,友盟等第三方崩溃定位的文件,也可以使用QuincyKit结合PHP服务端搭建自己的bug系统管理后台),根据archive文件(也就是十六进制文件,发包前的编译文件后的内存地址,实际第三方地位到的内存地址就是这个)定位第三方服务端提供的崩溃信息(内存地址)。如下图友盟定位的崩溃信息日志,
对于这样的一个崩溃信息,大多数人措手不及,但是通过dsym文件(十六进制内存地址),根据提示信息

1
2
3   LR                                  0x10c527 LR + 1082663
21 LR 0xf258b LR + 976267

根据之前发包的archive文件,(关于怎么找到archive文件,Command + shift +2)
找到对应时间的archive文件后,选择鼠标右键find show archive文件后,找到文件后,选择显示包含的内容知道DAWF文件后具体的路径显示简介即可找到/Users/qianjx_tyrbl/Library/Developer/Xcode/Archives/2014-08-12/4.30.xcarchive/dSYMs/LR.app.dSYM/Contents/Resources/DWARF,进入DWARF文件,在terminate终端输入

1
2
3
dwarfdump --uuid LR //查看当前项目的uuid(LR表示项目名称),是否和第三方定位的崩溃匹配uuid
atos -arch armv7 -o LR 0x10c527 //armv7内存地址对应的内容
atos -arch arm64 -o LR 0x10c527 //arm64内存地址对应的内容

2.当然如果你是用电脑archive的方式,在上传appstore的时候,苹果会有提示是否勾选上传dsym文件,那么在本台机器上面即可查到对应提交版本所有线上崩溃的问题。

Mac开发工具

Mac上面经常需要要安装的一些强大软件,便于开发人员开发工作。

下面介绍一些Mac上面经常使用的开发软件的工具

  • iterm2被称为是mac上最强大的终端,各种功能都很强大,具体特色功能见这里。然后搭配安装ohmyzsh,中文版本详解

  • 为了增加设计能效,我们的日常UI设计工具将会从Photoshop转为Sketch,带有布局、坐标、属性等开发需要的信息组合而成的html标注稿,更加方便UI和技术的对接。

  • Gif Brewery,一款能够将move转变成gif的工具,当然苹果的appstore即可下载,配合苹果的QuickTime Player进行选择->文件->新建屏幕录制->保存,打开Gifbrewery导入录制的屏幕后,选择转换为gif图保存即可。

  • MacDown一款适合代码开发人员的编辑文本的工具,而且Github等大平台都支持,语法简单实用。