Xcode中将环境变量当作调试开关

在开发新功能的过程中有时会为了进行一些快速的测试,会在上下文中修改变量的值或者执行一些方法来配置测试环境, 一但这么做就有可能忘记移除测试代码,将其编译到最终的发由的产品中,于是为了降低风险用#if DEBUG来包装了一下测试代码, 把风险控制在开发环境。
但是这样依然有可能干扰团队其它成员的开发。如果用宏定义开关来控制,偶尔也会因把配置文件提交而造成干扰。
那么如何做才能减少这些弊端,既能配置测试环境又不会不小心把测试代码提交到版本库呢?
想到Xcode的工程文件中的非公享Scheme是分用户的,那么里面的配置是不会扩散到团队的,可以从这里入手,看了下Xcode的Scheme配置,可以指定环境变量,在应用中就可以通过环境变量来配置测试环境,一来可以不干扰其它成员,二来也可以把一些常用的测试代码共享给别的人用。使用POSIX的getenv方法也比较方便进行标记判断,也可以带一些参数上去。
比如:

屏幕快照 2016-04-07 22.41.39


#if DEBUG
if (getenv("reset")) {
[Debugger resetApp];
}
#endif


#if DEBUG
char *tab_index = getenv("INITIAL_TAB");
if (tab_index) {
[_tabBarController setSelectedIndex:atoi(tab_index)];
}
#endif

这样就可以通过在scheme中勾选要调试的选项来进行测试了。勾选了reset,那么每次启动时就清掉所有的数据,勾选了INITIAL_TAB就会在打开App的时候自动进入对应的Tab。

除此之外如果简单的修改一些变量,可以在Xcode的断点命令中执行expr var = val这种操作,也是不会干扰到正常代码的一个方法。

Xcode 5.1 下 提示 duplicate declaration of method 的解决方法

升级到Xcode5.1后遇到一个奇怪的问题: llvm5.1在编译SVProgressHUD时会报错,提示类方法与实例方法重复,即 “duplicate declaration of method” 对比了两个方法,发现两个方法中一个参数声明为CGFloat一个声明为float, 两个类型不匹配。估计被认为是尝试重载方法,修改一致后这个error就消失了。经尝试只有方法在类的Extension中声明时会有这个问题,如果在Category或者@interfacer中声明则不会有此问题。

objc block是如何实现的

Objective-C中引入的Block给代码的编写以及多线程的处理带来了很多方便,但它是如何实现的呢?可以借助clang的–rewrite-objc来揭开她的面纱。

因为下面的代码会涉及一点c++ 在揭开之前先说一点c++的简单知识。
struct在c++中与class一样都是声明一个类,但struct中默认都声明为public。既然是个类struct就可以有自己的构造函数。

struct Foo{
  int bar;
  Foo(int _bar) {
    bar = _bar
  };
}
Foo f = Foo(1);
int x = f.bar;

这里Foo(int _bar)就是Foo的构造函数,与objc中的init类似。

关于struct的说明就这些,以下把struct都叫类, 下面来看block

看一段代码,给hi赋值了一个打印”hello!”的block,然后调用它

#import 
int main()
{
	void (^hi)(int a) = ^(int a){printf("hello %d\n", a);};
	hi(1);
	return 0;
}

使用clang -rewrite-objc test.m得到test.cpp, 看下转化后的代码, 这里省去部分无关的代码。

# 2 "test.m" 2

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

#line 4 "test.m"
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
printf("hello %d\n", a);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

#line 2 "test.m"
int main()
{
 void (*hi)(int a) = (void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
 ((void (*)(__block_impl *, int))((__block_impl *)hi)->FuncPtr)((__block_impl *)hi, 1);
 return 0;
}

我们看到之前的代码变成了下面的样子

 void (*hi)(int a) = (void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
 ((void (*)(__block_impl *, int))((__block_impl *)hi)->FuncPtr)((__block_impl *)hi, 1);

看到hi()的调用实际是调用了hi->FuncPtr所指向的函数并把hi和原来的能数作为参数列表。
那么FuncPtr指向了哪个函数呢,从__main_block_impl_0的构造函数和hi的创建可以看到FuncPtr指到了__main_block_func_0。下面看一下__main_block_func_0的实现

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
printf("hello %d\n", a);
}

这里就是之前写的block里面的内容,那么这里为什么会把block自己传递进来了,这个后面再看。

到此为至,看到了之前写的block的内容会变成一个c函数,原来引用的block的变量会指向到一个__main_block_impl_0的实例上。__main_block_impl_0会有一个指针指向到变成的c函数上。

block有个特点是可以抓住block外的变量,比如下面这段代码:

#import 
int main()
{
	int foo = 1;
	void (^hi)() = ^{printf("hello! %d\n", foo);};
	hi();
	return 0;
}

rewrite后

# 2 "test1.m" 2

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int foo;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _foo, int flags=0) : foo(_foo) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

#line 5 "test1.m"
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int foo = __cself->foo; // bound by copy
printf("hello! %d\n", foo);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

#line 2 "test1.m"
int main()
{
 int foo = 1;
 void (*hi)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, foo);
 ((void (*)(__block_impl *))((__block_impl *)hi)->FuncPtr)((__block_impl *)hi);
 return 0;
}

对比之前的代码,发现block实例化的时候把foo这个变量传递了进去并保存在block的实例中,在block对应的函数中,我们看到了如何使用这个变量

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int foo = __cself->foo; // bound by copy
  printf("hello! %d\n", foo);
}

所以block的外部变量引用是在初始化的时候会把变量拷贝一份到block实例中,在block调用时,再使用这一份拷贝的数据。

block还有一个功能修改一个外部变量的值,即用__block修饰的变量,再来看一段示例代码

#import 
#import 
int main()
{
   	__block int foo = 1;
	void (^hi)() = ^{foo=2;};
	hi();
	printf("foo: %d\n", foo);
	return 0;
}

clang test2.m -lobjc -rewrite-objc

# 2 "test2.m" 2
struct __Block_byref_foo_0 {
  void *__isa;
__Block_byref_foo_0 *__forwarding;
 int __flags;
 int __size;
 int foo;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_foo_0 *foo; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_foo_0 *_foo, int flags=0) : foo(_foo->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

#line 5 "test2.m"
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_foo_0 *foo = __cself->foo; // bound by ref
(foo->__forwarding->foo)=2;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->foo, (void*)src->foo, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->foo, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

#line 2 "test2.m"
int main()
{
    __attribute__((__blocks__(byref))) __Block_byref_foo_0 foo = {(void*)0,(__Block_byref_foo_0 *)&foo, 0, sizeof(__Block_byref_foo_0), 1};
 void (*hi)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_foo_0 *)&foo, 570425344);
 ((void (*)(__block_impl *))((__block_impl *)hi)->FuncPtr)((__block_impl *)hi);
 printf("foo: %d\n", (foo.__forwarding->foo));
 return 0;
}

这次变化略大,还是从main开始看。
原来的foo从int变成了__Block_byref_foo_0, 其它的调用与之前差不多,注意到后面对foo的操作都是在修改或获取foo.__forwarding->foo。

看一下__Block_byref_foo_0的定义,

struct __Block_byref_foo_0 {
  void *__isa;
 __Block_byref_foo_0 *__forwarding;
 int __flags;
 int __size;
 int foo;
};

和实例化所使用的方法

__Block_byref_foo_0 foo = {(void*)0,(__Block_byref_foo_0 *)&foo, 0, sizeof(__Block_byref_foo_0), 1}

注意到这里的__forwading引用了foo也就是其自身, 为什么要这样做呢,为了在block copy到堆后可以修改block外的变量。

从上面的代码可以看到block的创建是在栈上,如果一旦函数结束foo和block就会被释放掉,所以需要在作用域外使用block时会对block做一次copy操作,把block从栈拷贝到堆上,拷贝完成后foo.__forwarding会指向堆里的foo这样,堆中的foo.__forwarding指向自身,后面需对foo的操作都会对堆中的内存进行修改,即使作用域结束栈中的变量无效,也能正常对__block修饰的变量进行读写了。

简单的介绍了一下block的原理与automatic variable capturing的原理. 再看下下面几个会出现异常的代码。

typedef void (^blk_t)(void);
blk_t createBlock(void) {
  int a = 1;
  return ^{ printf("a: %d\n", a);}
}

int main(int argc, char **argv) {
  createBlock()();
  return 0;
}

这段代码在非arc下运行时会出问题。因为a在createBlock后被干掉了,如果声明为__block int a是否会出问题呢?感兴趣的盆友可以自行验证一下。

Xcode 4 无法打开 Xcode 5 DP 打开过的工程文件 解决方法

试用Xcode 5 DP打开现有工程文件后再用Xcode4打开后 Xcode 4 会进入崩溃模式
折腾了几次发现下面的方法可以让工程文件恢复

如果在Xcode5-DP中打开过xib文件需要在侧栏中修改Interface Builder Document下的Open in为Xcode 4.6, 然后Clean,关掉工程,再到DerivedData中删掉对应的文件夹

如果遇到打开xib还会崩的话可以找一个没有用Xcode5-DP打开过的xib把两个文件用文本编辑器打开,对比着把前几行的版本号修改一下

另外还可以尝试删除 .xcodeproject中的.xcworkspace 与 xcuserdata

还有打开后4下断点失效的问题,可以试试设置link-time optimization为NO

create cgcontext

创建一个 CGContext 的方法

CG_INLINE CGContextRef CGContextCreate(CGSize size)

{

CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();

CGContextRef ctx = CGBitmapContextCreate(nil, size.width, size.height, 8, size.width * (CGColorSpaceGetNumberOfComponents(space) + 1), space, kCGImageAlphaPremultipliedLast);

CGColorSpaceRelease(space);

return ctx;

}

CG_INLINE UIImage* UIGraphicsGetImageFromContext(CGContextRef ctx)

{

CGImageRef cgImage = CGBitmapContextCreateImage(ctx);

UIImage* image = [UIImage imageWithCGImage:cgImage scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];

CGImageRelease(cgImage);

return image;

}

 

Xcode中调试位置移动

Xcode/iPhone Simulator可以通过使用GPX来模拟当前位置,有时需要测试位置变动时可以使用这个特性来提高生产效率

关于gpx1.0的标准这里(http://www.topografix.com/gpx_manual.asp)有一篇文档。

实际应用当中可以通过使用google earch配合一些工具来生成gpx。

首先在http://www.google.com/earth/index.html下载 Google Earth

1 移动到想要添加路径点的地方

2 在菜单中点击 添加/路径 或者从工具栏按对应的图标

3 把弹出的窗口放到一边,在地图上用鼠标点想要的路径点

4 添写名称点确认

5 在侧栏中右键选择创建好的路径,点“将位置另存为”, 然后保存为kml

到此便完成了kml的创建

http://www.gpsvisualizer.com 提供了这一个功能

打开网站后会看到 “Upload a GPS file”

1 点边上上传控件选取gpx文件

2 在Choose an output format 中选则”GPX file”

3 点Go!

4 转换好的GPX会在网站上显示出来,把它粘贴到一个gpx文件中

xcode貌似对生的GPX格式不支持, 只需要一点修改就可以使用了

1 把trk, name, trkseg这三个标签干掉

2 把trkpt整体替换成wpt

这样这个文件就可以使用了

大概是这个样子

<?xml version=“1.0”?>

<gpx creator=“GPS Visualizer http://www.gpsvisualizer.com/ version=“1.0” xmlns=http://www.topografix.com/GPX/1/0 xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation=http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd>

    <wpt lat=“39.984440439” lon=“116.309970222”></wpt>

    <wpt lat=“39.986689168” lon=“116.353745154”></wpt>

</gpx>


然后把这个gpx文件添加到Xcode工程中,在target中钩掉所有target, 因为这个文件不需要打包到最终的产品中.

这时就可以模拟位置了,点工具栏中的工程名,选’Edit Schema’在Options标签中选 ‘Allow Location Simulation’ 在Default Location中先刚才加上的的gpx文件名就可以,也可以选None,在程序调试期间选底部的定位图标来选gpx文件。

写了一个简陋的Demo https://github.com/stcui/GPXDemo

关于自定义 UITableViewCell

自定义UITableViewCell的方法有很多 发现一些人都会遇到自己定义的cell里面图片错乱的问题 这个问题往往是因为没有实现prepareForReuse这个方法导致的.

UITableViewCell在向下滚动时复用, 得用的cell就是滑出去的那些, 而滑出去的cell里显示的信息就在这里出现了 解决的方法就是在UITableViewCell的子类里实现perpareForReuse方法, 把内容清空掉

比如

- (void)prepareForReuse
{
    [super prepareForReuse];
    self.myImageView.image = nil;
}