Widget的简单开发

今天简单对原有的工程添加了 Widget支持,这里只简单记录下中间遇到的问题。

Widget支持8.0之后的SDK API调用,
Widget只能给现有的工程进行扩展设计,目前还无法进行单独的开发。
Widget由于不具备UI层的一些特性,故一些UI的API不支持,比如以下代码不支持

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
  Widget仅仅是对原有工程的一个扩展,具有自己独有的场景才可使用,并非滥用

Widget使用的时候如果需要调用基本工程中的数据,根据苹果的安全原则,必须进行跨程序协作才可调用,需要开启工程中Capabilties->App Groups
当然打开基本工程需要使用appextension://123进行调用

PS:最终贴一副最终的效果图,由于特有的场景和背景,这里可能需要UI进行设计

屏幕快照 2015-06-25 22.01.36

06/25/2015 20:52 下午 posted in  apple

CocoaPods版本升级

命令行执行

$ pod install
[!] The 'master' repo requires CocoaPods 0.37.2 - 

失败,提示需要CocoaPods 0.37.2版本

查看CocoaPods版本

$ pod --version
0.36.3

命令行更新(安装)步骤

$ sudo gem update --system // 先更新gem,国内需要切换源
$ gem sources --remove https://rubygems.org/
$ gem sources -a http://ruby.taobao.org/
$ gem sources -l
\*\*\* CURRENT SOURCES \*\*\*
http://ruby.taobao.org/
$ sudo gem install cocoapods // 安装cocoapods
$ pod setup

和安装过程是一样的,再次查看版本

$ pod --version
0.37.2

更新结束

06/17/2015 09:12 上午 posted in  Cocoapods

Markdown 语法和 MWeb 写作使用说明

转载

张三->李四: 嘿,小四儿, 写博客了没?
Note right of 李四: 李四愣了一下,说:
李四-->张三: 忙得吐血,哪有时间写。
st=>start: 开始
e=>end: 结束
op=>operation: 我的操作
cond=>condition: 确认?

st->op->cond
cond(yes)->e
cond(no)->op
st=>start: Start|past:>http://www.google.com[blank]
e=>end: End:>http://www.google.com
op1=>operation: My Operation|past
op2=>operation: Stuff|current
sub1=>subroutine: My Subroutine|invalid
cond=>condition: Yes 
or No?|approved:>http://www.baidu.com
c2=>condition: Good idea|rejected
io=>inputoutput: catch something...|request

st->op1(right)->cond
cond(yes, right)->c2
cond(no)->sub1(left)->op1
c2(yes)->io->e
c2(no)->op2->e
06/15/2015 09:32 上午 posted in  Markdown&MWeb

NSURLProtocol和NSRunLoop的那些坑

最近用AFNetworking替换掉了工程里的ASIHttpRequest,结果陆续碰到很多问题:

·如何统一地添加全局的HTTP头(不仅仅是UA而已)

·如何优雅地进行流量统计

·对特定的地址进行CDN加速(URL到IP的替换)

·怎么实现HTTP的同步请求

前三个需求对于ASIHttpReqeust来说都不是问题,只需要在几个统一的点进行修改即可。而使用AFNetworking后就没有那么容易了:一方面AFNetworking中生成NSURLRequest的点比较多,并没有一个统一的路径。其次工程中会有部分直接使用NSURLConnecion的场景,无法统一。经cyzju提醒发现了NSURLProtocol这个大杀器,可惜对应的文档过于简略,唯一比较详细的介绍就只有RW的这篇教程而已,掉了很多坑,值得记上一笔。

NSURLProtocol

##概念

NSURLProtocol也是苹果众多黑魔法中的一种,使用它可以轻松地重定义整个URL Loading System。当你注册自定义NSURLProtocol后,就有机会对所有的请求进行统一的处理,基于这一点它可以让你

·自定义请求和响应

·提供自定义的全局缓存支持

·重定向网络请求

·提供HTTP Mocking (方便前期测试)

·其他一些全局的网络请求修改需求

使用方法

继承NSURLPorotocl,并注册你的NSURLProtocol

[NSURLProtocol registerClass:[YXURLProtocol class]];

当NSURLConnection准备发起请求时,它会遍历所有已注册的NSURLProtocol,询问它们能否处理当前请求。所以你需要尽早注册这个Protocol。

实现NSURLProtocol的相关方法

当遍历到我们自定义的NSURLProtocol时,系统先会调用canInitWithRequest:这个方法。顾名思义,这是整个流程的入口,只有这个方法返回YES我们才能够继续后续的处理。我们可以在这个方法的实现里面进行请求的过滤,筛选出需要进行处理的请求。

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    if ([NSURLProtocol propertyForKey:YXURLProtocolHandled inRequest:request])
    {
        return NO;
    }
    NSString *scheme = [[request URL] scheme];
    NSDictionary *dict = [request allHTTPHeaderFields];
    return [dict objectForKey:@"custom_header"] == nil &&
    ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
     [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame);
}

当筛选出需要处理的请求后,就可以进行后续的处理,需要至少实现如下4个方法

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a
                       toRequest:(NSURLRequest *)b
{
    return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading
{
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    [YXURLProtocol applyCustomHeaders:mutableReqeust];
    [NSURLProtocol setProperty:@(YES)
                        forKey:YXURLProtocolHandled
                     inRequest:mutableReqeust];
     
    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust
                                                    delegate:self];
}
- (void)stopLoading
{
    [self.connection cancel];
    self.connection = nil;
}

·canonicalRequestForRequest: 返回规范化后的request,一般就只是返回当前request即可。

·requestIsCacheEquivalent:toRequest: 用于判断你的自定义reqeust是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。

·startLoading和stopLoading 实现请求和取消流程。

实现NSURLConnectionDelegate和NSURLConnectionDataDelegate

因为在第二步中我们接管了整个请求过程,所以需要实现相应的协议并使用NSURLProtocolClient将消息回传给URL Loading System。在我们的场景中推荐实现所有协议。

- (void)connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)error
{
    [self.client URLProtocol:self
            didFailWithError:error];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
    if (response != nil) 
    {
        [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
    }
    return request;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
    return YES;
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    [self.client URLProtocol:self
didReceiveAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection
didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
{
    [self.client URLProtocol:self
didCancelAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
    [self.client URLProtocol:self
          didReceiveResponse:response
          cacheStoragePolicy:[[self request] cachePolicy]];
}
- (void)connection:(NSURLConnection *)connection
    didReceiveData:(NSData *)data
{
    [self.client URLProtocol:self
                 didLoadData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    return cachedResponse;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.client URLProtocolDidFinishLoading:self];
}

在每个delgate的实现中我都刨去了工程中的特定实现(流量统计),只保留了需要实现的最小Protocol集合。

NSURLProtocol那些坑

从上面的介绍来看,NSURLProtocol还是比较简单,但是实际使用的过程中却容易掉进各种坑,一方面是文档不够详尽,另一方面也是对于苹果这套URL Loading Sytem并不熟悉,不能将整个调用过程有机地统一。

·坑1:企图在canonicalRequestForRequest:进行request的自定义操作,导致各种递归调用导致连接超时。这个API的表述其实很暧昧:

It is up to each concrete protocol implementation to define what “canonical” means. A protocol should guarantee that the same input request always yields the same canonical form.

所谓的canonical form到底是什么呢?而围观了包括NSEtcHosts和RNCachingURLProtocol在内的实现,它们都是直接返回当前request。在这个方法内进行request的修改非常容易导致递归调用(即使通过setProperty:forKey:inRequest:对请求打了标记)

·坑2:没有实现足够的回调方法导致各种奇葩问题。如connection:willSendRequest:redirectResponse: 内如果没有通过[self client]回传消息,那么需要重定向的网页就会出现问题:host不对或者造成跨域调用导致资源无法加载。

同步AFNetworking请求

虽然Mattt各种鄙视同步做网络请求,但是我们不可否认某些场景下使用同步调用会带来不少便利。一种比较简单的实现是使用信号量做同步:

@implementation AFHTTPRequestOperation (YX)
- (void)yxStartSynchronous
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        dispatch_semaphore_signal(semaphore);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        dispatch_semaphore_signal(semaphore);
    }];
    [self start];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
@end

但是这样带来的问题是在UI线程调用同步请求就会导致线程堵死崩溃(好吧,就不应该允许UI线程上这么做)。一种改进的方法是使用NSRunLoop

即:

while (_shouldBlock)
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:[NSDate distantFuture]];
   }

但是这种写法是有大坑的:如果当前NSRunLoop并没有任何NSTimer或Input Source,runMode:beforeDate:方法将立刻返回NO,于是造成死循环,占用大量CPU,进而导致NSURLConnection请求超时。 规避的方法是往RunLoop中添加NSTimer或者空NSPort使得NSRunLoop挂起而不占用CPU。(ASIHttpRequest就是在当前RunLoop中添加了0.25秒触发一次的刷新Timer)

If no input sources or timers are attached to the run loop, this method exits immediately and returns NO; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop does not guarantee that the run loop will exit immediately. OS X may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

06/13/2015 08:21 上午 posted in  apple

NSTimer的使用

##1. NSRunLoopCommonModes和Timer

当使用NSTimer的scheduledTimerWithTimeInterval方法时。事实上此时Timer会被加入到当前线程的Run Loop中,且模式是默认的NSDefaultRunLoopMode。而如果当前线程就是主线程,也就是UI线程时,某些UI事件,比如UIScrollView的拖动操作,会将Run Loop切换成NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不会执行。

所以为了设置一个不被UI干扰的Timer,我们需要手动创建一个Timer,然后使用NSRunLoop的addTimer:forMode:方法来把Timer按照指定模式加入到Run Loop中。这里使用的模式是:NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopModeNSEventTrackingRunLoopMode的结合。(参考Apple文档

参考代码:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSLog(@"主线程 %@", [NSThread currentThread]);
    //创建Timer
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
    //使用NSRunLoopCommonModes模式,把timer加入到当前Run Loop中。
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

//timer的回调方法
- (void)timer_callback
{
    NSLog(@"Timer %@", [NSThread currentThread]);
}

输出:

主线程 <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}

##2. NSThread和Timer
上面讲的NSRunLoopCommonModesTimer中有一个问题,这个Timer本质上是在当前线程的Run Loop中循环执行的,因此Timer的回调方法不是在另一个线程的。那么怎样在真正的多线程环境下运行一个Timer呢?

可以先试试NSThread。同上,我们还是会把Timer加到Run Loop中,只不过这个是在另一个线程中,因此我们需要手动执行Run Loop(通过NSRunLoop的run方法),同时注意在新的线程执行中加入@autoreleasepool

完整代码如下:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSLog(@"主线程 %@", [NSThread currentThread]);
    
    //创建并执行新的线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
    [thread start];
}

- (void)newThread
{
    @autoreleasepool
    {
        //在当前Run Loop中添加timer,模式是默认的NSDefaultRunLoopMode
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
        //开始执行新线程的Run Loop
        [[NSRunLoop currentRunLoop] run];
    }
}

//timer的回调方法
- (void)timer_callback
{
    NSLog(@"Timer %@", [NSThread currentThread]);
}

输出:

主线程 <NSThread: 0x7118800>{name = (null), num = 1}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}

##3. GCD中的Timer

GCD中的Timer应该是最灵活的,而且是多线程的。GCD中的Timer是靠Dispatch Source来实现的。

因此先需要声明一个dispatch_source_t本地变量:

@interface ViewController ()
{
    dispatch_source_t _timer;
}

接着通过dispatch_source_create函数来创建一个专门的Dispatch Source,接着通过dispatch_source_set_timer函数来设置Timer的参数,注意这里的时间参数有些蛋疼。

开始时间的类型是dispatch_time_t,最好用dispatch_time或者dispatch_walltime函数来创建dispatch_time_t对象。如果需要Timer立即执行,可以传入dispatch_time(DISPATCH_TIME_NOW, 0)

internalleeway参数分别表示Timer的间隔时间和精度。类型都是uint64_t。间隔时间的单位竟然是纳秒。可以借助预定义的NSEC_PER_SEC宏,比如如果间隔时间是两秒的话,那interval参数就是:2 * NSEC_PER_SEC

leeway就是精度参数,代表系统可以延时的时间间隔,最高精度当然就传0。

然后通过dispatch_source_set_event_handler函数来设置Dispatch Source的事件回调,这里当然是使用Block了。

最后所有dispatch_source_t创建后默认都是暂停状态的,所以必须通过dispatch_resume函数来开始事件监听。这里就代表着开始Timer。

完整代码:

NSLog(@"主线程 %@", [NSThread currentThread]);
//间隔还是2秒
uint64_t interval = 2 * NSEC_PER_SEC;
//创建一个专门执行timer回调的GCD队列
dispatch_queue_t queue = dispatch_queue_create("my queue", 0);
//创建Timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函数设置timer参数
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//设置回调
dispatch_source_set_event_handler(_timer, ^()
{
    NSLog(@"Timer %@", [NSThread currentThread]);
});
//dispatch_source默认是Suspended状态,通过dispatch_resume函数开始它
dispatch_resume(_timer);

输出:

主线程 <NSThread: 0x711fab0>{name = (null), num = 1}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
Timer <NSThread: 0x713a380>{name = (null), num = 3}

转载:iOS: NSTimer使用小记

06/12/2015 19:29 下午 posted in  apple

iOS 常用数学函数


   1、 三角函数 
  double sin (double);正弦 
  double cos (double);余弦 
  double tan (double);正切 
  2 、反三角函数 
  double asin (double); 结果介于[-PI/2, PI/2] 
  double acos (double); 结果介于[0, PI] 
  double atan (double); 反正切(主值), 结果介于[-PI/2, PI/2] 
  double atan2 (double, double); 反正切(整圆值), 结果介于[-PI, PI] 
  3 、双曲三角函数 
  double sinh (double); 
  double cosh (double); 
  double tanh (double); 
  4 、指数与对数 
  double exp (double);求取自然数e的幂 
  double sqrt (double);开平方 
  double log (double); 以e为底的对数 
  double log10 (double);以10为底的对数 
  double pow(double x, double y);计算以x为底数的y次幂 
  float powf(float x, float y); 功能与pow一致,只是输入与输出皆为浮点数 
  5 、取整 
  double ceil (double); 取上整 
  double floor (double); 取下整 
  6 、绝对值 
  double fabs (double);求绝对值 
  double cabs(struct complex znum) ;求复数的绝对值 
  7 、标准化浮点数 
  double frexp (double f, int *p); 标准化浮点数, f = x * 2^p, 已知f求x, p ( x介于[0.5, 1] ) 
  double ldexp (double x, int p); 与frexp相反, 已知x, p求f 
  8 、取整与取余 
  double modf (double, double*); 将参数的整数部分通过指针回传, 返回小数部分 
  double fmod (double, double); 返回两参数相除的余数 
  9 、其他 
  double hypot(double x, double y);已知直角三角形两个直角边长度,求斜边长度 
  double ldexp(double x, int exponent);计算x*(2的exponent次幂) 
  double poly(double x, int degree, double coeffs [] );计算多项式 
  nt matherr(struct exception *e);数学错误计算处理程序

06/12/2015 19:25 下午 posted in  apple

我为什么讨厌产品经理

转载:我为什么讨厌产品经理
半年前的文章《我为什么讨厌程序员》最近老被拎出来,看着一些评论,开始有些脾气,然后我完整得乐坏了……

先补这一篇,我接下打算写一篇文章,来说明自己为什么智商这么低,这么二……

其实也是奇怪,多数PM看到这样的文章会乐呵呵的看完;而多数的程序员看到另一篇文章,反应却不是这样的?

##理由1,其实想做经理,而不是产品

有人在称赞世界上存在过的伟大的产品经理,同时又有很多人说,“人人都是产品经理”。

想做经理么?产品经理,可真是捷径。不管什么专业出生,不管聪明或者愚蠢,不管年轻或者年老,都可以的事情。

如果2012年12月21号,我可以祈祷某一类人消失的话,毫无疑问,我会选择应届生产品经理。别跟我探讨新牛不下地何以学耕的问题,这个跟本文无关,既然你有勇气认为自己是产品经理,就别用新人为自己找借口。

内心屈辱地承认自己的失败,才会奋起直追。

曾经看到一篇某PM对自己工作“平实”的介绍,大家觉得说得非常厚道。但全文的中心思想不过是按照BOSS的要求做事,以及机械式的做事,并且,没有赚多少钱。

丝毫看不出那种产品人应该具备的价值观、观察世界的能力、唾弃以及热爱;做平庸的产品,然后自认问心无愧,不再有所追求。即使自认还有理想,也不过是一个行将就木的人类,逐步在往更高级别的管理者行进而已。当然,他仍然可能失败。

我曾经面试过一个奇葩产品经理,他居然在考各种管理类的证书…… 奇葩地是在于,他以这样的身份工作了不少年。呃,别吐槽工资低,虽难算高薪,但那次招聘,我们的标的是5位数,接近月入一平。

##理由2,浪费纸张、键盘

虽然没有多少程序员会认真地去阅读产品文档、技术实现文档;但是,这些文档并不是最短命的。

最短命的是BRD、PRD……

求你们了,为了世界和平,省点电吧!

##理由3,满嘴跑火车

有一种PM,动动就说,你的成长空间如何如何,为了你的成长如何如何。看似苦口婆心,但他真在乎么,并且他真的有能力来照顾我的成长空间么?假道学而已。成长靠自己,成功靠勤奋,成事看天,这是常识呀。

还有一种PM,有种天然的绝杀噎死人的逻辑。

问:你觉得我们团队的核心优势是什么?

PM答:我们的优势是积累出来的……

PM还说: 给大家分享一个行业数据,……,这个数据说明,我们现在做的事情,做的市场很大呀!!

路人说: 你的数据哪来的?

PM顿而道: 内部数据,呵呵。

##理由4,强奸数据

数据是最理性。

但处理数据的过程中,有太多水分可以兑进去了。

有时产品上线了一段时间,PM给所有分享运营数据。散会后,我们经常会有这样感觉,我的智商受到了挑战。成交率低,就说下单数还可以;下单数也不灵,就取某冤大头的大笔消费,平均客单价不错哦;跳出率高的时候,死不承认过来的垃圾流量有问题,装一副单纯样,还要继续研究。

##理由5,孬种

全宇宙的SB都知道的不靠谱的某种特性,当然SB PM也知道,只要老板说要这么做;他也不反抗。然后,然后,居然扯出像模像样的原型以及各种文档。

在实施过程中,遇到阻碍的时候,他通常会说,老板的意思是这样,我也没有办法……

当然,孬种PM中还有种类型是人畜无害的,唯唯诺诺,但脾气好。所以,你真的会心疼他……

还有一件事情我无法理解的,为什么有些产品经理竟然以下跪为荣?!解释产品设计的过程,需要跪下么?需要摆这种姿态么?你是如何的弱,才需要做这样的事情?!

Q刚毕业过来的时候,也有这个毛病,觉得这样会亲和很多。我不止一次告诫他,别跪下,蹲下都不可以;话太多,说累了,拉把椅子,没椅子你直接盘坐在地毯上也好啊!

亲,跪下的产品经理,差不多都是装出来的吧!

##理由6,不懂技术还有理了?

本条吐槽无力。

刚入门的时候,人人抱着一本人人都是产品经理,这太让人厌恶了;如果人人抱着xx语言入门,才让人喜爱。如果没点聪明才智,PM能做好?如果有点聪明才智,各种语言的入门时间不过个把月甚至个把礼拜而已。我说的这个,并不是与程序员更好的沟通的前提,而是,对自己事业的尊重。

还有种纯程序员转岗的PM,呃,放过我吧,Project Manager?

##理由7,装逼扮用户

跟产品经理沟通的时候,我最多听到的句子、关键字是 用户不会这么觉得如果我是用户。

如果你是一个男人,你去设计女士内衣。我不会肤浅地认为你不懂女人。你作为一名设计师,你即是产品最大的核心。

但是我非常用心的去观察动不动就以用户为名的PM,根本没有办法把他们和产品以及用户联系在一起。他们更多的时候讨论的是这个按钮是这么颜色,应该放在哪里,文案应该怎么写。讨论这些错了么?当然没有错。错就错在,把这些问题当做了核心问题,并且即使这些交互级的问题,也经常做出令人啼笑皆非的决定来。

不知还有多少人顶着所谓的“产品经理综合症”,常常对生活中各种物品提出自己关于如何重新设计的看法,有茶杯、咖啡机、电梯、电灯、座位怎么安排 .etc, 各种你想的到想不到的问题。讨论这些错了么?当然没有错。错就错在,讨论了这么多年,他们还在讨论这些,公转自转,孜孜不倦!

##理由8,好运的混蛋

知道国内几个大平台上出产的PM,但真人,谈吐所体现出的产品人气息,常常是瞬间就冒出一个疑问,是他们成就了那些平台,还是那些平台成就了他们? 我的答案毫无疑问会选择后者。

但他们身上所体现出来的能力,是另一种非产品人的能力。他们中的多数,不得不佩服。想来想来,只有这个名词好运的混蛋能表达这种爱恨交织的感情了。

但把他们当做纯种的PM来看,呃,那这又是一条足以让人讨厌的理由了。

##最后的绝杀

其实,多数PM的工资,不如多数的程序员高。PM,是一个多么奇葩的岗位。

而且,我们还有一个必杀技,可以直接秒杀PM。你遇到的几乎任何事情,只要咆哮以下这段即可。

这不都是你产品经理应该做的事情么?!

06/11/2015 20:08 下午 posted in  杂七杂八

我为什么讨厌程序员

PM与工程师的交恶,由来已久。不仅如此,视觉与工程师的交恶也由来已久。
还有些哥们之所以能游刃有余,是因为在产品实现的过程中,已经习惯了打人情牌。

很多产品或工程师朋友可能会交流这些沟通过程中,如何很好的搞定SB般的对方?

这些冲突,我认为没有讨论的必要。等活在这些冲突里的朋友们,level up后,就会脱离这样的苦海,自当能理解“没有必要”的意思。

今天,我想说的是,我为什么讨厌程序员?!

我是谁?我是Producer。

真的很讨厌么?其实不然,我非常爱工程师。按我的经验来看,多数工程师比多数PM,其实更理解用户。

不仅是如此,我的技术涉猎也比较广,django、python、mootools、html/css这些写得很溜,在不知所以的情况下,node.js、Java、PHP、jython、AS等,也照用不误。

技术是什么?程序是什么?这跟程序语言没有本质的关系,极端的说,它就是+1和-1而已。

虽然常会误解为一个纯coder,但自己程序员这种角色,坦诚说,厌恶的很。

按照自己几年前的性格,逮到垃圾程序员,能直接破口大骂。后来,发现人都好面子,不能骂了。所以,憋了很久的气,今天要倒一倒。

##理由1:装逼

他们经常会用一些技术上的关键词来说话,如果关键词的含金量很高,咱听不懂,那没有办法,术业有专攻。但一旦你理解那些所谓的术语有多么简单以及自然的时候,你心里最想说的恐怕是骂娘了。

这样的程序员,比郭小四不相上下。

##理由2:傻逼

我需要你来设计产品的实现,换句话说,就是告诉我程序打算怎么写,数据结构是什么设计的,数据流动的环节是打算如何处理的。

你会给一个方案。基本上会是最笨的办法。然后,哥还得夸你,嗯,做的不错,有进步;不夸的话,你会觉得自己的价值没有得到尊重。

用谢耳朵的话说,你在你的领域做的非常出色,但你做的事情没有任何意义。

大白话:你是一名出色的傻逼。

##理由3:冷忽悠

程序员都很木讷?都不会忽悠?

知道什么叫冷暴力忽悠么?!

我们当前的结构不支持这样的实现的⋯⋯

这个技术上是没有办法实现的⋯⋯

但按照我的经验来看,对于你无法佩服的程序员,别信他们这样的鬼话,他们不仅仅会忽悠你,他们同时也在忽悠自己,因为他们自己真是这么想的。

碰到你非常佩服的程序员,请相信我,再逼一逼他,一起探讨各种hack的方法,有些所谓的“不可能”就迎刃而解了。

真遇到了不可能,那就让它去吧。有一天,灵感会光临的。

理由4:看起来很忙,出BUG都是有原因的

会存在没有原因的bug么?!!

不过,呃,这个bug的原因不是我造成的⋯⋯

靠!

##理由5:缺乏想象力,不思进取

对程序员来说,最大的侮辱,是重构他的代码,并且效率提升了100、10000倍。

但多数程序员不会碰到这样的事情,所以还有个办法,你直接骂他在写垃圾代码就好了。

但这算是侮辱么?!

真正的coder最关心的是对方重构的方式和思维的哲学,是不是质变性的。如果有人只为了骂而骂,为了重构而重构,coder倒是要开心了,又来个傻逼。

我认为现在这个行业的从业人员实在太多,弱者也实在太多,为了照顾弱者的脆弱心灵,容忍他们贫乏的想象力,容忍他们的不思进取,同时还谎称他们人不错,很上进;才是让我们的互联网技术力量无法跟老美他们匹敌的原罪。

##理由6:不知廉耻

写垃圾代码,做垃圾的解决方式,然后自鸣得意去分享。

被斥责的时候,会说,你不尊重人,退一万步讲,我就算写垃圾代码,那也是我能力所限呀。

我最厌恶这种固步自封的家伙,简直就是不知廉耻的流氓。

Excerpted from 我为什么讨厌程序员

06/08/2015 15:54 下午 posted in  apple

ios数据持久化存储&文件系统编程

##包系统
就是app程序本身,其实是个zip包,资源/可执行文件 都在里面

+ (NSBundle *)mainBundle 得到程序本身的bundle类. NSBundle提供了很多path相关函数
运行期只读,不可写
没有必要建立子文件夹,苹果不推荐在包中建立子文件夹
+ (UIImage *)imageNamed:(NSString *)name 可以直接加载包中的图片文件
包中带有的数据不能太多,不然不利于用户安装
NSBundle pathForResource:ofType: 使用这个函数查找包存放的文件
Sandbox 系统
每个app安装之后都存放在一个单独的目录当中,这个目录叫做沙盒,目录结构如下

  • Documents
  • Library/Caches
  • Library/Preferences
  • tmp

##程序Bundle

  • Documents 会在用户通过itunes备份的时候被自动备份,其他目录随意用

  • NSHomeDirectory() 得到app的沙盒目录

  • NSTemporaryDirectory() 得到 $(沙盒目录)/tmp

  • NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) 得到 $(沙盒目录)/Documents (函数返回的是数组)

  • NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES) 得到 $(沙盒目录)/Library
    NSString 定义了一堆 关于文件路径的 join,split的函数,路径相关函数主要定义在:NSPathUtilities.h 文件当中

##NSUserDefaults 系统
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
文件存放在 $(沙盒目录)/Library/Preferences/{Bundle Identifier}.plist
ios提供了一个工厂函数,没有容量限制,最好不要在里面存放业务数据, 这会破坏ios对此的语义设定,这里面应该存放程序设置相关信息
##Keychain 系统
专注于存放密码或者证书
数据是加密存储的
keychain里保存的信息不会因App被删除而丢失,在用户重新安装App后依然有效
##Cache系统
真的是cache,存储会有过期时间

NSURLCache,以url为单元,可以存在硬盘上
NSCache,只放在内存当中,和NSMutableDictionary区别不大,有意义的是:ios可以在程序运行期自行释放NSCache
##文件格式
二进制:NSData
plist: 最常见而且简单
对象序列化:NSKeyedArchiver
sqlite: 文件数据库,Core Data/libsqlite3/FMDB
plain text: 罕见,用Standard C Library操作,fopen/fwrite/fclose

06/06/2015 16:00 下午 posted in  apple

使用CocoaPods进行Xcode的项目依赖管理

我们来看一个Podfile文件事例

platform :ios, '5.0'
inhibit_all_warnings!

pod 'SDWebImage','~>3.2'
pod 'JASidePanels','~>1.3.1'
pod 'BlocksKit','~>1.8'
pod 'TTTAttributedLabel','~>1.7'
pod 'MBProgressHUD','~>0.6'
pod 'RTLabel','~>1.0'
pod 'KNSemiModalViewController','~>0.3'
pod 'SFHFKeychainUtils','~>0.0.1'
pod 'MBMvc',:git => 'git@github.com:alibaba/MBMvc.git'
pod 'RSA',:path => 'libs/Sources/RSA'

可以看到依赖的库不单单可以依赖官方库,还可以直接依赖某个git上的库(git=>标示)或者本地的库(path=>标示) 不过自己依赖的库需要自己写podspec文件,这个下节会详述

如上的Podfile有一个比较坑爹的事情就是 你这样写的话,只有对项目中的第一个target的起效,如果你的项目中有多个target的话怎么把依赖关系直接应用到每个target上,如下在

target :XXX_91 do
    pod 'Resources',:path => 'Resources'
end

target :XXX_Weiphone do
    pod 'Resources',:path => 'Resources'
end

target :XXX_Release do
    pod 'Resources',:path => 'Resources'
end

target :XXX_PreRelease do
    pod 'Resources',:path => 'Resources'
end

为每个target都加上一个pod依赖,那么这个target会被Pod整体的加上依赖和配置,因为依赖是会被继承的,除非特别指定:exclusive => true

详情见 http://docs.cocoapods.org/podfile.html#target

当你写好了Podfile,就可以直接执行pod install,这个时候pod就是为您建立起一个Pods项目,并且简历好一个workspace来包含Pods项目和你自己的项目,并在你的项目中直接依赖Pods项目产出的lib包

如果以后Podfile文件有变动的话,需要更新项目配置就不是执行pod install了 而是执行’pod update’,他会更新整个依赖和Pods项目..具体的依赖关系 可以直接cat Podfile.lock 来查看

##怎么编写自己的PodSpec

PodSpec是对一个Pod项目的描述,pod可以根据PodSpec文件来指导需要拉那些文件下来编译以及设置编译的参数

详细的信息可以看http://docs.cocoapods.org/specification.html

Pod::Spec.new do |s|
  s.name         = "MBMvc"
  s.version      = "1.0.0"
  s.summary      = "MBMvc is a Message Based MVC framework."
  s.homepage     = "https://github.com/alibaba/MBMvc"

  s.license      = { :type => 'GPL2' , :text => <<-LICENSE
             (C) 2007-2013 Alibaba Group Holding Limited
             This program is free software; you can redistribute it and/or modify
             it under the terms of the GNU General Public License version 2 as
             published by the Free Software Foundation.
 LICENSE
                    }

  s.author       = { "文通" => "wentong@taobao.com" }
  s.source       = { :git => "https://github.com/alibaba/MBMvc.git", :tag => "1.0.0" }


  s.platform     = :ios, '6.1'

  s.ios.deployment_target = '4.3'

  s.source_files = 'MBMvc/**/*.{h,m}'

  s.public_header_files = 'MBMvc/**/*.h'

  s.requires_arc = true

  s.prefix_header_contents = <<-EOS

#ifdef DEBUG
#define TBMB_DEBUG
#endif

EOS

end

如上可以看到一个很简单的描述文件..他执行了需要编译的源文件和头文件 以及是否支持ARC(这个很帅,不需要再自己去设置-fno-obj-arc了)..

但是很多时候我们依赖的不是源码而是framework甚至带有Resource,这里给出一个支持直接依赖framework和Resource的例子:

Pod::Spec.new do |s|
  s.name         = "Huoyan"
  s.version      = "1.0.0"
  s.summary      = "Huoyan"

  s.source_files = '**/*.h'
  s.preserve_paths = 'huoyan.framework','TBScanLib.framework'
  s.requires_arc = true
  s.frameworks = 'huoyan','TBScanLib'
  s.resource = "huoyan.bundle"
  s.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '"$(SRCROOT)/libs/Frameworks/Huoyan"' } 

end

通过preserve_paths 留下framework文件,然后通过xcconfig来设置framework扫描路径来依赖framework

而资源文件的拷贝直接用s.resource = "huoyan.bundle"的方式 依赖

##模块化
通过PodSpec的subspec 可以使一个项目能模块化输出功能 ,一个例子:

Pod::Spec.new do |s|
  s.name         = "iOS_Util"
  s.version      = "0.10.0"
  s.summary      = "Some iOS Util"

  s.license      = 'MIT'

  s.author       = { "文通" => "wentong@taobao.com" }


  s.platform     = :ios, '6.1'

  s.ios.deployment_target = '4.3'

  s.subspec 'Common' do |cos|
    cos.source_files = 'iOS_Util/Common/*.{h,m}'
    cos.public_header_files = 'iOS_Util/Common/*.h'
  end

  s.subspec 'Core' do |cs|
    cs.source_files = 'iOS_Util/Core/*.{h,m}'
    cs.public_header_files = 'iOS_Util/Core/*.h'
    cs.dependency 'libextobjc', '0.2.5'
  end

  s.subspec 'Json' do |js|
    js.source_files = 'iOS_Util/Json/*.{h,m}'
    js.public_header_files = 'iOS_Util/Json/*.h'
    js.dependency 'iOS_Util/Core'
  end

  s.subspec 'Bean' do |bs|
    bs.source_files = 'iOS_Util/Bean/*.{h,m}'
    bs.public_header_files = 'iOS_Util/Bean/*.h'
    bs.dependency 'iOS_Util/Core'
  end

  s.subspec 'DB' do |ds|
    ds.source_files = 'iOS_Util/DB/*.{h,m}'
    ds.public_header_files = 'iOS_Util/DB/*.h'
    ds.dependency 'FMDB/standard' ,'~> 2.1'
    ds.dependency 'iOS_Util/Common'
    ds.dependency 'iOS_Util/Core'
  end

  s.subspec 'WebP' do |ws|
    ws.source_files = 'iOS_Util/WebP/*.{h,m}'
    ws.public_header_files = 'iOS_Util/WebP/*.h'
    ws.dependency 'libwebp' ,'~> 0.3.0-rc7'
    ws.frameworks = 'CoreGraphics'
  end

  s.subspec 'Location' do |ls|
    ls.source_files = 'iOS_Util/Location/*.{h,m}'
    ls.public_header_files = 'iOS_Util/Location/*.h'
    ls.dependency 'iOS_Util/Common'
    ls.dependency 'iOS_Util/DB'
    ls.frameworks = 'CoreLocation' ,'MapKit'
    ls.resource = 'iOS_Util/Location/chinaDivision.sqlite'
  end

  s.subspec 'AMR' do |as|
    as.source_files = 'iOS_Util/AMR/**/*.{h,m,mm}'
    as.public_header_files = 'iOS_Util/AMR/**/*.h'
    as.preserve_paths = "iOS_Util/AMR/**"
    as.library   = 'opencore-amrnb','opencore-amrwb'
    as.xcconfig  = { 'LIBRARY_SEARCH_PATHS' => '"$(PODS_ROOT)/iOS_Util/iOS_Util/AMR/lib"' }
  end

  s.subspec 'Cache' do |cas|
    cas.source_files = 'iOS_Util/Cache/*.{h,m,mm}'
    cas.public_header_files = 'iOS_Util/Cache/*.h'
    cas.dependency 'iOS_Util/Common'
  end

  s.subspec 'Preference' do |ps|
    ps.source_files = 'iOS_Util/Preference/*.{h,m,mm}'
    ps.public_header_files = 'iOS_Util/Preference/*.h'
    ps.dependency 'iOS_Util/Json'
  end

  s.requires_arc = true



end

可以看到通过subspec可以区分出不同的模块,而且模块间也能依赖

而在其他项目要用到的时候在Podfile里面可以

pod 'iOS_Util/Json',:git => 'git@gitlab.alibaba-inc.com:tbw/ios_util.git'

这样的方式来直接依赖其中一个模块的代码,而其他模块的代码不会被打包进来

参考

cocoapods添加github上自制类库_2_添加subspec
使用CocoaPods进行Xcode的项目依赖管理

06/02/2015 23:08 下午 posted in  Cocoapods