从上海创业做游戏、IOS产品《心城》到离开来到北京已经有半个多月了。

两年多后再次回到北京,刚来的那几天,眼睛酸疼的想流眼泪,污浊的空气让我嗓子干燥的难受。北京的空气更差了,要不是考虑女朋友,也许我还是更愿意待在上海发展或者回到西安。

既来之,则安之吧。

跑了一周多快两周的工作,也收到了一些offer,考虑了下我选择了嘀嗒拼车。这家公司的现状可能会更加的对我胃口。

入职后,老大给了我几天的时间阅读了源代码,同事都很nice,抽了时间,对项目的目录,架构做了一些简介。之后,结合真机流程测试,大概对整体和核心的部分有了一些认识。

从干程序以来,游戏,应用,因为公司也不大,一直都是一个人在单打独斗负责前端。这次头次和同事一起开发,心里不禁有一丝喜悦和兴奋。

多人协作开发会涉及到版本的管理,公司采用了svn来进行版本控制。用惯了git,对svn不太熟悉,现阶段遵循公司的方式,我想如果将来可能的话,建议公司切换到git。

项目由不同的同事开发,其中的代码风格也不尽相同。阅读项目时,也发现了一些味道比较坏的代码。我想将来有时间的话,应该和同事将部分部分代码进行重构,最重要的是将不合理的架构和类进行修改,提高阅读性,否则后续开发我想会越来越乱。关于对代码的重构,后面有时间的话很想和大家做个探讨。

随笔到这先。

票不好买,我买了今天回家的票。下午5点多的汽车,明天到郑州。

坐在电脑前,回想从我们两人(另外一人,一哥是负责产品),从开始做心城,磕磕碰碰,一路走到现在,对自己坚持的毅力也感到吃惊。

整个程序我一个人写,从一个从游戏开发转应用开发的“新手”而言,刚开始对应用开发,完全摸不到节奏。节奏太重要了。

怎么去寻找节奏?感谢R网站,跟着教程,我试着去理清ios应用开发的一些知识脉络。时间不等人。一边学习,一边开发,遇到问题,不断调试,查阅苹果文档,google和stackoverflow已经成了我解决问题离不开的渠道。知识面的不足,可以通过学习来弥补。但经验的不足,些许可以从旁人中吸取,但自己摔过的跟头,才会永远透彻的记得。审核时犯的错,容不得犯第二次。

迭代了4个版本,也是近半年的开发成果,上周六通过了1.4.0版本,但存在一个bug,用户的数据没能正确显示出来,这是致命的。我不得不说,这次的跟头跌的太大了,完全是我的疏忽,提交前没有对升级进行严格测试。

阅读全文 »

问题描述

在心城的开发过程中,在处理音频部分时,有这么段代码

1
NSError* error;
self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:[self getTempAudioPath] settings:settingDic error:&error];;
//...
if ([self.audioRecorder prepareToRecord]) {
	[self.audioRecorder record];
}

在模拟器上崩溃,始终停在了prepareToRecord这一行,但真机测试没问题。怀疑是模拟器的bug。
Stackoverflow问题讨论

解决办法(不是办法的办法)

在测试音频录制功能时,先暂时禁用掉Xcode的全局断点功能。之后,恢复正常开启全局断点。(默认快捷键:Command+Y)

原文链接:https://github.com/dabing1022/Blog/issues/5

在接触到开源项目 Masonry 后,里面的布局约束的链式写法让我颇感兴趣,就像下面这样

1
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

其他语言比如 Lua, 实现链式语法很容易。但在 Objective-C 中,如何实现链式语法呢?

阅读全文 »

《心城》1.3.0版本目前可以在AppStore下载

迭代的这几个版本,还没有正式迈入到我们设想的功能点。

在下面的1.4.0版本中,我们俩决定按照之前设想的,加入标签系统,这套系统会成为组织数据的一个核心,今天上午我自己出了个概念图,不过当务之急要将功能、流程走通。

问题描述

我们在用 Cocoapods 做第三方开源库管理的时候,有时候发现

1
$ pod search XXX

版本低于github上仓库的最新release版本 (注:XXX为仓库名称)

解决方法–>升级Cocoapods版本

查看当前系统Cocoapods版本命令:pod --version

升级方法

1
2
3
4
5
$ sudo gem update --system
$ gem sources --remove https://rubygems.org/
$ gem sources -a http://ruby.taobao.org/
$ sudo gem install cocoapods
$ pod setup

其中

1
2
$ gem sources --remove https://rubygems.org/ 
$ gem sources -a http://ruby.taobao.org/

这两句话可以省略,但我们在天朝,还是加上的好。国内网络原因(你懂的),如果使用原来的https://rubygems.org/,那么在sudo gem install cocoapods的时候,存放在 Amazon S3 上面的资源文件间歇性连接失败。

升级结束后再次pod --version,会发现 Cocoapods 版本号高于之前的版本,升级成功了。

再次

1
$ pod search XXX

OK,github仓库的最新版已经有了。

问题描述

项目中使用到了从字符串创建选择器,编译时发现警告:”performSelector may cause a leak because its selector is unknown”(因为performSelector的选择器未知可能会引起泄漏),为什么在ARC模式下会出现这个警告?

经过搜索后,在Stackoverflow上发现了一个令人满意的答案。见http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

原因

在ARC模式下,运行时需要知道如何处理你正在调用的方法的返回值。这个返回值可以是任意值,如void,int,char,NSString,id等等。ARC通过头文件的函数定义来得到这些信息。所以平时我们用到的静态选择器就不会出现这个警告。因为在编译期间,这些信息都已经确定。

如:

1
...
[someController performSelector:@selector(someMethod)];
...
- (void)someMethod
{
  //bla bla...
}

而使用[someController performSelector: NSSelectorFromString(@"someMethod")];时ARC并不知道该方法的返回值是什么,以及该如何处理?该忽略?还是标记为ns_returns_retained还是ns_returns_autoreleased?

解决办法

1.使用函数指针方式

1
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

当有额外参数时,如

1
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = func(_controller, selector, someRect, someView);

2.使用宏忽略警告

1
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
   [someController performSelector: NSSelectorFromString(@"someMethod")]
#pragma clang diagnostic pop

通过使用#pragma clang diagnostic push/pop,你可以告诉Clang编译器仅仅为某一特定部分的代码来忽视特定警告。

如果需要忽视的警告有多处,可以定义一个宏

1
#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

在产生警告也就是performSelector的地方用使用该宏,如

1
SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

如果需要performSelector返回值的话,

1
id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

3.使用afterDelay

1
[self performSelector:aSelector withObject:nil afterDelay:0.0];

如果在接受范围内,允许在下一个runloop执行,可以这么做。xCode5没问题,但据反映,xCode6的话这个不能消除警告。

原文链接:https://github.com/dabing1022/Blog/issues/2

2014年11月08号,心城v1.1.0通过了苹果的审核。如果阅读过前面心城1.1.0今天提交审核了的朋友们会发现,时间俨然过了2周多。期间发生了什么事情?正常的Waiting For Review进入InReview大概1周差不多,这次怎么两周多了?

被拒了一次。

来看下被拒的原因吧,希望后面的朋友别和我一样。

1.问题描述

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
Reasons
2.23: Apps must follow the iOS Data Storage Guidelines or they will be rejected
3.3: Apps with names, descriptions, screenshots, or previews not relevant to the content and functionality of the App will be rejected
----- 2.23 -----

We found that your app does not follow the iOS Data Storage Guidelines, which is required per the App Store Review Guidelines.

In particular, we found that on launch and/or content download, your app stores 2.04MB. To check how much data your app is storing:

- Install and launch your app
- Go to Settings > iCloud > Storage & Backup > Manage Storage
- If necessary, tap "Show all apps"
- Check your app's storage

The iOS Data Storage Guidelines indicate that only content that the user creates using your app, e.g., documents, new files, edits, etc., should be backed up by iCloud.

Temporary files used by your app should only be stored in the /tmp directory; please remember to delete the files stored in this location when the user exits the app.

Data that can be recreated but must persist for proper functioning of your app - or because customers expect it to be available for offline use - should be marked with the "do not back up" attribute. For NSURL objects, add the NSURLIsExcludedFromBackupKey attribute to prevent the corresponding file from being backed up. For CFURLRef objects, use the corresponding kCFURLIsExcludedFromBackupKey attribute.

For more information, please see Technical Q&A 1719: How do I prevent files from being backed up to iCloud and iTunes?.

It is necessary to revise your app to meet the requirements of the iOS Data Storage Guidelines.

----- 3.3 -----

In addition, your marketing screenshots do not sufficiently reflect the app in use, which does not give the user an accurate understanding of what the app does, as required by the App Store Review Guidelines.

We’ve attached the relevant screenshot(s) for your reference.

It would be appropriate to revise your screenshots to demonstrate the app functionality in use.

If your iTunes Connect Application State is Rejected, a new binary will be required. Make the desired metadata changes when you upload the new binary.

For discrete code-level questions, you may wish to consult with Apple Developer Technical Support. When the DTS engineer follows up with you, please be ready to provide:

- complete details of your rejection issue(s)
- screenshots
- steps to reproduce the issue(s)
- symbolicated crash logs - if your issue results in a crash log

一共两个原因。
分别对应苹果审核规则的2.23和3.3规则。

2.先说3.3 元数据问题

截图不够丰富,表达不了应用的功能,增加了3张图片解决问题。

3.再谈2.23 数据存储问题

心城将用户拍照以及头像设置的图片以及用户的录音文件都保存在了沙箱Documents文件夹,MagicalRecord用的数据库文件默认存放在了Library/Application Support目录下。

我举个例子来说明:

Documents下的文件是会被 iCloud 自动备份的,苹果的测试人员发现,他们拍了一些照片,录制一些音频,然后退出应用,同步iCloud发现数据有2.04M,很明显,照片和音频数据以及数据库文件(存放在Library下的ApplicationSupport下,同样会被iCloud自动同步)是造成这2.04M的罪魁祸首,所以被拒。

究其原因是我存储文件的位置不对或者说是规则不对造成的。为了避免被 iCloud 同步,需要对这些文件进行“不备份标记”。将文件夹标记为不备份,那么其子文件和子文件夹都不会被备份。这里还不敢对 Documents 标记不备份,那样“太过分了”,毕竟还有其他数据和文稿需要备份。对Documents下的不需要的文件夹进行标记即可。

和一个朋友交流中,他提到了他以前犯过的一个错误,他的应用在访问网络时,把一些图片存放在了 Documents 文件夹里面了,也是导致被拒,这些图片应该视情况放在tmp或者cache里面。

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
43
44
//沙盒Documents目录路径
#define DOC_PATH ([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0])
//沙盒Cache目录路径
#define CACHE_PATH ([NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0])
//沙盒Library目录路径
#define LIB_PATH ([NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0])
//沙盒Temp目录路径
#define TEMP_PATH (NSTemporaryDirectory())
// app data directory name
#define APP_DATA_PHOTOS_DIR_NAME @"photos"
#define APP_DATA_AUDIOS_DIR_NAME @"audios"
#define APP_DATA_AVATARS_DIR_NAME @"avatars"

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//.....
[self addSkipBackup];
//.....
}

-(void)addSkipBackup
{
NSString* photoDirPath = [DOC_PATH stringByAppendingPathComponent:APP_DATA_PHOTOS_DIR_NAME];
NSString* audioDirPath = [DOC_PATH stringByAppendingPathComponent:APP_DATA_AUDIOS_DIR_NAME];
NSString* avatarDirPath = [DOC_PATH stringByAppendingPathComponent:APP_DATA_AVATARS_DIR_NAME];

[self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:photoDirPath]];
[self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:audioDirPath]];
[self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:avatarDirPath]];
[self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:[NSPersistentStore MR_applicationStorageDirectory]]];
}

-(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
}
return success;
}

修改之后,重新提交,漫长的WaitingForReview又重新开始。今天进入了InReview后顺利通过上架。

4.杂说

在等待的期间,v1.2.0也在另外的分支开发着。今天1.1.0上架后,v1.2.0 也做了提交。

v1.2.0的改动
1.崭新的UI界面;
2.增加密码锁保护隐私;
3.消除了5连拍时存在的闪退隐患。

襁褓中的心城,并不完美也并不强大,虽然收到了负面的反馈,但不妨碍我把TA坚持做下去做好的决心。

行走在黑暗中,也许某天,会看到光明。