
10年互联网老兵
背景 在PHP中使用Memcache或者Redis时,我们一般都会对Memcache和Redis封装一下,单独完成写一个Cache类,作为Memcache或者Redis的代理,且一般为单例模式。在业务代码中,使用Cache类时,操作的基本的示例代码如下 // cache 的 key $key = 'this is key'; $expire = 60;// 超时时间 // cache 的实例 $cache = Wk_Cache::instance(); $data = $cache->fetch($key); // 判断data if(empty($data)){ // 如果为空,调用db方法 $db = new Wk_DB(); $data = $db->getXXX(); $cache->store($key, $data, $expire); } // 处理$data相关数据 return $data; 基本流程为第一步,先组装查询key,到Cache查询Value,如果存在,继续处理,进入第三步;如果不存在,进入第二步第二步,根据请求,到DB中,查询相关数据,如果数据存在,把数据放到Cache中第三步,处理cache或者db中返回的数据 问题 上述流程基本上会出现在每次调用Cache的部分,先cache查询,没有的话调用DB或者第三方接口,获取数据,再次存入Cache,继续数据处理。单独看这样的代码,逻辑合理,但是如果所有调用缓存的地方都是这样的方式多就是一种问题了,应该把这种查询方式封装到更底层的方法内,而不是每次重复这样的逻辑,除了封装的问题外,还有其他一些问题,我们统一列举下 第一:从设计角度来说 重复代码,需要更底层逻辑封装。 第二:key的组装,麻烦繁琐,实际情况,可能会把各种参数组装进去,维护的时候,不敢轻易修改。 第三:设置的expire超时时间,会分散在各处逻辑代码中,最终很难统计Cache缓存时间的情况。 第四:由于要把cache->store方法放到调用db之后执行,如果db后,还有其他逻辑处理,有可能会忘掉把数据放入cache,存在调试风险。 第五:最重要的是高并发系统中,cache失效那一刻,会有大量请求直接穿透到后方,导致DB或者第三方接口压力陡升,响应变慢,进一步影响系统稳定性,这一现象为“Dogpile”。 以上问题中,最简单的是2,3问题。对于expire超时时间分散的问题,我们可以通过统一配置文件来解决,比如我们可以创建这样的一个配置文件。 “test"=>array( // namespace,方便分组 "keys"=> array( “good”=>array( // 定义的key,此key非最终入cache的key,入key需要和params组装成唯一的key "timeout"=>600, // 此处定义超时时间 "params"=>array("epid"=>1,"num"=>1), // 通过这种方法,描述需要传递参数,用于组装最终入cache的key "desc"=>"描述" ), "top_test"=>array( // 定义的key,此key非最终入cache的key,入key需要和params组装成唯一的key "timeout"=>60, // 此处定义超时时间 "ttl"=>10, // 自动触发时间 "params"=>array('site_id'=>1,'boutique'=>1,'offset'=>1,'rows'=> 1,'uid'=>1,'tag_id'=>1,'type'=>1), // 通过这种方法,描述需要传递参数,用于组装最终入cache的key "desc"=>"描述", "author"=>"ugg", ), ) ) 如上所示,通过一个算法,我们可以把site_top_feeds和params组装成唯一的入库key,组装后的key,大概是这样site_top_feeds_site_id=12&boutique=1&offset=0&rows=20&uid=&tag_id=0&type=2通过这种方式,我们避免工人自己组装key,从而杜绝第二种问题,在这个配置文件中,我们也设置了timeout,这样调用store时,我们可以直接从配置文件中读取,从而避免第三个问题。经过如上修改后,我们的cache方法,也做了适当的调整,调用示例如下。 $siteid = 121; $seminal = 1; $tag_id = 12; $tag_id = 22; $data = fetch(‘site_top_feeds’,array('site_id'=>$siteid,'boutique'=>$seminal, 'offset'=>"0", 'rows' => "20", 'uid' =>null,’tag_id’=>$tag_id,’type'=>$type),'feed'); if(empty($data)){ // db相关操作 $db = new Wk_DB(); $data = $db->getTopFeeds($site_id,$seminal,0,20,null,$tag_id,$type); // $data数据其他处理逻辑 这里 …… $cache->store(‘site_top_feeds’,$data,array(‘site_id'=>$siteid,'boutique'=>$seminal, 'offset'=>"0", 'rows' => "20", 'uid' =>null,’tag_id’=>$tag_id,’type'=>$type),'feed'); } 通过以上方案,我们看到,timeout超时时间没有了,key的组装也没有了,对于外层调用是透明的了。我们通过配置文件可以知道site_top_feeds的timeout是多少,通过封装的算法,知道组装的key是什么样的。 这种方式,并没有解决第一和第四的问题,封装性;要想完成封装性,第一件事情要做的就是回调函数,PHP作为脚本语言,并没有完善的函数指针概念,当然要想执行一个函数其实也不需要指针。PHP支持回调函数的方法有两种call_user_func,call_user_func_array。 但是,经过测试会发现上述方法,执行效率比原生方法差很多 native:0.0097959041595459s call_user_func:0.028249025344849s call_user_func_array:0.046605110168457s 例子代码如下: $s = microtime(true); for($i=0; $i< 10000 ; ++$i){ $a = new a(); $data = $a->aaa($array, $array, $array); $data = a::bbb($array, $array, $array); } $e = microtime(true); echo "native:".($e-$s)."s\n"; $s = microtime(true); for($i=0; $i< 10000 ; ++$i){ $a = new a(); $data = call_user_func(array($a,'aaa'),$array,$array,$array); $data = call_user_func(array('a','bbb'),$array,$array,$array); } $e = microtime(true); echo "call_user_func:".($e-$s)."s\n"; $s = microtime(true); for($i=0; $i< 10000 ; ++$i){ $a = new a(); $data = call_user_func_array(array($a,'aaa'),array(&$array,&$array,&$array)); $data = call_user_func_array(array('a','bbb'),array(&$array,&$array,&$array)); } $e = microtime(true); echo “call_user_func_array:".($e-$s)."s\n"; 在PHP中,知道一个对象和方法,其实调用方法很简单,比如上面的例子 $a = new a(); $data = $a->aaa($array, $array, $array); $obj = $a; $func = ‘aaa’; $params = array($array,$array,$array); $obj->$func($params[0],$params[1],$params[2]); // 通过这种方式可以直接执行 详细代码:$s = microtime(true); for($i=0; $i< 10000 ; ++$i){ $obj = new a(); $func = 'aaa'; $params = array($array,$array,$array); $obj->$func($params[0],$params[1],$params[2]); // 通过这种方式可以直接执行 } $e = microtime(true); echo "my_callback:".($e-$s)."s\n"; 这种方式的执行性能怎么样,经过我们对比测试发现 native:0.0092940330505371s call_user_func:0.028635025024414s call_user_func_array:0.048038959503174s my_callback:0.011308288574219s 在加入大量方法策略验证中,性能损耗比较低,时间消耗仅是原生方法的1.25倍左右,远小于call_user_func的3倍多,call_user_func_array的5倍多,具体封装后的代码 switch(count($params)){ case 0: $result = $obj->{$func}();break; case 1: $result = $obj->{$func}($params[0]);break; case 2: $result = $obj->{$func}($params[0],$params[1]);break; case 3: $result = $obj->{$func}($params[0],$params[1],$params[2]);break; case 4: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3]);break; case 5: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4]);break; case 6: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5]);break; case 7: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5],$params[6]);break; default: $result = call_user_func_array(array($obj, $func), $params); break;// 超过7项数据后变态方法,采用call_user_func_array机制,作为保底方案 } 备注:在使用这种方法之前,考虑过使用create_function来创建匿名函数,执行函数回调,经过测试create_function只能创造全局函数,不能创建类函数和对象函数,遂放弃。 完成以上准备工作后,就可以使用回调机制了,再次调用的业务代码 …. // 相关变量赋值 $db = new Wk_DB(); $callback['obj'] = $db; $callback['func'] = 'getTopFeeds'; $callback['params'] = array('site_id'=>$siteid,'boutique'=>$seminal, 'offset'=>"0", 'rows' => "20", 'uid' =>null,'tag_id'=>$tag_id,'type'=>$type); $top_feed_list = $cache->smart_fetch('site_top_feeds',$callback,'feed');// smart_fetch第一步会先从cache取数据,如果cache无数据,会自动触发$db->getTopFeeds($site_id...)方法的回调 使用以上方法实现对cache调用的封装,同时保证性能的高效,从而解决第一和第四个问题。 至此已经完成前四个问题,从而实现Cache的封装,并有效的避免了上面提到的第二,第三,第四个问题。但是对于第五个问题,dogpile问题,并没有解决,针对这种问题,最好的方式是在cache即将失效前,有一个进程主动触发DB操作,获取DB数据放入Cache中,而其他进程正常从Cache中获取数据(因为此时Cache并未失效);好在有Redis缓存可以选择,我们可以使用Redis的两个特性很好解决这个问题,先介绍下这两个接口 TTL方法:以秒为单位,返回给定 key 的剩余生存时间 (TTL, time to live),当 key 不存在时,返回 -2 。当 key 存在但没有设置剩余生存时间时,返回 -1 。否则,以秒为单位,返回 key 的剩余生存时间。很明显,通过这个方法,我们很容易知道key的还剩下的生存时间,通过这个方法,可以在key过期前做点事情,但是光有这个方法还不行,我们需要确保只有一个进程执行,而不是所有的进程都做,正好用到下面这个方法。 SETNX方法:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则SETNX 不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET) 的简写。返回值:设置成功,返回 1 。设置失败,返回 0 。通过这个方法,模拟分布式加锁,保证只有一个进程做执行,而其他的进程正常处理。结合以上Redis方法的特性,解决第五种的问题的,实例代码。 … // 变量初始化 $key = “this is key”; $expiration = 600; $recalculate_at = 100; $lock_length = 20; $data = $cache->fetch($key); $ttl = $cache->redis->ttl($key); if($recalculate_at>=$ttl&&$r->setnx("lock:".$key,true)){ $r->expire(“lock:”.$key, $lock_length); $db = new Wk_DB(); $data = $db->getXXX(); $cache->store($key, $expiration, $value); } 解决方案 好了,关键核心代码如下1:function回调部分代码 public static function callback($callback){ // 安全检查 if(!isset($callback['obj']) || !isset($callback['func']) || !isset($callback['params']) || !is_array($callback['params'])){ throw new Exception("CallBack Array Error"); } // 利用反射,判断对象和函数是否存在 $obj = $callback['obj']; $func = $callback['func']; $params = $callback['params']; // 方法判断 $method = new ReflectionMethod($obj,$func); if(!$method){ throw new Exception("CallBack Obj Not Find func"); } // 方法属性判断 if (!($method->isPublic() || $method->isStatic())) { throw new Exception("CallBack Obj func Error"); } // 参数个数判断(不进行逐项检测) $paramsNum = $method->getNumberOfParameters(); if($paramsNum < count($params)){ throw new Exception("CallBack Obj Params Error"); } // 6个参数以内,逐个调用,超过6个,直接调用call_user_func_array $result = false; // 判断静态类方法 if(!is_object($obj) && $method->isStatic()){ switch(count($params)){ case 0: $result = $obj::{$func}();break; case 1: $result = $obj::{$func}($params[0]);break; case 2: $result = $obj::{$func}($params[0],$params[1]);break; case 3: $result = $obj::{$func}($params[0],$params[1],$params[2]);break; case 4: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3]);break; case 5: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3],$params[4]);break; case 6: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5]);break; case 7: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5],$params[6]);break; default: $result = call_user_func_array(array($obj, $func), $params); break; } }else{ switch(count($params)){ case 0: $result = $obj->{$func}();break; case 1: $result = $obj->{$func}($params[0]);break; case 2: $result = $obj->{$func}($params[0],$params[1]);break; case 3: $result = $obj->{$func}($params[0],$params[1],$params[2]);break; case 4: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3]);break; case 5: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4]);break; case 6: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5]);break; case 7: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5],$params[6]);break; default: $result = call_user_func_array(array($obj, $func), $params); break; } } 2:自动触发回调机制 public function smart_fetch($key,$callback,$namespace="wk") { key = $prefix.$key.$suffix; $result = $this->_redis->get($key); $bttl = false; // ttl状态判断(注意冷启动) if(!empty($ttl)){ // 获得过期时间 $rttl = $this->_redis->ttl($key); if($rttl > 0 && $ttl >= $rttl && $this->_redis->setnx("lock".$key,true)){ // 设置超时时间(超时时间3秒) $this->_redis->expire("lock".$key,3); $bttl = true; } } // 如何返回值不存在,调用回调函数,获取数值,并保持数据库 if($bttl || !$result || (isset($CONFIG['FLUSH']) && !empty($CONFIG['FLUSH']))){ // 重新调整参数 $callbackparams = array(); foreach($params as $k=>$value){ $callbackparams[] = $value; } $callback['params'] = $callbackparams; $result = Wk_Common::callback($callback); $expire = $key_config["timeout"]; // 存储数据 $status = $this->_redis->setex($key, $expire, $result); $result=$this->_redis->get($key); } // 删除锁 if($bttl){ $this->_redis->delete("lock".$key); } return $result; } 至此,我们使用脚本语言特性,通过user_call_func_array方法补齐所有函数回调机制,从而实现对Cache的封装,通过配置文件定义组装key的规则和每个key的超时时间,再通过Redis的ttl和setnx特性,保证只有一个进程执行DB操作(setnx并非严格意义分布式锁),从而很好避免dogpile问题,实现cache自动触发,保证cache持续存在数据,并且有效减少DB的访问次数,提高性能。
自己实现server时,一定要对内核TCP有关的参数做一些调整,才能使系统的吞吐量处于最佳值。需要注意的是建立连接的吞吐量,网络IO吞吐量,以及连接关闭的处理。 建立连接 我们在做性能测试的时候也许会发现,机器的硬件配置很好,但不管我们怎么调整并发数,机器的load就是一点也上不去。这种情况一般都是由于操作系统建立连接成为瓶颈。在建立连接的三次握手过程中,Linux内核使用到两个队列: 未完成队列,处于SYN_RECV状态的socket队列 已完成队列,处于ESTABLISHED但没有被应用accept的socket队列 我们要调整两个参数:/etc/sysctl.conf net.ipv4.tcp_max_syn_backlog 未完成队列的最大长度,建议设为20480。 net.core.somaxconn 已完成队列的最大长度,建议设为20480。 系统关于上面两个配置的默认值很小,只有200多。如果自己开发server,一定要修改这两个配置。可以通过 /sbin/sysctl -a | grep xxx来查看。修改完后sysctl -p 重新载入内核参数。 listen方法的backlog参数 应用在listen()传入的backlog也用来指定已完成队列长度,系统取值为 min(listen_backlog, net.core.somaxconn)。因此建议在调用listen时传入很大的backlog值。 The behaviour of the backlog parameter on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using the tcp_max_syn_backlog sysctl. When syncookies are enabled there is no logical maximum length and this sysctl setting is ignored. See tcp(7) for more information. accept()线程 如果应用调用accept()不及时,随着新连接的建立,已完成队列和未完成队列会先后达到容量上限,无法创建新的连接。 TCP读写 sendQ太小,网络的输出吞吐量就上不去,tps也会上不去。而滑动窗口的大小受到对端recvQ的大小限制,因此recvQ也不能太小。 /etc/sysctl.conf 中有几个参数可以修改socket的sendQ和recvQ的大小。 net.ipv4.tcp_rmem = 4096 4096 16777216 // 设置读写缓冲区 min default max net.ipv4.tcp_wmem = 4096 4096 16777216 net.ipv4.tcp_mem = 196608 262144 393216 //单位:页 关闭连接 修改/etc/sysctl.conf中的以下参数, net.ipv4.tcp_fin_timeout 主动关闭连接一方在FIN_WAIT2状态的超时时间,不会因为没有收到对端的FIN包而一直处于FIN_WAIT2。 net.ipv4.tcp_tw_reuse 直接重用TIME_WAIT状态的socket net.ipv4.tcp_tw_recycle 回收利用TIME_WAIT状态的socket,不知道跟tcp_tw_reuse有什么区别。 改为 sessionConfig.setSoLinger(-1) 工具 netstat -s netstat -s列举出各种网络事件的次数。从中也可以找到一些线索。
CSDN 社区大趴-北京站 2016年1月9日 时间 1月9日 14:30-19:00 14:30-15:00 签到 15:00-16:30 密室逃脱 16:30-19:00 饕餮盛宴+Running Programmer Running Programmer:参考Running Man的游戏而来 地点 朝阳区 望京广顺南大街悠乐汇商业街A4-311 STARROOM(地铁14号线 阜通站/望京南下) 奖品 Cherry 机械键盘 金士顿SSD固态硬盘240G 罗技游戏鼠标(3个) 华为荣耀盒子 100元京东卡(14份) 50元京东卡(20份) CSDN背包(3个) 程序员杂志 2016第一期(5本) 最终到场40人 计划签到时间为14:30,我们到STARROOM的时候刚刚14点,竟然有位社区专家比我们还提前到达,超感动,他便是老友记的时候申哥采访的社区老友之一许长敬老师,05年就已经开始写博客了,而且能坚持至今,实为不易。 聊着聊着看到对面来了位漂亮MM,咦,郭晓湉(这个小湉不太甜),真的跟微信里看到的一样一样,真的是漂亮(作为女纸的我都忍不住多看几眼)。签到,贴名牌、选颜色手环、拍照、扫码进群一系列的动作后,大家开始聊天。程序媛遇到程序猿,难免会被问及有无男朋友之类的,做哪方面的,是不是前端妹纸等等等等……照片我就不PO了,大家可以看她的博客,头像就是真人~ 之前在群里说道,不给我带礼物的,我得好生招待。这不,柴俊堃带着新疆大核桃就来了,此时感觉吹来的是温暖的春风,心里也暖暖的~ 从论坛到博客,叶子在线上已陪伴我许久(额,可以略过此段)。今天总算是见到真人了,叶子王立国和冯亚荣相约而至,叶子和亚荣在CSDN是论坛、博客兼顾,一个数据库、一个.NET。帅,我有点激动了已经~ 任富飞也带了礼物,一大兜的稻香村点心,你们对我们是真爱啊!!!很阳光的一个大男孩,就是瘦了点,不知道是不是加班太多的缘故。富飞,不得不说你太瘦了,我得请你吃顿饭。 江文聪本来是邀请去深圳的,但是沟通后才发现,文聪已经转战北京了,放弃了蓝天,与我们同一片雾霾了。 以上单人照有想要的可以私信我,哈哈哈~ 接下来看到的场景就是,几位都在低头看手机,抢红包~ 这时微信群里热闹不已,各种位置共享,活动的这个位置实在是比较绕,进来了不一定能按原路走回。感谢大家在我顾及不上的时候帮助其他朋友找到地方~ CSDN元老:卞老师–红孩儿工具箱作者、赵老师-恒信移动、曹老师-学院讲师、田老师-搜索引擎专家(CSDN 2007 MVB)、孟老师- CSDN社区 Web开发版大版主(CSDN 2007 MVB)、刘老师-叶帆科技创始人(CSDN 2007 MVB)、陈老师-大数据技术经理 、房老师-仙剑3主程,功夫世界+龙的引擎缔造者、闫老师-第一视频Java高级工程师、以及从武汉赶过来没来得及拍照的青润–软件工程专家(CSDN 2007 MVB) CSDN 2007 MVB,一开始我也是不知道的,后来微信群里田老师发了出来,让大家猜今天到了哪几位,很感动。感谢这些元老们对CSDN一如既往的支持和关爱。 年轻有为的攻城狮们:长宇-优酷Android开发工程师、松阳- 实例妙解Cocos2D-X游戏开发 图书作者、技术小黑屋-极客头条核心成员、何红辉- Android源码设计模式解析与实战 图书作者、雷霄骅-中国传媒大学博士,专注视音频编解码、李鲁-Android工程师、李朋伟-资深研发工程师、孟祥月-学院讲师、任富飞-Java工程师、田亮亮-Oracle工程师、闫森-Android工程师、叶现一- Oracle Software Research and D Siebel Reg、世昌-Android开发经理、晓燕- 软件研发工程师,GIS方向、贺礼-极客头条核心成员、旭光-软件研发工程师,GIS方向、赵凯强-Android工程师、罗伟富-C++工程师 …… 大家陆陆续续都来了,有一群Android的社区专家,二哥称他们为安卓后起之秀,评价:已成逆天之势,大赞 这里可以大家一起找二哥~ 15点进入密室逃脱,进场前来个照片 合影 蓝队 红队 黄队 绿队 …… 前方传来战报,黄队获得第一 我想说,有两个妹纸,程序猿果然就有更大的动力啊~ …… 饕餮盛宴+ Running Programmer 即将开始 游戏环节,我们每个队有26个球备用,获得球的个数会根据游戏排名决定。 刚从密室出来,心情难免有些忐忑,为了让大家轻松起来~ 先来愉快的猜歌名吧~ 猜歌名么有皂片~别等了 指压板 伏地挺身接力夹乒乓球,这游戏暴露出了捞面大神,也叫筷子小能手-许长敬 长敬老师正轻松地小跑着~ 游戏三 你比划我来猜成语 获胜者蓝队,卞老师 总结了一下这游戏,六个字:放得开、脑洞大 接下来是密西密西时间 这里全是单人照,不PO了~~ 大家边吃边聊甚是温馨,就像是许久未见的朋友,这里没有拘束,只有畅言欢笑。 游戏四 盐水游戏 五人站成一排,按照胖纸报出的数量,依次下蹲,一旦出错,队友即刻喝水,其中准备的水中是有盐水的,喝到盐水的队即被淘汰。 最后两位女生胜出,开始谎话剪刀石头布游戏一决胜负。晓湉的机智让程序猿们又不淡定了,子墨这游戏表现很不错,这孩纸太诚实了~ 游戏五 Pie Face 玩家转动转盘来决定旋转几次玩具把手的次数,把手会随机地把奶油扔到玩家脸上。该游戏只有一位胜出者。 刚开始大家都是不愿上来的,怕被拍~ 孟祥月帮了个大忙,发了个20人的红包,抢到红包的猿媛们上来玩游戏~ 雨衣猿媛们登场了 各种表情,我实在忍不住乐了 最最后是抽奖啦~ 之前的密室逃脱以及游戏我们都有计分,第一名5个球以上(根据游戏会有所不同),第二、三名,2个球,第四名,1个球 我们将每个游戏获奖的球都会放入抽奖箱内,留到最后抽奖用~ 最后的结果看到的是黄队和蓝队领先,红队和绿队剩下的球比较多,我们以为这样黄队和蓝队中奖概率就会高很多了,结果,我们错了,那只是以为。 结果恰好相反,一抽一个红色一个绿色的,把黄队跟蓝队着急的不行不行的~哈哈哈,这就是命啊~ 最后未获奖的朋友太少太少了,我们把游戏道具都送了,就差把人给送出去了~因为我们希望来的人都有收获,哈哈哈哈~ 转眼9点多了,说好的7点结束的呢~美好的时间总是过得太快,难忘今宵……(老邓看到,应该会怨我吧,没有让他早点回家,要罚跪了~) 大家出门前,我们送上了人手礼: CSDN 2016笔记本台历、保温杯、不止于代码贴纸,希望大家在新的一年里工作顺利,身体健康,不止于代码~~~ 明年我们再聚~ 转帖:http://blog.csdn.net/soledadzz/article/details/50497559
Carmela介绍 Carmela提供基于PHP,PHP扩展,JAVA,C++等语言的一套处理4四节UTF-8解决方案,比如常见Emoji标签支持 背景: UTF-8格式含有Emoji表情字符串直接插入数据库,如果数据库未做调整会提示报错,通过更改数据库和表的字符集为utf8mb4_general_ci,可以避免这种问题。但是,在很多大型系统和架构中,修改数据库的字符集可能会引发很多的问题,比如PC端展示,新老数据兼容问题。针对这类问题,还有另外一种解决方案,入库前替换,出库后根据客户端类型做反向替换。 Carmela Carmela提供基于PHP扩展一套处理4四节UTF-8解决方案,可以把UTF-8中大于3个字节的UTF-8字符替换成ubb模式,比如某UTF-8字符%f0%9f%91%a4(为了展示方便,展示emoji标签的encode模式),替换后的样子[u]1f464[/u],同时从数据库读出时,根据不同的请求客户端(iOS,Andriod,PC)做反向替换。 Carmela的名字来源《不一样的卡梅拉》,《不一样的卡梅拉》系列故事讲的是母鸡卡梅拉和她的儿女们卡梅利多和卡门的历险故事,卡梅拉家族里的每个人都是那样的与众不同,敢于幻想,更敢于去尝试别人不敢想的事情。 安装 1.编译打包 git clone https://github.com/ugg/Carmela <php-bin>/phpize ./configure --with-php-config=<php-path>/php-config-path make make install 修改配置文件 vim /php.ini 添加以下内容 [carmela] extension=carmela.so 方法: carmela_str2ubb: 包含emoji标签的字符串转换成ubb模式,替换后的样子[u]1f464[/u]。 一个例子: $str = urldecode("This is test %F0%9F%98%9C+%F0%9F%98%99 by ugg"); echo "str:".$str."\n"; echo "ubb:".carmela_str2ubb($str)."\n"; 输出结果: str:This is test xxxx(CSDN Emoji不能展示用XXXX代替) by ugg ubb:This is test [u]1f61c[/u] [u]1f619[/u] by ugg carmela_ubb2str:包含ubb标签转换为utf-8字符串格式,针对PC平台的转移,可以参考encode.class.php中的carmela_ubb2str方法。 一个例子: $str = urldecode("This is test %F0%9F%98%9C+%F0%9F%98%99 by ugg"); $str = carmela_str2ubb($str); echo "ubb:".$str."\n"; echo "str:".carmela_ubb2str($str)."\n"; 输出结果: ubb:This is test [u]1f61c[/u] [u]1f619[/u] by ugg str:This is test(CSDN Emoji不能展示用XXXX代替) by ugg carmela_substr: 截取包含emoji字符的字符串指定长度字符。 carmela_sububb: 截取包含ubb标签的字符串的指定长度字符。 carmela_delstr: 删除字符串中的emoji字符,非严格模式,3字节的emoji字符无法删除,主要用在一些。 carmela_delubb: 删除包含ubb标签字符串中的ubb标签。 性能 使用PHP分别实现了两种方法,分别使用PHP的str_replace方法和PHP查找四字节emoji,进行替换的方法,以及PHP扩展方式,使用相同数据分别进行测试,测试效果如下。 =========================== 方案1:PHP str_replace方式 ========================= =========== EMOJI TO STRING ========== TIME:781.94ms,处理行数: 100,处理字数:10100,处理字节数:31028 平均每行处理时间:7.819ms =========== STRING TO EMOJI ========== TIME:118.566ms,处理行数: 100,处理字数:18710,处理字节数:37793 平均每行处理时间:1.186ms =========================== 方案2:PHP字符查找方式 ========================= =========== EMOJI TO STRING ========== TIME:51.526ms,处理行数: 100,处理字数:10100,处理字节数:31028 平均每行处理时间:0.515ms =========== STRING TO EMOJI ========== TIME:27.959ms,处理行数: 100,处理字数:23092,处理字节数:41236 平均每行处理时间:0.28ms =========================== 方案3:PHP扩展方式 ========================= =========== EMOJI TO STRING ========== TIME:0.721ms,处理行数: 100,处理字数:10100,处理字节数:31028 平均每行处理时间:0.007ms =========== STRING TO EMOJI ========== TIME:0.956ms,处理行数: 100,处理字数:20308,处理字节数:38452 平均每行处理时间:0.01ms 从以上测试效果上来看,str_replace方式,性能非常的差。使用PHP直接编写替换函数方式,性能提升10倍多,而采用扩展方式后,性能提升明显,在把emoji从字符形式转换为ubb方式时,性能提升1000倍。 以上测试数据通过create_file.php可以动态生成。本测试用例,生成100行数据,每行100个字符,100字符中可以包含3-10个emoji字符,进行测试的,直接运行benchmark.php 查看运行性能。 原理 处理四字节的emoji原理非常简单,通过字符对比找到emoji字符进行替换。难点就是在基本原理上如何提升性能,如何快速查找,替换。PHP扩展方式,为大家提供了一种思路,可以参考这种思路实现java,C#,js等等版本的。 PC如何支持EMoji表情展示? 在项目目录中的emoji目录下找到images目录,从web根目录创建emoji文件夹,把images文件夹整个拷贝到emoji文件下,调用encode.class.php里面的carmela_ubb2str方法, Util_Encode::carmela_ubb2str($str, "PC"); 即可在PC上展示Emoji表情,目前收集到的845个emoji表情,一些新的表情符号并没有纳入其中,当然,目前这种方法并没有写入PHP扩展中,性能相对来说并不高。 Contact ugg.xchj@gmail.com for all questions
简介 valgrind是开源的性能分析利器。 根据它的文档,可以用它来检查内存泄漏等问题,还可以用来生成函数的调用图,就这两个功能就足够有吸引力了。本文主要是介绍如何使用valgrind的callgrind工具进行性能分析。 分析过程 使用callgrind工具生成性能分析数据 命令格式如下: 1 valgrind --tool=callgrind ./exproxy 其中 ./exproxy就是我们要分析的程序。执行完毕后,就会在当前目录下生成一个文件。文件名为“callgrind.out.进程号”。如,callgrind.out.31113。注意,对于daemon进程的调试,不要通过kill -9方式停止。 如果你调试的程序是多线程,你也可以在命令行中加一个参数 –separate-threads=yes。这样就会为每个线程单独生成一个性能分析文件。如下: 1 valgrind --tool=callgrind --separate-threads=yes ./exproxy G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind -v --tool=memcheck --leak-check=full --num-callers=40 --log-file=valgrind.log.3 php benchmark.php 生成的文件除了callgrind.out.31113外,还会多出一些子线程的文件。文件名如下:callgrind.out.31113-01 callgrind.out.31113-02 callgrind.out.31113-03 把callgrind生成的性能数据转换成dot格式数据 可以使用gprof2dot.py脚本,把callgrind生成的性能分析数据转换成dot格式的数据。方便使用dot把分析数据图形化。脚本可以点此下载。脚本使用方式如下: 1 python gprof2dot.py -f callgrind -n10 -s callgrind.out.31113 > valgrind.dot 使用dot把数据生成图片 命令格式如下: 1 dot -Tpng valgrind.dot -o valgrind.png 生成的图片示例 通过图形,我们可以很直观的知道那段程序执行慢,并且了解相关调用关系。 来源:http://www.bo56.com/%E4%BD%BF%E7%94%A8valgrind%E7%9A%84callgrind%E5%B7%A5%E5%85%B7%E8%BF%9B%E8%A1%8C%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90/
emoji资料 今天研究了emoji,挺有意思,资料挺多,摘要一些信息给大家分享,也算是自己记录学习。 emoji介绍 Emoji (絵文字,词义来自日语えもじ,e-moji,moji在日语中的含义是字符)是一套起源于日本的12x12像素表情符号,由栗田穣崇(Shigetaka Kurit)创作,最早在日本网络及手机用户中流行,自苹果公司发布的iOS 5输入法中加入了emoji后,这种表情符号开始席卷全球,目前emoji已被大多数现代计算机系统所兼容的Unicode编码采纳,普遍应用于各种手机短信和社交网络中。近期,更是有不少网友用emoji图案玩猜字游戏,享受这种表情文化带来的乐趣。 关于emoji的发音:很多人第一眼见到emoji便会下意识将其误读作“一磨叽”,其实不然,emoji音译过来大概读作“诶磨叽”,当中“e”的发音颇似字母abc的a的发音。 最初日本的三大电信运营商各自有不同的字符定义,分别是DoCoMo、KDDI和Softbank。随着iOS内置了Softbank的版本,emoji在全球范围内风靡(iOS5版本以前)。而Google又自己定义了一套emoji字符。iOS5以后,apple采用了unicode定义的emoji字符(iOS5版本以后)。 unicode定义的emoji是四个字符,softbank为3个字符,emoji的四个字符从存储到展示对应没有做过考虑的系统来说,简直就是灾难。 面临问题: 插入Emoji表情,保存到数据库时报错: SQLException: Incorrect string value: '\xF0\x9F\x98\x84' for column 'review' at row 1 UTF-8编码有可能是两个、三个、四个字节。Emoji表情是4个字节,而Mysql的utf8编码最多3个字节,所以数据插不进去。 解决方案:过滤解决 把emoji直接过滤掉,简单方便有效。虽然损失了几个emoji字符,但强过不至于导致整条记录丢失。 public static String removeNonBmpUnicode(String str) { if (str == null) { return null; } str = str.replaceAll("[^\\u0000-\\uFFFF]", ""); return str; } 这种方案能预防能解决问题,并且还能是程序更加健壮,但是从用户体验上来说并不好,用户发的emoji表情丢了,看下面的解决方案。 解决方案:将Mysql的编码从utf8转换成utf8mb4。 从 MySQL 5.5.3 开始,MySQL 支持一种 utf8mb4 的字符集,这个字符集能够支持 4 字节的 UTF8 编码的字符。 utf8mb4 字符集能够完美地向下兼容 utf8 字符串。在数据存储方面,当一个普通中文字符存入数据库时仍然占用 3 个字节,在存入一个 Unified Emoji 表情的时候,它会自动占用 4 个字节。所以在输入输出时都不会存在乱码的问题了。 要使用 MySQL 的这个特性,首先需要把 MySQL 升级到 5.5.3 以上的版本。 其次,需要修改数据结构中的字符集为 utf8mb4 ,如 utf8mb4_general_ci 。由于 utf8mb4 是 utf8 的超集,从 utf8 升级到 utf8mb4 不会有任何问题,直接升级即可;如果从别的字符集如 gb2312 或者 gbk 转化而来,一定要先备份数据库。 然后,修改 MySQL 的配置文件 /etc/my.cnf,修改连接默认字符集为 utf8mb4 ,如果是自己写的 PHP 脚本,也可以在连接数据库以后首先执行一句 SQL: SET NAMES utf8mb4;。这时候,PHP 应该就可以正常保存 Emoji 到数据库了。 这种方式可能带来的问题: 存储:在数据表中,对于变长的字段(如VARCHAR2,TEXT),utf8mb4最大可存储的字符可能少于utf8系列的collation;在索引中,对于文本类型的字段,utf8mb4可索引的字符少于utf8系列的collations。如InnoDB的索引最多使用767字节。如果使用utf8mb4,每一个字符都会预留4字节做索引,而utf8则预留3字节。故此前者是191个字符,后者是255个字符。。 性能:由于以上原因,加上字符集大,utf8mb4的性能可能比utf8系列的collations低,可以参考stackoverfolow上的一个测试结果:http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci,差异不是特别大。 运维:如果一个大的环境内,如果其他的数据库都是utf8模式,把其中某个库设置为utf8mb4模式,在后续交接运维可能会造成问题,遗留下坑。 上下游:数据库支持unicode的emoji存储,上下游不一定支持。比如mysql客户端驱动(低版本的jdbc就不行)可能不支持utf8mb4,或者DDL的中间件不支持utf8mb4。web端处理utf8mb4字符展示,这些都有可能影响emoji的存储活着展示。 从上面的信息,从数据库层面如果不是特别看重存储,性能,运维并能解决上下游的问题,数据库是完全可以支持emoji的,但是有个新问题没有解决,emoji在iOS上展示OK,andriod设备如何展示emoji表情? 解决方案:转义解决 1:unicode emoji转softbank的emoji。 我们知道unicode emoji是4个字节,softbank定义的emoji占用3个字节存储,通过emoji for php http://code.iamcal.com/php/emoji/,我们可以把unicode的emoji方式转换为softbank方式,从而实现不修改数据库,就能存储emoji,相对于数据库层面的解决问题的方式,动作要小的多,并且也不会有性能,运维等方面的问题。但是有个不可避免的问题是,Softbank方式已经不再维护,所以新增加的emoji表情,Softbank中都没有,会造成部分emoji表情丢失的情况。 2:ubb UBB代码是HTML(标准通用标记语言下的一个应用)的一个变种,是Ultimate Bulletin Board (国外的一个BBS程序)采用的一种特殊的TAG。您也许已经对它很熟悉了。UBB代码很简单,功能很少,但是由于其Tag语法检查实现非常容易,所以不少网站引入了这种代码,以方便网友使用显示图片/链接/加粗字体等常见功能。 比如emoji的太阳符号,他的unicode emoji编码为U+2600,在存入数据库时,可以把它转换成 UBB 代码 [emoji]2600[/emoji] 保存,读取的时候,可以转换回来。当然针对不同的设备,比如andriod我们可以转义成andriod可以处理的emoji符号。 这种转移,可以很好解决iOS和Andriod显示emoji的问题,但是还存在几个问题。 1:andriod和iOS的emoji并不相同,相同的编码 可能在iOS上是太阳,而在andriod上是阴天,解决这种问题方式最好做下iOS和andriod下的emoji映射,同时可以在web上通过js转义处理。 2:性能,采用转义的方式处理,性能肯定会有所下降,但是可以容忍。 与UBB对应的是html转义,这种方式,其实和ubb有些类似, 使用 HTML转义字符 &#x2600;,结果和性能和UBB差不多,从规范化上来说,ubb方式更好一些。 参考资料 PHP-emoji转换表:http://code.iamcal.com/php/emoji/unicode Emoji Symbols: http://www.unicode.org/~scherer/emoji4unicode/20091221/utc.htmlemoji图标和unicode对应关系:http://www.easyapns.com/iphone-emoji-alerts谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词:http://www.fmddlmyy.cn/text6.htmlemoji在线转换工具:http://unicodey.com/js-emoji/demo.htmEmoji表情图标在iOS与PHP之间通信及MySQL存储:http://blog.csdn.net/wildfireli/article/details/9370161Mysql中校对集utf8_unicode_ci与utf8_general_ci的区别:http://hi.baidu.com/phpkoo/item/38238bd8505899e955347fca,http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ciMysql排序规则utf8_unicode_ci与utf8_general_ci的区别:http://justdo2008.iteye.com/blog/2162842Unicode Character Sets:http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.htmlMySQL设置utf8mb4编码:http://www.linuxidc.com/Linux/2014-07/104231.htm|andriod支持emoji解决方案:http://blog.csdn.net/waylife/article/details/11095113Supporting New Emojis on iOS 6:http://blog.manbolo.com/2012/10/29/supporting-new-emojis-on-ios-6让MySql支持Emoji表情(MySQL中4字节utf8字符保存方法):http://www.w2bc.com/Article/8533如何处理emoji等4字节的Unicode字符:http://zhidao.baidu.com/link?url=z6PW1ya6plRBgFN7M2zdVLXUnmxYcH2_VYK8nW9Yi9-kh2estgmJomw1LssmsA853WYHsRtulkJn2okq0a3TAUDQHIiMe7b0VS-FeGMNYUusuppoting new emoji for ios6:http://blog.manbolo.com/2012/10/29/supporting-new-emojis-on-ios-6 UTF-8格式emoji:http://punchdrunker.github.io/iOSEmoji/table_html/ios6/index.html
json_encode,serialize,igbinary,msgpack四种序列化方式,在之前已经有过相关的测试,PHP5.5这方面的测试暂时没有,这次测试基于PHP5.5,并且测试用例,http://blog.csdn.net/hguisu/article/details/7651730的测试用例是一样的,只是从这个测试上家里igbinary serialize的测试,作为对比,可以参考http://www.ooso.net/archives/538 运行环境 PHP5.5 内存 16G 8核 2.0GMHz 性能&空间大小列表 采用小数组测试结果(注意为了数据好看,小数组测试时,循环次数为10000次,大数组为1000次) json :156 serialize :222 igbinary_serialize :123 msgpack :102 json_encode :0.22339701652527 json_decode :0.53043985366821 serialize :0.31040406227112 unserialize :0.30859398841858 Igbinary Serialize: 0.25647687911987 Igbinary unSerialize: 0.19416117668152 msgpack_pack: 0.14058780670166 msgpack_unpack: 0.29048585891724 方便对比把之前PHP5.3的测试结果放到下面(之前并未测试igbinary) json :156 serialize :222 json_encode :0.1087498664856 json_decode :0.12652111053467 serialize :0.041656017303467 unserialize :0.040987968444824 采用大数组测试结果 json :5350 serialize :8590 igbinary_serialize :2432 msgpack :3929 json_encode :0.92437314987183 json_decode :1.791629076004 serialize :1.3011419773102 unserialize :1.1485421657562 Igbinary Serialize: 0.90479803085327 Igbinary unSerialize: 0.69125699996948 msgpack_pack: 0.52022004127502 msgpack_unpack: 1.0104610919952 下面是之前的结果(之前并未测试igbinary) json :5350 serialize :8590 json_encode :0.90479207038879 json_decode :1.753741979599 serialize :1.3566699028015 unserialize :1.3003630638123 小结:数据方面: 1:升级到PHP5.5后,json,serialize,igbinary三种方式序列化后,大小没有变化,说明这三种格式的对象结构没有没有变化,所以可以无缝升级,msgpack由于没有之前的数据做对比,暂时未知。 2:占用空间方面,igbinary节省空间明显优势,比如在json一个数组5.4k大小的数据,serialize方式要8.6k,而使用igbinary方式,仅需2.4k,近乎为serialize方式的1/4,但在小数组方面msgpack方式更具优势,igbinary占用空间123,而msgpack方式仅为102。但是在大数组情况下,明显igbinary方式优势更明显。大数组igbinary胜出,小数组msgpack胜出。性能方面: 1:在小数据时,json和原生serialize的性能都比PHP5.3版本有所提升,而在处理大数据量时,性能又有所下降。 2:在序列化方面,msgpack方式性能最好,其次是json_encode的,再次是igbinary,这两者相差无几,最差的为原生serialize,原生serialize性能消耗大概为json和igbinary方式的的1.4倍左右,而是msgpack方式的2倍。在大数组方面,序列化方便,基本上和小数组一致,只是igbinary性能教较json_encode方式有所提升。本轮msgpack胜出。 3:在反序列方面igbinary的比序列化过程更快,当然也是最快的,但是这种快也是有成本代价的,参见最后的注意事项,最慢的为json_decode方式,猜测原因可能在于PHP作为服务器端应用,最多的场景是encode,而decode的最常见的为js处理方式,性能不是很理想。而msgpack反序列化性能基本上是它序列化的2倍。本轮igbinary胜出。 4:整体性能对比,整体性能是序列化和反序列化之和,简单对比会发现,json是最差的,次之是原生serialize,再次为igbinary的方式,最优的为msgpack,不过igbinary和msgpack相差真的非常小,而在占用空间方面,小数据时msgpack胜出,大数据时igbinary胜出,算是各有千秋。所以,如果追求极致的性能,可以考虑使用msgpack,如果对是使用空间要求苛刻,那就选择igbinary方式,估计这也是PHPRedis选择igbinary作为内置序列化方式的原因之一,另外还有一个原因,考虑到Redis应用场景多是一写多读,要保证反序列化性能足够高,非igbinary莫属。 使用igbinary并非没有代价,在测试中我们发现,调用igbinary_unserialize时,传递非法数据,会导致整个php进程死掉,日志 child 19131 exited on signal 11 (SIGSEGV) after 1.844938 seconds from start 1.844938 seconds from start估计是因为igbinary为了提升性能,在unserialize时,没有做相关格式验证,导致整个进程异常退出。在使用Redis时,我们先期使用SERIALIZE_PHP方式序列化,为了提升性能,减少对Redis空间的浪费采用igbinary_serialize方式,再切换的时候不小心踩到这个坑,导致服务器响应出错,直接502,幸亏在daily环境上。
几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核(假设cpu配置相同) more /proc/cpuinfo |grep "physical id"|grep "0"|wc -l cat /proc/cpuinfo | grep processor 1. 查看物理CPU的个数#cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l 2. 查看逻辑CPU的个数#cat /proc/cpuinfo |grep "processor"|wc -l 3. 查看CPU是几核#cat /proc/cpuinfo |grep "cores"|uniq 4. 查看CPU的主频#cat /proc/cpuinfo |grep MHz|uniq # uname -a Linux euis1 2.6.9-55.ELsmp #1 SMP Fri Apr 20 17:03:35 EDT 2007 i686 i686 i386 GNU/Linux (查看当前操作系统内核信息) # cat /etc/issue | grep Linux Red Hat Enterprise Linux AS release 4 (Nahant Update 5) (查看当前操作系统发行版信息) # cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c 8 Intel(R) Xeon(R) CPU E5410 @ 2.33GHz (看到有8个逻辑CPU, 也知道了CPU型号) # cat /proc/cpuinfo | grep physical | uniq -c 4 physical id : 0 4 physical id : 1 (说明实际上是两颗4核的CPU) # getconf LONG_BIT 32 (说明当前CPU运行在32bit模式下, 但不代表CPU不支持64bit) # cat /proc/cpuinfo | grep flags | grep ' lm ' | wc -l 8 (结果大于0, 说明支持64bit计算. lm指long mode, 支持lm则是64bit) 如何获得CPU的详细信息: linux命令:cat /proc/cpuinfo 用命令判断几个物理CPU,几个核等: 逻辑CPU个数:# cat /proc/cpuinfo | grep "processor" | wc -l 物理CPU个数:# cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l 每个物理CPU中Core的个数:# cat /proc/cpuinfo | grep "cpu cores" | wc -l 是否为超线程?如果有两个逻辑CPU具有相同的”core id”,那么超线程是打开的。 每个物理CPU中逻辑CPU(可能是core, threads或both)的个数:# cat /proc/cpuinfo | grep "siblings" 转载:http://www.cnblogs.com/xd502djj/archive/2011/02/28/1967350.html
背景在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制。 Redis命令介绍使用Redis实现分布式锁,有两个重要函数需要介绍SETNX命令(SET if Not eXists)语法:SETNX key value功能:当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。 GETSET命令语法:GETSET key value功能:将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。 GET命令语法:GET key功能:返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。 DEL命令语法:DEL key [KEY …]功能:删除给定的一个或多个 key ,不存在的 key 会被忽略。 兵贵精,不在多。分布式锁,我们就依靠这四个命令。但在具体实现,还有很多细节,需要仔细斟酌,因为在分布式并发多进程中,任何一点出现差错,都会导致死锁,hold住所有进程。 加锁实现 SETNX 可以直接加锁操作,比如说对某个关键词foo加锁,客户端可以尝试SETNX foo.lock <current unix time> 如果返回1,表示客户端已经获取锁,可以往下操作,操作完成后,通过DEL foo.lock 命令来释放锁。如果返回0,说明foo已经被其他客户端上锁,如果锁是非堵塞的,可以选择返回调用。如果是堵塞调用调用,就需要进入以下个重试循环,直至成功获得锁或者重试超时。理想是美好的,现实是残酷的。仅仅使用SETNX加锁带有竞争条件的,在某些特定的情况会造成死锁错误。 处理死锁 在上面的处理方式中,如果获取锁的客户端端执行时间过长,进程被kill掉,或者因为其他异常崩溃,导致无法释放锁,就会造成死锁。所以,需要对加锁要做时效性检测。因此,我们在加锁时,把当前时间戳作为value存入此锁中,通过当前时间戳和Redis中的时间戳进行对比,如果超过一定差值,认为锁已经时效,防止锁无限期的锁下去,但是,在大并发情况,如果同时检测锁失效,并简单粗暴的删除死锁,再通过SETNX上锁,可能会导致竞争条件的产生,即多个客户端同时获取锁。 C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,获得foo.lock的时间戳,通过比对时间戳,发现锁超时。C2 向foo.lock发送DEL命令。C2 向foo.lock发送SETNX获取锁。C3 向foo.lock发送DEL命令,此时C3发送DEL时,其实DEL掉的是C2的锁。C3 向foo.lock发送SETNX获取锁。 此时C2和C3都获取了锁,产生竞争条件,如果在更高并发的情况,可能会有更多客户端获取锁。所以,DEL锁的操作,不能直接使用在锁超时的情况下,幸好我们有GETSET方法,假设我们现在有另外一个客户端C4,看看如何使用GETSET方式,避免这种情况产生。 C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,调用GET命令获得foo.lock的时间戳T1,通过比对时间戳,发现锁超时。C4 向foo.lock发送GESET命令,GETSET foo.lock <current unix time>并得到foo.lock中老的时间戳T2 如果T1=T2,说明C4获得时间戳。如果T1!=T2,说明C4之前有另外一个客户端C5通过调用GETSET方式获取了时间戳,C4未获得锁。只能sleep下,进入下次循环中。 现在唯一的问题是,C4设置foo.lock的新时间戳,是否会对锁产生影响。其实我们可以看到C4和C5执行的时间差值极小,并且写入foo.lock中的都是有效时间错,所以对锁并没有影响。为了让这个锁更加强壮,获取锁的客户端,应该在调用关键业务时,再次调用GET方法获取T1,和写入的T0时间戳进行对比,以免锁因其他情况被执行DEL意外解开而不知。以上步骤和情况,很容易从其他参考资料中看到。客户端处理和失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着 DEL 命令被尝试执行(但这时锁却在另外的客户端手上)。也可能因为处理不当,导致死锁。还有可能因为sleep设置不合理,导致Redis在大并发下被压垮。最为常见的问题还有 GET返回nil时应该走那种逻辑? 第一种走超时逻辑C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock设置时间戳T0 发现有客户端获取锁,进入GET操作。C2 向foo.lock发送GET命令,获取返回值T1(nil)。C2 通过T0>T1+expire对比,进入GETSET流程。C2 调用GETSET向foo.lock发送T0时间戳,返回foo.lock的原值T2C2 如果T2=T1相等,获得锁,如果T2!=T1,未获得锁。 第二种情况走循环走setnx逻辑C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock设置时间戳T0 发现有客户端获取锁,进入GET操作。C2 向foo.lock发送GET命令,获取返回值T1(nil)。C2 循环,进入下一次SETNX逻辑 两种逻辑貌似都是OK,但是从逻辑处理上来说,第一种情况存在问题。当GET返回nil表示,锁是被删除的,而不是超时,应该走SETNX逻辑加锁。走第一种情况的问题是,正常的加锁逻辑应该走SETNX,而现在当锁被解除后,走的是GETST,如果判断条件不当,就会引起死锁,很悲催,我在做的时候就碰到了,具体怎么碰到的看下面的问题 GETSET返回nil时应该怎么处理? C1和C2客户端调用GET接口,C1返回T1,此时C3网络情况更好,快速进入获取锁,并执行DEL删除锁,C2返回T2(nil),C1和C2都进入超时处理逻辑。C1 向foo.lock发送GETSET命令,获取返回值T11(nil)。C1 比对C1和C11发现两者不同,处理逻辑认为未获取锁。C2 向foo.lock发送GETSET命令,获取返回值T22(C1写入的时间戳)。C2 比对C2和C22发现两者不同,处理逻辑认为未获取锁。 此时C1和C2都认为未获取锁,其实C1是已经获取锁了,但是他的处理逻辑没有考虑GETSET返回nil的情况,只是单纯的用GET和GETSET值就行对比,至于为什么会出现这种情况?一种是多客户端时,每个客户端连接Redis的后,发出的命令并不是连续的,导致从单客户端看到的好像连续的命令,到Redis server后,这两条命令之间可能已经插入大量的其他客户端发出的命令,比如DEL,SETNX等。第二种情况,多客户端之间时间不同步,或者不是严格意义的同步。 时间戳的问题 我们看到foo.lock的value值为时间戳,所以要在多客户端情况下,保证锁有效,一定要同步各服务器的时间,如果各服务器间,时间有差异。时间不一致的客户端,在判断锁超时,就会出现偏差,从而产生竞争条件。锁的超时与否,严格依赖时间戳,时间戳本身也是有精度限制,假如我们的时间精度为秒,从加锁到执行操作再到解锁,一般操作肯定都能在一秒内完成。这样的话,我们上面的CASE,就很容易出现。所以,最好把时间精度提升到毫秒级。这样的话,可以保证毫秒级别的锁是安全的。 分布式锁的问题 1:必要的超时机制:获取锁的客户端一旦崩溃,一定要有过期机制,否则其他客户端都降无法获取锁,造成死锁问题。2:分布式锁,多客户端的时间戳不能保证严格意义的一致性,所以在某些特定因素下,有可能存在锁串的情况。要适度的机制,可以承受小概率的事件产生。3:只对关键处理节点加锁,良好的习惯是,把相关的资源准备好,比如连接数据库后,调用加锁机制获取锁,直接进行操作,然后释放,尽量减少持有锁的时间。4:在持有锁期间要不要CHECK锁,如果需要严格依赖锁的状态,最好在关键步骤中做锁的CHECK检查机制,但是根据我们的测试发现,在大并发时,每一次CHECK锁操作,都要消耗掉几个毫秒,而我们的整个持锁处理逻辑才不到10毫秒,玩客没有选择做锁的检查。5:sleep学问,为了减少对Redis的压力,获取锁尝试时,循环之间一定要做sleep操作。但是sleep时间是多少是门学问。需要根据自己的Redis的QPS,加上持锁处理时间等进行合理计算。6:至于为什么不使用Redis的muti,expire,watch等机制,可以查一参考资料,找下原因。 锁测试数据 未使用sleep第一种,锁重试时未做sleep。单次请求,加锁,执行,解锁时间 可以看到加锁和解锁时间都很快,当我们使用 ab -n1000 -c100 'http://sandbox6.wanke.etao.com/test/test_sequence.php?tbpm=t'AB 并发100累计1000次请求,对这个方法进行压测时。 我们会发现,获取锁的时间变成,同时持有锁后,执行时间也变成,而delete锁的时间,将近10ms时间,为什么会这样?1:持有锁后,我们的执行逻辑中包含了再次调用Redis操作,在大并发情况下,Redis执行明显变慢。2:锁的删除时间变长,从之前的0.2ms,变成9.8ms,性能下降近50倍。在这种情况下,我们压测的QPS为49,最终发现QPS和压测总量有关,当我们并发100总共100次请求时,QPS得到110多。当我们使用sleep时 使用Sleep时 单次执行请求时 我们看到,和不使用sleep机制时,性能相当。当时用相同的压测条件进行压缩时 获取锁的时间明显变长,而锁的释放时间明显变短,仅是不采用sleep机制的一半。当然执行时间变成就是因为,我们在执行过程中,重新创建数据库连接,导致时间变长的。同时我们可以对比下Redis的命令执行压力情况 上图中细高部分是为未采用sleep机制的时的压测图,矮胖部分为采用sleep机制的压测图,通上图看到压力减少50%左右,当然,sleep这种方式还有个缺点QPS下降明显,在我们的压测条件下,仅为35,并且有部分请求出现超时情况。不过综合各种情况后,我们还是决定采用sleep机制,主要是为了防止在大并发情况下把Redis压垮,很不行,我们之前碰到过,所以肯定会采用sleep机制。 参考资料 http://www.worlduc.com/FileSystem/18/2518/590664/9f63555e6079482f831c8ab1dcb8c19c.pdfhttp://redis.io/commands/setnxhttp://www.blogjava.net/caojianhua/archive/2013/01/28/394847.html
使用https://github.com/coolbeet/CBStoreHouseRefreshControl中的CBStoreHouseRefreshControl做了一个组件, 死活执行不了,后来发现,修改导航图片了 if ([self.navigationController.navigationBar respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)]) { [self.navigationController.navigationBar setBackgroundImage:image forBarMetrics:UIBarMetricsDefault]; } 结果,发现下面这两个函数不在回调 #pragma mark - Notifying refresh control of scrolling - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [self.storeHouseRefreshControl scrollViewDidScroll]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [self.storeHouseRefreshControl scrollViewDidEndDragging]; } 正常情况下,页面加载时,会回调这两个方法,重新计算里面相关位置。 只有设置导航图片后,如果不设置 self.navigationController.navigationBar setTranslucent:YES或者设置 self.navigationController.navigationBar setTranslucent:NO 都不能触发这个消息,真吭爹啊
引入cocoaPods后,第一次编译报这个错误 Pods was rejected as an implicit dependency for 'libPods.a' because its architectures 'x86_64' didn't contain all required architectures 'i386' 查了些资料,在网上有一种解决方法是去设置pod工程的 valid Architectures 与你的项目工程的 valid Architectures 一致 试了试没有成功, 现在编写iOS程序,引用到第三方包,运用cocoapods进行包管理已经成为了一个趋势了,但是最近运用cocoapods构建的应用却在64bit的iOS7系统中有警告的产生,具体的警告信息如下面所示: Pods was rejected as an implicit dependency for ‘libPods.a’ because its architectures ‘i386’ didn’t contain all required architectures ‘x86_64’ 具体的解决方案如下: 在TAGETS =》 Build Settings 中重新设置值. Architectures: Standard architectures (armv7, armv7s) Base SDK: Latest iOS (iOS 7.0) Build Active Architecture Only: YES Supported Platforms: iOS Valid Architectures: arm64 armv7 armv7s 最后,如下代码 post_install do |installer| installer.project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ARCHS'] = "armv7" end end end 纪念下,另外,想使用任何包,可以先到http://www.cocoapods.org 搜索,真的很好用
把主干代码merge到 1: checkout targetURL cd到其目录 2: svn merge sourceURL targetURL 3:sudo svn st | grep "! "| xargs sudo svn resolved 4:svn commit 5: svn merge sourceURL 本地目录
一个项目做过一段时间后,想把这段时间内svn日志以图形化展示,如下图所示 gource是个不错的选择。在mac系统上使用port可以安装。 如果mac上没有port,可以到http://www.macports.org/install.php下载安装,为了省事,我直接选择dmg方式安装,安装后重启mac,port才能生效。 第一步:安装gource: sudo port install gource经过漫长的等待gource安装成功。第二步:生成svn日志,当然也可以生成git日志`gource --log-command svn` > wanke.log 第三步:根据日志,调用gource,展示动画,比如gource wanke.log --follow-user guangzhao --seconds-per-day 0.02 -1280x720 -o wanke.ppm --title Wanke --hide filenames,dirnames,root,usernames关于gource的具体参数这里:https://github.com/acaudwell/Gource,生成动画的过程中,我们可以通过 Interactive keyboard commands: (V) Toggle camera mode (C) Displays Gource logo (K) Toggle file extension key. (M) Toggle mouse visibility (N) Jump forward in time to next log entry. (S) Randomize colours. (+-) Adjust simulation speed. (<>) Adjust time scale. (TAB) Cycle through visible users (F12) Screenshot (Alt+Enter) Fullscreen toggle (ESC) Quit 视角,样式等的控制 仅仅通过gource生成动画还不够帅,我们需要把这个动画保存下,不至于每次每次想别人展示的都有装一个gource。这里就用到ffmpeg。同样通过macport也可以安装ffmpeg sudo port install ffmpeg 经过更长时间的等待。安装 通过如下命令,我们把ppm转换为mp4格式 ffmpeg -y -r 60 -f image2pipe -vcodec ppm -i wanke.ppm -vcodec libx264 -preset ultrafast -pix_fmt yuv420p -crf 1 -threads 0 -bf 0 wanke.mp4 通过这种方式生成的mp4文件有点大,并且没有声音效果,我们可以通过iMovie重新编辑视频,并增加声音特效,一个完美的视频就此搞定。
PHP自从5.3后新增PHAR归档,Phar 归档的概念来自 Java™ 技术的 JAR 归档,它允许使用单个文件打包应用程序,这个文件中包含运行应用程序所需的所有东西。该文件不同于单个可执行文件,后者通常由编程语言生成,比如 C,因为该文件实际上是一个归档文件而非编译过的应用程序。因此 JAR 文件实际上包含组成应用程序的文件,但是考虑到安全性,不对这些文件进行仔细区分。Phar 扩展正是基于类似的理念,但是在设计时主要针对 PHP 的 Web 环境。同样,与 JAR 归档不同的是,Phar 归档可由 PHP 本身处理,因此不需要使用额外的工具来创建或使用。Phar 扩展对 PHP 来说并不是一个新鲜的概念。它最初使用 PHP 编写并被命名为 PHP_Archive,然后在 2005 年被添加到 PEAR 库。然而在实际中,解决这一问题的纯 PHP 解决方案非常缓慢,因此 2007 年重新编写为纯 C 语言扩展,同时添加了使用 SPL 的 ArrayAccess 对象遍历 Phar 归档的支持。自那时起,人们做了大量工作来改善 Phar 归档的性能,目前对Phar使用非常有限,而关于Phar的性能测试很少,到底Phar性能如何,通过一个简单实验检验下。测试环境:PHP:5.5.10CPU:2GHz intel core i7Mem:8GB系统:Darwin 13.1.0主要测试点:1:Phar加载速度1.1:文件大小多少的影响?1.2: include/require的影响?1.3:Phar 存根(Stub)内容的影响?2:Phar代码执行速度2.1 全局函数对比2.2 类对象2.3 类方法为了保证尽量保证测试准确,每种方式运行3次,去3次的平均值。同时作为对比,我们会直接采用代码方式,获得基准数据。Phar 文件主要包含文件phar-builder.php用于生成phar文件,执行test命令前,先执行此文件生成phar-test.phar文件。test_load.php 测试加载phar文件速度src目录内包含文件index.php文件是存根文件,包含dates.php,for.php,functions.php,dates测试文件类方法,for.php测试对象方法,functions.php测试函数方法。具体附件代码。第一:phar加载速度,采用include和require方式测试发现差异不大,只采用require方式。 $stime = microtime(true); require './phar-test.phar'; $etime = microtime(true); $total = $etime - $stime; echo "phar total:".$total."s";执行后,效率如下localhost:phar ugg$ php test_phar_load.php phar total:0.0044760704040527s localhost:phar ugg$ php test_phar_load.php phar total:0.0051448345184326s localhost:phar ugg$ php test_phar_load.php phar total:0.0043849945068359s localhost:phar ugg$ vim test_phar_load.php 平均加载4.7毫秒 对比直接源代码引用方式。 $stime = microtime(true); require './src/index.php'; $etime = microtime(true); $total = $etime - $stime; echo "src total:".$total."s\n"; 执行后,效率如下 localhost:phar ugg$ php test_src_load.phpsrc total:0.0026230812072754s localhost:phar ugg$ php test_src_load.phpsrc total:0.0026969909667969s localhost:phar ugg$ php test_src_load.phpsrc total:0.0025439262390137s 平均加载2.6毫秒结论:通过加载速度对比,phar加载方式比直接文件加载方式慢了不少,几乎直接引用文件所耗时间的两倍。同时我又在phar文件中加载一些干扰文件,使phar文件变大,发现在10k以内,这个load时间变化不大。当然我并没有把新增的文件放到存根内,这样做的目的,对于超过10K的目录,文件组织方式比如是autoload方式,而不会通过一个文件包含所有的文件。phar加载时间是src直接加载的1.8倍左右。 第二:执行速度检验phar方式,代码如下 $stime = microtime(true); //require 'phar://phar-test.phar'; require 'phar-test.phar'; $sstime = microtime(true); for($i = 0; $i<10000; ++$i){ $date = dates::next_week(); $for = new fortest(); $i = $for->for1to10000(); $number = number2Chinese('12345'); } $eetime = microtime(true); $etime = microtime(true); $total = $etime - $stime; $total2 = $eetime - $sstime; echo "phar load total:".$total."s\n"; echo "phar execution 10000 total:".$total2."s";执行效率如下localhost:phar ugg$ php test_phar_functions.php phar load total:0.0047600269317627s phar execution 10000 total:0.00017499923706055s localhost:phar ugg$ php test_phar_functions.php phar load total:0.004863977432251s phar execution 10000 total:0.00017404556274414s localhost:phar ugg$ php test_phar_functions.php phar load total:0.004680871963501s phar execution 10000 total:0.00016689300537109s执行10000次的类方法,对象实例和对象方法,以及函数方法,总共时间消耗为0.17毫秒。src执行效率localhost:phar ugg$ php test_src_functions.php phar load total:0.0029089450836182s phar execution 10000 total:0.00019693374633789s localhost:phar ugg$ php test_src_functions.php phar load total:0.0028579235076904s phar execution 10000 total:0.0002140998840332s localhost:phar ugg$ php test_src_functions.php phar load total:0.0029168128967285s phar execution 10000 total:0.00019478797912598s 执行10000次的类方法,对象实例和对象方法,以及函数方法,总共时间消耗为0.20毫秒。小结:通过执行速度对比,发现是phar方式,执行速度,要比直接文件include方式,快了(0.20-0.17)/0.20*100=15%,phar方式执行速度快的具体原因没有找到,网上有份资料,apc+include_path设置 phar执行速度很快。https://github.com/ralphschindler/test-phar-performance-apc/。总结:PHP归档phar方式,加载速度要慢于正常文件包含方式,但是执行速度要高于文件包含方式,如果配合include_path设置和APC或者OP方式,优化phar归档的加载速度,就能提升php的执行速度。下一步会做方面的尝试,1:构建大phar文件,实验加载速度,执行速度。2:了解phar加载原理和执行原理,3:包概念管理和依赖。 其他一些参考资料PHP V5.3中新特性,创建并使用Phar归档。http://www.ibm.com/developerworks/cn/opensource/os-php-5.3new4/test-phar-performance-apc https://github.com/ralphschindler/test-phar-performance-apc/
上次发布一淘HD应用,头一天发布,第二天就上线,私下还在想,是不是苹果采用什么优化的解决方案了,导致审核速度加快了。 这两天发布新版,一直碰到Invaild Binary问题,才想明白,原来大家都被这个问题绊住了,导致上传的应用少,所以审核速度变快了。 一开始碰到Invaild Binary,网上搜索了下,看到一些资料说Icon问题,可能会导致Invaild Binary,碰巧这次发布修改了Icon问题,各种修改 折腾后,上传应用还是报Invalid Binary问题,由于未在某邮件组,一直收不到苹果发的问题邮件,盲目处理了1天都未解决问题,正好认识有个同学在邮件组,帮忙看了 才找到原因。邮件如下 看到邮件,终于找到问题根源 1:苹果的广告更新条款调整了,我之前上传的时候,一直选择的是NO,项目依赖外部库确实用了IDFA,因此按照相关选项勾选,如下图。 2:第二个是个notice信息,未使用带有push授权的认证文件进行打包,这只是个notice。没事。 按上述调整后,重新上传,终于OK,进入waiting for review状态 相关资料: http://techcrunch.cn/2014/04/12/apple-developers-must-now-agree-to-ad-identifier-rules-or-risk-app-store-rejection/ http://blog.changyy.org/2014/04/ios-improper-advertising-identifier.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+changyyorg+%28%E7%AC%AC%E4%BA%8C%E5%8D%81%E5%9B%9B%E5%80%8B%E5%A4%8F%E5%A4%A9%E5%BE%8C%29
一HD项目需求,支持iOS5版本开发,没有真机想装一个模拟器,找了一篇不错的问题, http://blog.csdn.net/forestml2008/article/details/21714259 下载iOS5.1 安装,目录调整,结果提示 The requested iOS Simulator SDK is not supported by the iOS Simulator. 仔细查看文档,有这么一句 但是离线安装也并不是能安装和运行所有低版本模拟器,经试验,OS X Mavericks(10.9.x)下离线安装后可以正常运行的最低iOS模拟器版本是iOS6.0,iOS5.1和iOS5.0模拟器都可以离线安装上,但是无法正常运行,会提示iOS SDK不支持模拟器,作者也不知道是否有解决办法,如果读者有解决办法,欢迎指教,但是作者使用iOS5.0.1版本的iPhone4硬件设备进行调试是没有问题的,也就是说在OS X Mavericks(10.9.x)下使用Xcode5.1对iOS6.0以下的版本只能使用硬件设备而不能使用模拟器进行调试。OS X Mountain Lion(10.8.x)下离线安装后可以正常运行的最低iOS模拟器版本是iOS5.0,在此之下的版本作者就没有去试验了。 谨记吧。
之前的建议方法是把在xxx.info.plist文件中把 icon already includes gloss and bevel effects 设置YES 在Xcode5下,反复实现不成功,今天终于找到解决方案,如果使用xcassets设置方法,需要选择iOS icon is pre-rendered 的选择框,如图 xcod5在iOS5下白图标问题, http://iphonedevsdk.com/forum/iphone-sdk-development/115604-ipad-icon-appearing-white-on-ios-5-using-xcode-5-0.html http://stackoverflow.com/questions/19220082/support-of-ios-5-0-icons-with-xcode-5
UITableView以style:UITableViewStylePlain方式创建时,只要有cell,就会有一条黑线 哪怕至于一个cell也会有,如图 在网上找了集中方法,都不好使,比如http://blog.csdn.net/l_ch_g/article/details/9290727,中的两种方法,都尝试不好使 第一种方法 1、加方法 -(void)setExtraCellLineHidden: (UITableView *)tableView{ UIView *view = [UIView new]; view.backgroundColor = [UIColor clearColor]; [tableView setTableFooterView:view]; [view release];} 2、在 - (void)viewDidLoad { [super viewDidLoad]; //设置tableView不能滚动 [self.tableView setScrollEnabled:NO]; //在此处调用一下就可以啦 :此处假设tableView的name叫:tableView [self setExtraCellLineHidden:self.tableView]; } 在iOS4.3和iOS5.0中通过:值得注意的是在iOS4.3中可以直接设置footer为nil,但是在5.0不行,因为UITableView会默认生成一个Footer。(详见iOS Release Notes中的说明:Returning nil from the tableView:viewForHeaderInSection: method (or its footer equivalent) is no longer sufficient to hide a header. You must override tableView:heightForHeaderInSection: and return 0.0 to hide a header.) plain类型的tableview当显示的数据很少时,下面的cell即使不显示数据也会有分割线,可以通过下面这个函数去掉多余的分割线。 - (void)setExtraCellLineHidden: (UITableView *)tableView { UIView *view =[ [UIView alloc]init]; view.backgroundColor = [UIColor clearColor]; [tableView setTableFooterView:view]; [view release]; } 当tableview的dataSource为空时,也就是没有数据可显示时,该方法无效,只能在numberOfRowsInsection函数,通过判断dataSouce的数据个数,如果为零可以将tableview的separatorStyle设置为UITableViewCellSeparatorStyleNone去掉分割线,然后在大于零时将其设置为 UITableViewCellSeparatorStyleSingleLine 第二种方法 if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; // Drawing our own separatorLine here because I need to turn it off for the // last row. I can only do that on the tableView and on on specific cells. // The y position below has to be 1 less than the cell height to keep it from // disappearing when the tableView is scrolled. UIImageView *separatorLine = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, cell.frame.size.height - 1.0f, cell.frame.size.width, 1.0f)]; separatorLine.image = [[UIImage imageNamed:@"grayDot"] stretchableImageWithLeftCapWidth:1 topCapHeight:0]; separatorLine.tag = 4; [cell.contentView addSubview:separatorLine]; [separatorLine release]; } // Setup default cell setttings. ... UIImageView *separatorLine = (UIImageView *)[cell viewWithTag:4]; separatorLine.hidden = NO; ... // In the cell I want to hide the line, I just hide it. seperatorLine.hidden = YES; ... In viewDidLoad: self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 最后,创建UITableView时,使用style:UITableViewStyleGrouped,方法解决问题, 代码如下 1 self.tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStyleGrouped]; 2 3 _tableView.separatorColor = [UIColor clearColor]; 4 _tableView.backgroundView=[[UIView alloc] init]; //改变表的背景视图 5 _tableView.backgroundColor = [UIColor whiteColor]; //添加颜色 使用UITableViewStyleGrouped类型创建的UITableView,背景颜色需要使用上面两个方法设置的才能生效,普通的backgroundcolor方法无效。同时由于UITableViewStyleGrouped模式默认会有Section高度,所以,要继承下heightForHeaderInSection方法,记住在UITableViewStyleGrouped,直接修改sectionHeaderHeight的方式是不行的。 -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ // This will create a "invisible" footer return 0.01f; }
估计最近苹果app应用上架的比较多,审核比较慢,现在一个app从提交到上架短则7,8天,长则2,3个星期。我在实际上线应用时,总结了一个简单实用的小技巧,可以加快上架时间,最近使用这种方法后,我们基本上从提交应用到上架基本上控制在1个星期以内。我们一般发布app流程是1:app开发测试完成2.0。2:在iTunesconnect上增加新版本更新2.0。3:上传应用4:应用进入 Waiting for review 状态 (2-9天)5:应用进入In review 状态 (2-5天)6:Processing for App store(10分钟)7:Ready for sale (5分钟)8:For Sale app store审核中,主要费时的是4,5步骤。在4步骤中,注意是我们说的排队时间,这个时间和这段时间上传的应用有数量有关,如果数量多,排队时间就比较长。如果数量少,排队时间就少。排队结束后,直接进入In Review状态,这个和应用本身设计有关,设计复杂的应用,审核时间稍微长些,并且还有其他一些因素影响,如果被打回,会重新进入4步的队列中,不过根据我的观察,应该有个专门被打回应用的队列,这个队列的优先级高于新上传的应用,所以,即使应用被打回,也会有较高优先级进入In Review,但是这个不是我们想看到的。在整个上述过程中,花费的总时间我们没有办法控制,但是我们可以通过一些技巧,尽量做到,我们真实提交app时,我们的应用,处在4中队列的前面。所以,我们的做法是 1:开发应用的同时,在在iTunesconnect上增加新版本更新2.0,并在当前版本上简单升级版本号,上传应用(这样做的目的:及时审核通过,用户也可以正常使用应用)2:应用进入Waiting for review状态,同时开发测试新版本应用(这个时间控制在5天左右)3:新版本应用开发完毕。4:从iTunesconnect上撤销用于排队版本应用,上传新版本app(一般3天左右)5:应用进入In review 状态 (2-5天)6:Processing for App store(10分钟)7:Ready for sale (5分钟)8:For Sale 这个改变非常简单,整个流程,由应用开发和苹果审核的串行过程修改为并行进行,从而加快app上线速度。我们在一淘HD和手机一淘上均做了这些尝试,目前验证OK,从提交应用到最后上线基本上控制在1周以内。苹果的审核策略和流程一直在变化,我们要做的是在变化过程中寻找技巧,解决 app 应用上线最后一公里的问题。 苹果审核指南中文版:http://www.cocoachina.com/appstore/top/2013/0304/5757.html
最近做了一个产品列表页类似于搜索列表页, 功能比较简单,比搜索页复杂的逻辑在于,生成各个查询条件的URL。我们的链接如下: http://xxx.xxx.xxx/product/list.html?spm=0.0.0.0.fCULEV&noHistoryApi=1&q=洗衣机&start_price=1300&end_price=2300&ppath=6560:98950,2814486;570:24403,2085950&sort=sort-fid&fid=3486一些特点如下: 比如品牌部分: 1:三洋的链接中,要在ppath现有的基础上去掉中的6560:98950 2:海尔的链接中,要在ppath现有的基础上增加6560:105540同理在:这部分,都有做ppath的调整。 而对价格部分 1:1300-2300的选项,点击这项是,要在现有的url中去掉start_price和end_price方法。 2: 选择其他价格时,要把start_price和end_price调整为对应的价格。再比如这部分: 1:分类筛选部分,去过去掉三洋,迷你部分的筛选,都要去掉相关的筛选项,构建url。 2:比如点击 能耗低,需要把url中的去掉sort=sort-fid&fid=3486,而点击省水时,需要把fid调整为3386,如选择人气和销量时,需要把sort修改为static-score,并把fid去掉。 以上这些组合链接,还要加上分页的s参数变化,当修改为查询条件时,分页页码s,也要删除。 总结下:生成链接中,主要工作是从现有的URL逻辑中删除掉一些参数,或加上一些参数,或者修改某些参数中的一些参数。其中最复杂的是ppath,ppath要支持5670:1234,123;5690:xxx,xxx,需要支持:和,两种形式的组合,而这些组合要支持加减操作。 根据这些规则,我们封装了一个方法build_url_param,需要修改url部分,调用这个方法即可。 <li class="item"> <a href="<?php echo THING_COMMENT_LIST.'&'.build_url_param(array('ppath'=>$inValue['pid'].':'.$inValue['vid']),'+');?>"> <?php }else{?> <li class="item-hidden"> <a href="<?php echo THING_COMMENT_LIST.'&'.build_url_param(array('ppath'=>$inValue['pid'].':'.$inValue['vid']),'+');?>"> <?php }}else{ ?> <li class="item-checked"> <a href="<?php echo THING_COMMENT_LIST.'&'.build_url_param(array('ppath'=>$inValue['pid'].':'.$inValue['vid'],'s'=>''),'-');?>"> <?php } // end if?> 代码如上,build_url_param 构建查询url的参数部分,build_url_param第一个参数,传递一个数组,数组中传递相关参数,第二个参数"+"或者"-",“+”表示在现有URL中,加上第一个参数array中的key和对应的value;“-”表示从现有的URL中,减去第一个参数array中对应的key和value,使用以上方法后,所有生成url的部分,就变得非常简单了,只有知道当前连接,需要调整那些参数,是新增参数还是去掉参数即可。 build_url_param函数的实现如下: /** * 根据QUERY构建url * $array: 传递参数, */ function build_url_param($array,$type){ global $QUERY; $paramArray = array('q','start_price','end_price','cat','vid','ppath','sort','s','n','fid'); $myArray = array(); // 去掉不必要的参数 foreach($paramArray as $key=>$value){ // 放到myArray数组 if(isset($QUERY[$value]) && !empty($QUERY[$value])){ // n 不能小于10 if($value == 'n'){ if($QUERY[$value] < 10){ $myArray[$value] = '10'; } }else{ $myArray[$value] = $QUERY[$value]; } } } // 构造最基本的 if($type == '+'){ // 加参数 foreach($array as $key=>$value){ $temp = array(); // ppath特殊处理 // pid相同时,pid:vid,vid; vid之间用,分割 // pid不同时,pid:vid;分割 // $value参数: pid:vid的形式 if($key == 'ppath' && !empty($myArray[$key])){ // 查找是否已经有此pid $temp = $myArray[$key]; // 数组中的ppath $tempArray = explode(";",$temp); $tempPids = array(); foreach($tempArray as $k=>$v){ list($tempPid,$tempVid) = explode(":",$v); $tempPids[] = $tempPid; } $valueArray = explode(":",$value); // 找到 if(isset($valueArray[0]) && stristr($temp,$valueArray[0]) != false && in_array($valueArray[0],$tempPids)){ $myArray[$key] = str_replace($valueArray[0].":",$value.",",$myArray[$key]); }else{ // 没有找到直接增加 $myArray[$key] = $myArray[$key].";".$value; } /*}elseif($key == 'fid'){ if(!empty($myArray['fid'])){ $myArray['sort'] = "sort-fid"; } $myArray[$key] = $value; */ }elseif($key == 'sort'){ unset($myArray['s']); $myArray[$key] = $value; }else{ unset($myArray['s']); $myArray[$key] = $value; } } }else{ // 减少参数 foreach($array as $key=>$value){ // 为空,去掉整个产生 if(empty($value)){ unset($myArray[$key]); }else{ // 获取value $temp = $myArray[$key]; // 单独处理ppath if($key == 'ppath'){ // 先找对应的pid,根据pid查找vid if($value == $myArray[$key]){ unset($myArray[$key]); }else{ // 数组,如果vid=0,删除所有的pid $valueArray = explode(":",$value); $ppathArray = explode(";",$myArray[$key]); $t = array(); // ppath判断 if(isset($ppathArray[0])){ foreach($ppathArray as $inKey=>$inValue){ // 直接去掉 if($inValue != $value){ //单条记录path $inValueArray = explode(":",$inValue); // pid相同 if($inValueArray[0] == $valueArray[0]){ // 删除vid $temp = str_replace(','.$valueArray[1],'',$inValueArray[1]); // 末尾 $temp = str_replace(':'.$valueArray[1],'',$temp); // 开头 $temp = str_replace($valueArray[1].',','',$temp); // 中间 // 第二条件,用于删除所有pid下的所有数据 if(!empty($temp) && $temp != $inValueArray[1]){ // 删除后的数据进入t数组 $t[] = implode(":",array($inValueArray[0],$temp)); } }else{ $t[] = $inValue; } } } } // 处理后的数组,赋值给ppath if(count($t)){ $myArray[$key] = implode(";",$t); } } }else{ // 删除里面信息 $myArray[$key] = str_replace($value,'',$temp); } } } } // 构造一段param return http_build_query($myArray); } 代码写的比较糙,有时间再细化下,关键是build参数的方式和方法希望对大家有所启发和帮助。
1: recv错误 recv() failed (104: Connection reset by peer) while reading client request line发生这种问题,主要是因为网络问题,在迁移aizher.com 服务器过程中,碰到这样的问题,情况比较特殊,也是网络问题,但是不是网上描述本地网络端口冲突的问题。出现这种问题原因是,西部数据的服务器上需要配置白名单,才能访问服务器,我之前只是做了DNS解析到西部数据的服务器上,第一次客户端能够请到服务器上,但是,西部数据会马上把请求连接给重置,导致nginx提示连接充值,出现上述错误。解决方案也很简单,直接在西部数据的服务器上添加白名单即可。(一开始知道白名单的事情,好长时间没搞,给忘记了) 2:js,图片被php执行。在在迁移aizher.com 服务器过程中碰到的另外一个问题是,js,css,和图片被php执行(当然这是后来知道),一开始的时候,整个服务器的页面都花掉了,页面样式全乱。并且是有些图片可以正常加载,而有的不能,png和jpg的都不能加载。当时因为是php配置问题,找了N久,没有找到原因。后来 error_reporting = E_ALL & ~E_NOTICE打开php的notice提示,发现,请求png图片时,报出php语法错误来,奇哉怪也,突然想到是nginx配置问题,所有的请求,都按php解析做的,原来的nginx配置是server_name *.aizher.com; index index.html index.htm index.php; root /home/admin/web/; location \$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /home/admin/web/$fastcgi_script_name; }找到原因后,调整如下server_name *.aizher.com; index index.html index.htm index.php; root /home/admin/web/; if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; } location ~ .*\.(php|php5)?$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /home/admin/web/$fastcgi_script_name; } location /status { stub_status on; access_log off; } location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { expires 30d; } location ~ .*\.(js|css)?$ { expires 12h; } 也许你会问,为什么不把之前的nginx拷贝过来直接使用。原因是,1:这台服务器上还有其他服务,nginx文件不能直接覆盖,之前是使用的lnmp,这个这台服务器,都是从头搭建,也没有使用fpm,所以...。2:大意失荆州,太小看这个问题,以为很容易就能搞定。
以前就碰到过设置发件人后缀的方式,这次迁移服务器居然忘记,从头开始记录下 1:第一种方法,修改/etc/hosts,据说sendmail使用hosts里面的本地设置域名,修复方法如下 127.0.0.1 aizher.com 结果:不成功 2:第二种方法:修复/etc/mail/acess,增加 Connect:aizher.com RELAY 结果:不成功,同理看了一下sendmail相关配置,不需要修改太多东西就可以:http://blog.163.com/koumm@126/blog/static/95403837200921794034579/,不过还是在vim /etc/mail/local-host-names,增加如下 aizher.com 3:第三种方法,修改 编辑/etc/mail/sendmail.mc: LOCAL_DOMAIN(`localhost.localdomain’)dnl 改为LOCAL_DOMAIN(`YOUDOMAIN.com')dnl 然后m4 sendmail.mc >sendmail.cf service sendmail restart 结果,路径不存在,失败 m4:sendmail.mc:10: cannot open `/usr/share/sendmail-cf/m4/cf.m4': No such file or directory 以上错误的原因,是因为没有安装sendmail-cf,通过yum安装sendmail。 yum install sendmail-cf 4:第四种方法:修改主机名,记得之前修改过,主机名和sendmail的发送邮件有关,vim /etc/sysconfig/network NETWORKING=yes HOSTNAME=aizher.com GATEWAY=219.234.xx.xx通过如下命令,重启网络服务 /etc/init.d/network restart 通过如下命令重启sendmail /etc/rc.d/init.d/sendmail restart 结果:成功,已登陆用户名的方式发送邮件 5:第五种方法:直接在php.ini中修改, sendmail_path = /usr/sbin/sendmail -f admin@aizher.com -t -i 这时候,就以admin@aizher.com的方法发送email,搞定。
最近发现数据库同步总是出问题,最诡异的时,主从数据库写入的数据不一样,我勒个去。程浩同学看了半天终于找到原因,原来是PDO的一个大坑,加上binlog的一个大坑。 首先声明,这篇文章有很强的攻击性,如果你利用这里面写的东西攻击,所造成的一切后果,自负! 起因: 2010/12/15 我的领导,突然要求我们开始折腾一下机器。主要的目的是,没做备份的,做一下备份,单个的数据库做主从,线上的机器要做一个能快速恢复的热备份。经过检查发现机器若干台需要整理,于是开始一一处理,其他的还算顺利,但发现了一个 数据库的同步经常有问题,主要问题表现在做好主从同步以后,经过一段时间就会发现重复的插入,引起同步失效。 排查过程: 1、首先考虑数据本身就不一致,造成的同步失效。 处理方法: 在一个周五的晚上 12 点,夜深人静的时候,趁大家都熟睡的时候,直接对那台线上的数据库 shutdown ,在回写硬盘缓存之后,打包数据,从新同步。当天夜里、及次日均未发现数据不同步的情况。 2、本以为问题解决,结果在周一,再次发现数据同步失败。 处理方法: 考虑主从数据库系统及版本不一致的情况。从装系统,及数据库,系统(CentOS 5.5) 数据库 (Mysql-5.8.87) ,之后重新做的数据同步。 3、 次日,悲剧在次发生,同步又掉了 …… 处理方法: 现在有点茫然了,主从两台机器,从系统到数据库完全一样,数据也保证没有任何问题。为什么就同步不起来呢。现在只能考虑其他因素了。系统我使用 kickstar 装的,数据库是我自己制作的 rpm , mysql 的数据更是从一个 tar 包里解出来的。为了方便测试,我把主库放到了一个卷上,对从数据库做了只读。清除所有日志,从新同步了数据库。并且求助于我的同事,张文亭、包鹏、李锁住,让他们和我共同观察这个问题,果不出所料,在之后的一段时间内,同步在一次的失败了。 经过数次的测试(因为有卷了,可以做卷影复制),我们发现了一个问题,每次同步失败的原因都是因为同一个错误,就是重复的键值,而且这些错误都是出现在同一个库的几张表内。我把这个库的数据 dump 出来,然后把库删除了,手工重建 库 和 表 ,然后把数据导入,重新同步。 4、 结果大家应该猜得到,同步依旧失效 …… 处理方法: 实在没办法了,只好吧这个库挪到另外的单独的一台机器上,单独观察,其他剩余的库做了同步。 5、 在那个库挪走以后同步竟然神奇的好了,观察了一周。 处理方法: 看来这问题就出在哪个被挪走的库上面了。于是把那个库的结构单独拿出来,做了主从,手工插入若干条,依旧没有出现任何问题,在把那个库的数据也导入了,手工插入更新若干条,也没有出现问题。观察使用了一周,一切正常。换到线上的同步一测试,不出半天同步又失效了 !~~难道这就是传说中的人品问题?鉴于我最近没干啥亏心事,决定对这个东西出大招,一探究竟, 无奈之下,在网上请教了一下mysql 的大牛人物(叶金融),他也认为这可能是一个 mysql 的 bug ,于是就和我的同事李锁住开始折腾。 依次查找用到的 apache ,php ,zend,pdo 等。这真是不看不知道,一看吓一跳!我发现这做运维的网管和程序员那就是一对天敌。看这些东西那叫一个郁闷。 首先查看 apache ,发现是使用 apache 的 proxy 模块代理到另一个机器。 到了那个机器我怎么也找不到访问的那个路径,我不懂 php 但配置 apache 不菜啊,咋就没有呢,这一通折腾才发现人家在那个目录下写了一个 .htaccess 又重定向了 !~ 这次总算找到那个 php 了,一搜索光 Insert 函数就有 4、5 个。这玩意太多,还是找 mysql_query() 。找了半天没有~~,仔细一看用的是 PDO 你用 PDO 你就 $var = new PDO(‘mysql:host=xx;dbname=xx;charset=gbk’,'xx’,”); 用吧,一找 new PDO 还没有。 一点点的找下去发现 人家用的是 Zend_Db::factory(),还搞了一个超复杂的对象。 $config->db->adapter,$config->db->config->toArray() 这里咬牙、跺脚若干次。 总算找到正主了,找这个可真不容易: 访问不直接访问,用 proxy 弄跑了,弄到另一个机器上也不老老实实的访问,整一个 302 跳转了,php 链接 mysql 有现成的 mysql_query() 不用,非要调用 PDO ,PDO还不直接调用,要用 Zend 的框架调用。 接下来的事情就是跟踪 PDO Zend 调用过程,抓包查看交互的数据,根据许许多多的调试信息来看,终于发现了,这个 PDO 处理数据的方式比较特别。 访问 mysql server 的方式有两种。 1、 直接访问模式 2、 预处理模式 先说说这两个结构的区别,直接访问就像我们用客户端连接进数据那样,标准的 sql 语句插入、更新、删除和查询。这个要求就是每个命令里面都要指明 表、字段、等信息。 预处理就是:先告诉 mysql 一个表的结构,然后,后面的全都按照这个表结构来,这样就不用每次都发送 表、字段等信息了。这样的优势是大量的插入会快一点。特点是只在第一次发送表结构。而不是每次都发送一遍,问题是 mysql binlog 里面不支持这种格式。 这两种方式比较起来,第一种 安全,第二种 快速 。第二种因为没有表结构,所以当任何一个字段出现问题,就会造成所有的数据问题,而不像第一种,只影响那一句。那个 PDO 使用的就是第二种方式,而且他错误的认为一切都是字符串,把所有的数据都转换成 16 进制了。 在第一次插入的时候 mysql 使用第二种方式插入数据,但 binlog 里面因为没有这种结构,所以他自己把语句转换成了 第一种模式,加上了表、及字段信息,但 mysql 不会对 int 形做相应的转换,(这个在字符串表示中是没有错误的),造成了记录的日志是按照字符串的方式记录的。这样在吧一个字符串插入 int 形就出现了插入的数据和日志不一致的情况。要解决这个问题只有 1、给mysql 写一个补丁,解决这个问题。(现在功力不够还写不出来) 2、在我们公司禁用 预处理结构体方式的数据写入。看来目前我们只能使用第二种方法了。 结论: 总的来说,pdo 写的有问题,mysql 的 log 记录转换的方式也存在问题。下面是我写的一个能够触发这个 bug 的代码。 #include <stdio.h>#include <stdlib.h>#include <string.h>#include <mysql.h>#define INSERT_QUERY "INSERT INTO a(a) VALUES(?)" #if !defined(MC68000) && !defined(DS90)char *strmov(register char *dst, register const char *src){ while ((*dst++ = *src++)) ; return dst-1;}#elsechar *strmov(dst, src), char *dst, *src;{ asm(" movl 4(a7),a1 "); asm(" movl 8(a7),a0 "); asm(".L4: movb (a0)+,(a1)+ "); asm(" jne .L4 "); asm(" movl a1,d0 "); asm(" subql #1,d0 ");}#endifint main(int argc, char **argv){ if (argc != 2) { printf("%s digit\n",argv[0]); return(1); } char *server="localhost",*user="root",*password=""; MYSQL *conn; MYSQL_RES *res; MYSQL_ROW row; conn = mysql_init(NULL); if (!mysql_real_connect(conn, server, user, password, "test", 0, NULL, 0)) { fprintf(stderr, "%s\n", mysql_error(conn)); exit(EXIT_FAILURE); } MYSQL_STMT *stmt = mysql_stmt_init(conn); mysql_stmt_prepare(stmt, INSERT_QUERY, strlen(INSERT_QUERY)); MYSQL_BIND bind[1]; memset(bind, 0, sizeof(bind)); unsigned long length; char query[100] = {0}; char *pos = query; strcpy(query,argv[1]); bind[0].buffer_type= MYSQL_TYPE_BLOB; bind[0].buffer= query; bind[0].is_null= 0; bind[0].length= &length; /* Bind the buffers */ mysql_stmt_bind_param(stmt, bind); /* Supply data in chunks to server */ mysql_stmt_send_long_data(stmt,0, pos, strlen(query)); mysql_stmt_execute(stmt); mysql_stmt_close(stmt);} 测试过程如下: mysql -uroot -p <<'EOF'CREATE DATABASE test;USE test;DROP TABLE IF EXISTS a;CREATE TABLE a ( a int(11) NOT NULL COMMENT 'id', UNIQUE KEY a (a)) ENGINE=MyISAM;EOF# 制作同步数据库,省略代码若干条 gcc -g $(mysql_config --cflags --libs) -o mysql_test mysql_test.c./mysql_test 12345./mysql_test 23456# 现在查看你的从数据库已经不同步了。 以上转自:http://blog.chinaunix.net/uid-8746761-id-2015321.html 下面是我们的php测试代码: <?php $dsn = "mysql:host=localhost;dbname=wanke"; $db = new PDO($dsn, 'wanke', 'wanke'); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); //必须加 $db->exec('SET NAMES gbk'); //必须加 //$sth = $db->prepare("DELETE FROM `atest` WHERE t2=:t1"); //$sth = $db->prepare("INSERT INTO atest (`t1`,`t2`) VALUES(?,?)"); $sth = $db->prepare("INSERT INTO atest (`t1`,`t2`) VALUES(:a,:b)"); //必须是这种形式,不能是问号的 //$sth->bindValue(':t1','666777',PDO::PARAM_STR); //$sth->bindValue(1,67890); //$sth->bindValue(2,'ttttttasdfasttttt'); $sth->bindValue(':a','6784444'); $sth->bindValue(':b','ttttttasdfasttttt'); $count = $sth->execute(); echo $count; $db = null; ?> 另外一种解决方案 主库使用:binlog_format="ROW" 模式可以避免这种情况的发生,不用修改PDO属性。mysql 默认的binlog 使用的是 Statement 模式
网站建设完成之后,第一件事情就是向各大搜索引擎提交新网站。搜索引擎提交包括提交给搜索引擎爬虫和提交给分类目录。提交给搜索引擎爬虫的目的是让搜索引擎将网站收录到索引数据库。检验网站是否被搜索引擎收录的办法是直接在搜索引擎中搜索网址,查看能否找到网站结果,也可以通过输入命令site:yoursite获得具体的页面收录数量。提交给搜索引擎分类目录有两个目的:一是为了用户通过分类目录检索到网站,二是为网站获得一个高质量的外部链接,有助于增加网站的链接广度。 中文网站提交给搜索引擎爬虫和分类目录 360搜索引擎登录入口:http://info.so.360.cn/site_submit.html 即刻搜索网站提交入口:http://zz.jike.com/submit/genUrlForm 盘古数据开放平台:http://open.panguso.com/data/resource/url/new 百度搜索网站登录口:http://www.baidu.com/search/url_submit.html 百度单个网页提交入口:http://zhanzhang.baidu.com/sitesubmit Google网站登录口:https://www.google.com/webmasters/tools/submit-url Google新闻网站内容:http://www.google.com/support/news_pub/bin/request.py?contact_type=suggest_content&hl=cn bing(必应)网页提交登录入口:http://www.bing.com/toolbox/submit-site-url 简搜搜索引擎登陆口:http://www.jianso.com/add_site.html 搜狗网站收录提交入口:http://www.sogou.com/feedback/urlfeedback.php SOSO搜搜网站收录提交入口:http://www.soso.com/help/usb/urlsubmit.shtml 雅虎中国网站登录口:http://sitemap.cn.yahoo.com/ 网易有道搜索引擎登录口:http://tellbot.youdao.com/report 中搜免费登录服务:http://register.zhongsou.com/NetSearch/frontEnd/free_protocol.htm MSN必应网站登录口:http://cn.bing.com/docs/submit.aspx?FORM=WSDD2 Alexa网站登录入口:http://www.alexa.com/help/webmasters TOM搜索网站登录口:http://search.tom.com/tools/weblog/log.php 铭万网B2B(必途)网址登陆口:http://search.b2b.cn/pageIncluded/AddPage.php 蚁搜搜索网站登录口:http://www.antso.com/apply.asp 快搜搜索网站登录口:http://www.kuaisou.com/main/inputweb.asp 汕头搜索登录口:http://www.stsou.com/join.asp 孙悟空搜索网站登录:http://www.swkong.com/add.php 天网网站登陆口:http://home.tianwang.com/denglu.htm 速搜全球登陆口:http://www.suso.com.cn/suso/link.asp 酷帝网站目录提交入口:http://www.coodir.com/accounts/addsite.asp 快搜网站登陆口:http://www.kuaisou.com/main/inputweb.asp 搜猫搜索引擎登录入口:http://test.somao123.com/search/url_submit.php 泽许搜索网站登录入口:http://www.zxyt.cn/guide/?m=adc4&Nsid=a3c6847db163587d&wver=t 一淘网开放搜索申请入口:http://open.etao.com/apply_intro.htm?spm=0.0.0.40.9VF4FQ
以前好像很少碰到Xcode中代码提示出问题的情况,即使碰到了大多也是后来自然的就好了,最近换用了Xcode4.3,经常遇到这个问题。 通过无所不能的谷歌大神,找到了苹果论坛上提供的一个解决方案(https://discussions.apple.com/thread/2746273?start=0&tstart=0): 1. cd进入~/Library/Developer/Xcode/DerivedData2. ls一下3. 找到你的项目所用的目录(一般以你的项目名开头)4. cd 目录名5. rm -r Index 删除掉你的项目所用的索引文件夹 或者在Xcode->Window->Organizer->Projects选中你的项目,点击如下图Derived Data右侧的Delete按钮 注:(1) 原文表示删除 ~/Library/Developer/Xcode/DerivedData下所有的文件,我尝试发现只需要删除当前项目相关的索引文件即可(2) DerivedData从字面上理解应该是收集到的数据,应该是Xcode针对这个项目缓存的一些数据,不会影响项目本身的完整性 原文地址:http://www.1mima.com/xcode4%E4%B8%AD%E4%BB%A3%E7%A0%81%E8%A1%A5%E5%85%A8code-completion%E5%A4%B1%E6%95%88%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/
团购精品推荐,又被拒绝了,拒绝原因。 12.3: Apps that are simply web clippings, content aggregators, or a collection of links, may be rejected 这是好事啊,需要多关注下产品本身,而不是随便上功能,抢位置了。 附图一张,这可是我辛苦做的 类似 sina微博的模式啊
想在iOS上获取城市名称,采用了一个方法 - (void)startedReverseGeoderWithLatitude:(double)latitude longitude:(double)longitude{ CLLocationCoordinate2D coordinate2D; coordinate2D.longitude = longitude; coordinate2D.latitude = latitude; MKReverseGeocoder *geoCoder = [[MKReverseGeocoder alloc] initWithCoordinate:coordinate2D]; geoCoder.delegate = self; [geoCoder start]; } #pragma mark - - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark { NSString *subthroung=placemark.subThoroughfare; NSString *local=placemark.locality; NSLog(@"城市名:%@-%@-%@",placemark.locality,local,subthroung); if (local) { [cityLabel setText:local]; } } - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error { } 原文参考:http://blog.csdn.net/diyagoanyhacker/article/details/6412557 上面的文章只给了一个实现类,对于新手来说,比较困难。 我在补充下, 增加框架,coreLocation.framework,MapKit.framework框架。 在实现文件中包括 #import <CoreLocation/CoreLocation.h> #import <Mapkit/Mapkit.h> 同时增加委托 CLLocationManagerDelegate,MKReverseGeocoderDelegate> 直接编译运行,出现 server did not accept client registration 68 经过google,终于找到这是个bug。原文 http://forums.bignerdranch.com/viewtopic.php?f=79&t=2069 解决方法在,实现文件的的#import 和 @implementation 之间增加如下代码(hach crash) @implementation CLLocationManager (TemporaryHack) - (void)hackLocationFix { CLLocation *location = [[CLLocation alloc] initWithLatitude:42 longitude:-50]; [[self delegate] locationManager:self didUpdateToLocation:location fromLocation:nil]; } - (void)startUpdatingLocation { [self performSelector:@selector(hackLocationFix) withObject:nil afterDelay:0.1]; } 经过重重磨难,终于跑起来了,也获得了经纬度,不过悲剧的是reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark ,方法没有执行。难道是模拟器的是?回家放到设备上看看去~
最近提交了一个应用,猫言猫语(毛毛猫)。 我比较喜欢一些蛋疼的语录,已经做了一个麻辣语录的应用,不过光文字,没有图片感觉比较单调。而猫言猫语就是用猫的独特眼光表达了当代人的那些蛋疼语录,正好图文并茂。正好和我的文字应用想搭配。不过,在互联网上找到的图片很少,毕竟涉及到版权问题,不能利用所有的图片。所以,我的想法很简单,帮助猫言猫语做一个应用,展示这些图片,给没有看够的,喜欢看这些蛋疼图片语录的朋友,提供正版图书的购买网址。应用很简单,也算是一种尝试。为了简单期间,我把所有收集到的近一百张照片全部放到应用里面,大概有5M的样子。做出来的样子大概是这样的 这样的应用提交上去了,审核时被拒,两个看似非常可笑的原因 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 4.9 Mb data. To check how much data your app is storing: 3.12 We also found that your app includes URLs (http://www.hehexiao.com/) which do not properly navigate to the intended destination, which is not in compliance with the App Store Review Guidelines. 在原因2.23中,被拒理由特别指出,加载时有4.9M的数据下载。这个4.9M数据,我在第一次加载时,从应用目录拷贝到documents目录内。违反了,苹果的数据存储规则。后来了解到不能把图片放到documents目录内,没办法,我使用的three20进行图片展示,图片不放在documents目录内,只能放在网上,进行加载。 而原因3.12更是有意思,之前都没有碰到过。支持support url中没有包含猫言猫语应用的相关信息,是的没错,http://www.hehexiao.com只是我的一个笑话网站。没想到苹果现在加强了严格审查,要求support url中必须包含应用相关的信息,解决方案,只能做了一个页面,单独介绍下这个应用。 以上两个原因在做andriod应用,根本算不上什么问题,但是对于苹果来说,就是被拒的理由。这样的理由对于app store中app应用保证了质量,也使开发者真真的去好好做每个应用,而不是凑合了事。对于这样审核,我举双手赞同,只有这样app store这个生态系统才能长久,不至于被下三滥的应用拉低档次。 最新反馈:经过修改,已经通过审核,现在已经在app store上上线,链接就不发了,省的说是做广告。 转帖注明:http://blog.csdn.net/ugg
快递行业是和地址打交道的行业,地址这种东西,不是公交车,他是固定不变的,经纬度摆在那里,是多少就是多少,变化的是包裹从一个地方移动到另外一个地方,快递行业服务移动,从一个城市移动到另外一个城市,快递行业已经很好的去解决了,我们要说的是LBS在快递中最终一公里的应用。 设想一下,如果我是快递员,我希望到了你们门口,还没有敲门的那一瞬间,你已经打开门,直接签单收件了。不用我去打电话发短信和你一次确认,最郁闷的是到了你家楼下,你却不在家,如果我们能提前知道你不在家,就不用送了,节省下来的时间别别人送多好。 而我们收货时,最纠结的是,不知道快递员什么时候上门,也不知道快递员送到哪里了?什么时候能送到?心想这小子怎么还不打电话啊? 而快递公司那?快递员领了件后,半天发完是他,一天发不完也是他,中间干了什么不知道? 造成这些问题的原因是,信息不对称。快递员知道自己在哪里?要去哪里?用户知道我的地址在这里,我的人也在这里,我不知道你什么时间过来?那么我们通过LBS来贡献这些信息,不就好了么?快递公司知道快递员领了件,但是不知道快递员送的情况? 来吧,让我们给快递员装上个GPS,这样用户和快递公司通过GPS了解快递员的位置了,但是快递员不敢了,我的隐私怎么办啊?再说,我送的话,也得一家一家的送,就算你知道我的信息,你也不知道我多长时间送到啊,我们离成功很近了,不妨好好想想怎么处理会更好。 快递员要有一个设备A,具有如下功能 1:定位,能够获得当前的地理位置。 2:能联网,汇报/获取的一些服务信息。 3:记录本次要送快件的信息(快件编号或者地址) 4:通知功能,当进入范围内时,可以通知这个范围地址内的快件接受人,做好准备,快件马上就要送到。 快递公司的设备B:具有如下功能 1:获取A 的定位信息(A为复述)。 2:能够给A提供一些信息帮助。 用户设备C 1:能够得到A或者B的消息通知 2:必要是可以直接和A联系。 卖家设备D 1:能够收到A或者C的消息通知 2:必要时能够和C直接联系。 整个流程就是这样的 1:A从B处取到快件,一路分发,当他距离C,1000米时,A发通知消息给C,通知C做好准备,快件快要送到。A继续移动,距离500米时,再次发确认消息给C,马上就要送到。A继续移动,距离50米时,再次发确认消息给C,快件已到楼下。 2:如果中间过程C告知A,不方便接快件,A直接去送下一家。 更复杂的流程我们可以这样。 1:A从B处取到快件,一路分发,当他距离C,1000米时,A发通知消息给C,通知C做好准备,快件快要送到;A发消息给D,告知D正去给C派件。A继续移动,距离500米时,再次发确认消息给C,马上就要送到,A发消息给D,告知D正去给C派件。A继续移动,距离50米时,再次发确认消息给C,快件已到楼下,C过来取件,并直接在A的设备上签单,A把签单信息返回B,B通知D已签单。
欢迎拍砖和讨论,切勿人生攻击,如果你对LBS已经非常了解,那就从“中”读取吧。 每年总有那么一段时间,快递不给力。快递爆仓新闻铺满天,快递员都踩着风火轮,快递员生活也被誉为急速人生,没有时间上厕所,每天弯腰1200多次。 这个行业被誉为劳动密集型行业,只要有力气,认识几个字,就可以送快递了。其过程就是拿到快件,到了地方,打电话确认,收件。作为距离当前最热门行业电子商务如此近的快递行业,丝毫没有通过技术手段提高快递服务效率,不能不说是电子商务的悲哀,也就印证那句说法电子商务一直在赚快递的钱。 传统行业和互联网行业有机结合,将会使资源信息有效贡献分配,彻底改变人们生活,电子商务就是很好的例子。传统行业中买卖双方是要当面交易,一手交钱一手拿货的,互联网出现,无需在当面交易,也无需当面交钱,就可以完成整改交易流程,真正改变人们的生活,现在如果每周你不收两个快递,都不好意思出面了。 快递行业和移动互联网结合将会出现什么神奇效果?我们慢慢聊来。 快递这个行业,本身就是移动的,把一个东西从一个地方移动的另外一个地方,可以说先天就具有移动的本性,不用移动互联网网,简直是没天理了。但是,现在就是没天理的时代,快递员送取货物,还是通过两条腿去移动。所谓提供效率的地方,无非是每个快递员使用手机打个电话确认下,那怕他用的是最火的手机iPhone,也和移动互联网没有半点关系。这不糟蹋手机么,更重要是还糟蹋人,难倒就没有办法提供整个效率么? LBS基于位置的服务(Location Based Service,LBS),它是通过电信移动运营商的无线电通讯网络(如GSM网、CDMA网)或外部定位方式(如GPS)获取移动终端用户的位置信息(地理坐标,或大地坐标),在GIS(Geographic Information System,地理信息系统)平台的支持下,为用户提供相应服务的一种增值业务。 它包括两层含义:首先是确定移动设备或用户所在的地理位置;其次是提供与位置相关的各类信息服务。意指与定位相关的各类服务系统,简称"定位服务",另外 一种叫法为MPS-Mobile Position Services, 也称为"移动定位服务"系统。如找到手机用户的当前地理位置,然后在上海6340平方公里范围内寻找手机用户当前位置处1公里范围内的宾馆、影院、图书 LBS 馆、加油站等的名称和地址。所以说LBS就是要借助互联网或无线网络,在固定用户或移动用户之间,完成定位和服务两大功能。 以上内容来源百度百科 http://baike.baidu.com/view/152851.htm,给大家扫盲,了解下移动互联网的LBS服务。 目前LBS的主要应用方向有 1:休闲娱乐行,以Foursquare为代表的签到,大富翁游行等 2:生活服务性,主要是周边生活信息相结合,那里有便宜东西了,在和旅游类结合等等 3:社交型,SNS火爆的延续,什么地点交友,小区讨论了。 4:商业性:LBS+团购,优惠信息推送等等。 从上面可以看到,生活服务性和团购应用等可以很好的结合LBS,后续也必将发展壮大,我们拭目以待,以上撤了半天,无非就是给大家扫扫盲,接下来步入正题。
// 是否高清屏 #define isRetina ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO) // 是否iPad #define isPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) // 是否模拟器 #define isSimulator (NSNotFound != [[[UIDevice currentDevice] model] rangeOfString:@"Simulator"].location)
Default-Portrait.png iPad专用竖向启动画面 768x1024或者768x1004 2 Default-Landscape.png iPad专用横向启动画面 1024x768或者1024x748 3 Default-PortraitUpsideDown.png iPad专用竖向启动画面(Home按钮在屏幕上面),可省略 768x1024或者768x1004 4 Default-LandscapeLeft.png iPad专用横向启动画面(可省略), 1024x768或者1024x748 5 Default-LandscapeRight.png iPad专用横向启动画面(可省略), 1024x768或者1024x748 6 Default.png iPhone默认启动图片,如果没有提供上面几个iPad专用启动图片,则在iPad上运行时也使用Default.png(不推荐) 320x480或者320x460 7 Default@2x.png iPhone4启动图片640x960或者640x920 iOS设备现在有三种不同的分辨率:iPhone 320x480, iPhone 4 640x960, iPad 768x1024。以前程序的启动画面(图片)只要准备一个Default.png就可以了,但是现在变得复杂多了。 如果一个程序,既支持iPhone又支持iPad,那么它需要包含下面几个图片: Default-Portrait.png iPad专用竖向启动画面 768x1024或者768x1004 Default-Landscape.png iPad专用横向启动画面 1024x768或者1024x748 Default-PortraitUpsideDown.png iPad专用竖向启动画面(Home按钮在屏幕上面),可省略 768x1024或者768x1004 Default-LandscapeLeft.png iPad专用横向启动画面,可省略 1024x768或者1024x748 Default-LandscapeRight.png iPad专用横向启动画面,可省略 1024x768或者1024x748 Default.png iPhone默认启动图片,如果没有提供上面几个iPad专用启动图片,则在iPad上运行时也使用Default.png(不推荐) 320x480或者320x460Default@2x.png iPhone4启动图片640x960或者640x920 为了在iPad上使用上述的启动画面,你还需要在info.plist中加入key: UISupportedInterfaceOrientations。同时,加入值UIInterfaceOrientationPortrait, UIInterfacOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight。
Q:在EGORefreshTable中手动启动下拉更新的方法? A:EGORefreshTable中提供了方法,让用户下拉table到一定位置实现下拉更新的效果,现在我想复用这种效果用于table更新,比如我做一个按钮,当用户点击这个按钮时,执行这种数据加载中的效果,或者app刚刚启动时,也可以执行这个操作。详细参考代码 -(void) ViewFrashData{ [tblView setContentOffset:CGPointMake(0, -75) animated:YES]; [self performSelector:@selector(doneManualRefresh) withObject:nil afterDelay:0.4]; } -(void)doneManualRefresh{ [_refreshHeaderView egoRefreshScrollViewDidScroll:tblView]; [_refreshHeaderView egoRefreshScrollViewDidEndDragging:tblView]; } 说明: 1:viewFrashData方法是手动调用执行的方法。 2:[tblView setContentOffset:CGPointMake(0, -75) animated:YES],以动画形式展现下拉table,设置75的原因是,EGORefreshTable需要下拉65个像素才能触发更新操作,设置75这样还可以有种动态回弹的效果,你可以根据自己的需求再调整。 3:[self performSelector:@selector(doneManualRefresh) withObject:nil afterDelay:0.4];调用延迟方法的原因是,scrollview的动画效果需要一定时间,在动画还未完成时,调用egoRefreshScrollViewDidScroll方法时,是不会触发下拉更新操作的。 4:doneManualRefresh 调用 egoRefresh的didscroll和endDragging方法,模拟下拉操作。 5:tblView为UITableView对象 误区: 一开始碰到的误区是,对scrollView的方法不太熟悉,直接调用的 [tblView setContentOffset:CGPointMake(0, -75)]方法,然后做下拉动画,由于对CATransition动画不熟悉,倒腾了半天没做成下拉的动画效果(有谁知道这种效果也麻烦告诉下)。 转帖请注明: http://blog.csdn.net/ugg
大名鼎鼎的Three20想必大家都听说过,很多APP都是用它开发的,开发UI很方便,功能也很强大,用它就不必深究枯燥的iOS SDK。今天介绍一下如何在xcode4中配置环境支持Three20开发。 1。首先我们得去把源码下载下来。网址在:https://github.com/facebook/three20 解压放在我们工程目录下。 2。在解压目录下src/Three20下的Three20.xcodeproj拖到自己的工程里,在弹出的对话框中Destination栏“Copy Items into destination group's folder”不要选中,Addto targets栏中选中自己的工程target,并不是unit test target,然后点Finish. 3。在工程中展开Three20,你会发现有一个group叫Dependencies,展开它,下面有六个依赖工程,选中它们,并拖到自己的工程中去。 4。将src目录下的Three20.bundle拖到自己的工程中去。 5。加入需要的静态库。如下图红色部份是加入的: 6。加入需要的目标依赖项。如下图: 7。加入Quartz.framework 8。在工程配置页,build里Header Search Paths里加入 $(BUILT_PRODUCTS_DIR)/../three20" " $(BUILT_PRODUCTS_DIR)/../../three20" " $(CONFIGURATION_BUILD_DIR)/../../three20" 如下图: 9。在Other Linker Flags里加入 -ObjC -all_load 如图: 10。然后在需要用Three20的文件中引入头文件#import "Three20/Three20.h" 就可以用Three20的功能了。 还有一种方法就是利用脚本,在src目录下面有一个script目录,打开terminal进入到script目录下,然后运行命令: python ttmodule.py -p path/to/your/project/yourproject.xcodeproj Three20 --xcode-version=4 -p后面是你工程的绝对路径,当然也可是当前script的相对路径。 这样就自动完成第一种方法中的各种配置。 这儿有一些Three20的教程,example目录下也有不少demo工程 Three20 Stylesheets iPhone Tutorial Three20 Custom Cells iPhone Tutorial Three20 Table Item Tutorial http://www.codeios.com/thread-598-1-1.html http://iosguy.com/2010/10/19/tthree20-a-brief-ttlauncherview-tutorial/ http://three20.pypt.lt/start
在iOS上出现软键盘后,希望点击非键盘部分,隐藏键盘,即使键盘消失的方法讨论。 第一种方法:增加一个button,相应touch down事件,隐藏键盘。这种方法,太山寨了。为了相应一个事件增加一个button太不值得的。 第二种方法:在背景图片上添加Tap事件,相应单击处理。这种方法,很好代替了button方式,但是如果UI上没有背景图片,这种方法又回到到第一种山寨的方法行列中。 // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { [super viewDidLoad]; // 添加带有处理时间的背景图片 UIImageView *backView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)]; backView.image = [UIImage imageNamed:@"small3.png"]; backView.userInteractionEnabled = YES; UITapGestureRecognizer *singleTouch = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboard:)]; [backView addGestureRecognizer:singleTouch]; backView.tag = 110; [self.view addSubview:backView]; // 添加uitextfield text = [[UITextField alloc] initWithFrame:CGRectMake(30, 150, 250, 31)]; //[text setBackgroundColor:[UIColor grayColor]]; text.borderStyle = UITextBorderStyleRoundedRect; text.placeholder = @""; [self.view addSubview:text]; // 添加返回按钮 UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; button.frame = CGRectMake(125, 40, 75, 35); [button addTarget:self action:@selector(done:) forControlEvents:UIControlEventTouchUpInside]; //[button setBackgroundColor:[UIColor grayColor]]; [button setTitle:@"返回" forState:UIControlStateNormal]; [self.view addSubview:button]; } -(void)dismissKeyboard:(id)sender{ [text resignFirstResponder]; } 第三种方法:在xib文件中,修改xib文件的objects属性,默认是view属性,我们可以修改为UIControl属性,从而是xib文件相应touch down事件。这种方法,缺点就是没有xib就悲剧了。不过按说也应该可以动态设置,目前没有找到方法,那位网友知道的话,不妨告诉我下。 设置参考这里: 把objects设置未control后,可以直接相应touch down事件 综合以上三种方法,编写了一个例子,大家可以下载看看代码 代码点击这里下载 下载代码
前几天实现iBooks类似的图书列表形式,share一下,效果如下。 实现关键代码原理: 1:创建UIt=TableView对象时,设置背景透明,分隔条透明 // 设置table的分割符透明 tbView.separatorColor = [UIColor clearColor]; // 设置table背景透明 tbView.backgroundColor = [UIColor clearColor]; 2:在tableView:cellForRowAtIndexPath中绘制cell内容,展示图书。这里一个技巧是自定义一个UIButton,覆盖在图书图片上,相应点击事件。其中使用button的tag来保存table数组中的所在图书的下标。 // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *identifier = @"etuancell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if (!cell) { //cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:identifier]; cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; [cell setAccessoryType:UITableViewCellAccessoryNone]; // 取消选择模式 cell.selectionStyle = UITableViewCellSelectionStyleNone; }else{ // 删除cell中的子对象,刷新覆盖问题。 while ([cell.contentView.subviews lastObject] != nil) { [(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview]; } } // 定义图书大小 #define kCell_Items_Width 156 #define kCell_Items_Height 230 // 设置图片大小206*306 // 图片与图片之间距离为50 // 每行3,4本图书 CGFloat x = 80; CGFloat y = 40; NSInteger nowNum = kNum; if (bLandScape) { nowNum += 1; } NSInteger row = indexPath.row * nowNum; // 循环绘制出图书图片 for (int i = 0; i<nowNum; ++i) { // 跳出循环 if (row >= [data count]) { break; } // 展示图片 UIImageView *bookView = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, kCell_Items_Width, kCell_Items_Height)]; NSString *bookName = [[NSString alloc] initWithFormat:@"book%d.png",row]; bookView.image = [UIImage imageNamed:bookName]; [cell.contentView addSubview:bookView]; // 添加按钮 UIButton *bookButton = [UIButton buttonWithType:UIButtonTypeCustom]; bookButton.frame = CGRectMake(x, y, kCell_Items_Width, kCell_Items_Height); // 这里采用一个技巧,使用button的tag,记录tabledata中的序号 bookButton.tag = row; [bookButton addTarget:self action:@selector(testButton:) forControlEvents:UIControlEventTouchUpInside]; [cell.contentView addSubview:bookButton]; x += (80+kCell_Items_Width); // row+1 ++row; } return cell; } 3:在tableView:numberOfRowInSection中,动态返回tableview的row数量,其中kNum为3 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. NSInteger count = ([data count]+kNum-1)/kNum; if (bLandScape) { count = ([data count]+kNum)/(kNum+1); } return count; } 4:更多效果参考 源代码下载,点击这里 。如果您有任何问题或者疑问可以随时联系我。转发请注明http://blog.csdn.net/ugg
前几天和一网友聊天,顺便看了下他们团队的应该应用。 整体来说功能齐全很不错。 不过唯一缺陷的是界面做的颜色很浅,从界面元素,到文字显示,看起来比较费劲。 顺便提了个建议,可以把界面做的颜色鲜亮点。 得到的回复是“界面是美工的事,我只管程序” 彻底无语,如果一个工程师不热爱自己的产品,如何能做好产品。立贴记录下。
方便自己记忆:转自http://blog.csdn.net/favormm/article/details/6739311 大名鼎鼎的Three20想必大家都听说过,很多APP都是用它开发的,开发UI很方便,功能也很强大,用它就不必深究枯燥的iOS SDK。今天介绍一下如何在xcode4中配置环境支持Three20开发。 1。首先我们得去把源码下载下来。网址在:https://github.com/facebook/three20 解压放在我们工程目录下。 2。在解压目录下src/Three20下的Three20.xcodeproj拖到自己的工程里,在弹出的对话框中Destination栏“Copy Items into destination group's folder”不要选中,Addto targets栏中选中自己的工程target,并不是unit test target,然后点Finish. 3。在工程中展开Three20,你会发现有一个group叫Dependencies,展开它,下面有六个依赖工程,选中它们,并拖到自己的工程中去。 4。将src目录下的Three20.bundle拖到自己的工程中去。 5。加入需要的静态库。如下图红色部份是加入的: 6。加入需要的目标依赖项。如下图: 7。加入Quartz.framework 8。在工程配置页,build里Header Search Paths里加入 $(BUILT_PRODUCTS_DIR)/../three20" " $(BUILT_PRODUCTS_DIR)/../../three20" " $(CONFIGURATION_BUILD_DIR)/../../three20" 如下图: 9。在Other Linker Flags里加入 -ObjC -all_load 如图: 10。然后在需要用Three20的文件中引入头文件#import "Three20/Three20.h" 就可以用Three20的功能了。 还有一种方法就是利用脚本,在src目录下面有一个script目录,打开terminal进入到script目录下,然后运行命令: python ttmodule.py -p path/to/your/project/yourproject.xcodeproj Three20 --xcode-version=4 -p后面是你工程的绝对路径,当然也可是当前script的相对路径。 这样就自动完成第一种方法中的各种配置。 这儿有一些Three20的教程,example目录下也有不少demo工程 Three20 Stylesheets iPhone Tutorial Three20 Custom Cells iPhone Tutorial Three20 Table Item Tutorial http://www.codeios.com/thread-598-1-1.html http://iosguy.com/2010/10/19/tthree20-a-brief-ttlauncherview-tutorial/ http://three20.pypt.lt/start http://three20.pypt.lt/starthttp://three20.pypt.lt/start
问题:Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[UIBarButtonItem BarButtonItemWithTitle:type:target:action:]: unrecognized selector sent to class 0x87600c'环境:XCode4.2 场景:这种问题多发生在XCode4.2 移植低版本项目时出现,编译无问题,在运行是crash。 原因:unrecognized selector sent to class,特别注意下这里面的class,这里的处理方法和unrecognized selector sent to intance,方法是完全不同的。前者主要原因是在.h文件中声明和实现多个类导致的(未从苹果文档上找到详细对应的条款,如果有谁知道可以告知下)。后面的问题主要是临时变量引起的。 解决方案:把.h文件中的多个类的声明和实现分别放到不同的文件内,然后在这些.h文件中包含这些文件即可。 参考资料:http://www.cocoachina.com/bbs/read.php?tid=90337&page=1#544989
问题:Applications are expected to have a root view controller at the end of application launch环境:XCode4.2 场景:这种问题多发生在XCode4.2 移植低版本项目时出现。 原因:在iOS5下,应用加载时,需要一个root view controller,在iOS5以下的版本会有MainWindow作为启动文件,iOS5以上没有了。 解决方案:手动创建一个root view controller,在application:didFinishLaunchingWithOptions中添加如下方法,同时为了避免新增加的view对已有的程序产生影响,把ViewController.xib文件的Alpha值设置为0,即完全透明。 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // ===========add code=========== self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil]; //self.viewController = [[ViewController alloc] init]; self.window.rootViewController = self.viewController; // ===========add code============ [self showTabBarController:1]; [self.window makeKeyAndVisible]; return YES; } 参考资料:http://stackoverflow.com/questions/7520971/applications-are-expected-to-have-a-root-view-controller-at-the-end-of-applicati http://alpascual.com/post/2011/10/09/Applications-are-expected-to-have-a-root-view-controller-at-the-end-of-application-launch.aspx
首先头文件应继承CLLocationManagerDelegate. 并:#import <CoreLocation/CoreLocation.h> 响应事件中写如下代码: CLLocationManager *_locManager = [[CLLocationManager alloc] init]; [_locManager setDelegate:self]; [_locManager setDesiredAccuracy:kCLLocationAccuracyBest]; [_locManager startUpdatingLocation]; 重载 #pragma mark - #pragma mark Location manager - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { CLLocationCoordinate2D loc = [newLocation coordinate]; NSString *lat =[NSString stringWithFormat:@"%f",loc.latitude];//get latitude NSString *lon =[NSString stringWithFormat:@"%f",loc.longitude];//get longitude NSLog(@"%@ %@",lat,lon); }
最近开发浏览器插件有点上瘾,先开发了一个FF(火狐)浏览器插件,后来又开发了一个谷歌浏览器的插件,还是不觉得不过瘾,这次要尝试开发一个粗糙的IE浏览器插件,最终实现在一键实现订餐,一键取消订餐操作。 凡事知道就好做了,只是这次走了些弯路。网上只要提到IE插件开发的无不提到Com,BHO等等概念,为此我还下载了vs2010,安装platform sdk,并到codeproject下载N个例子进行研究,虽然已经好几年不用vs2010了,也不曾在写com了,还好有点底子,基本能看懂,了解了下BHO的原理,同时了解了com的一些东西,也算有些收获。不过越看越觉得我的不需要这么麻烦,不需要BHO处理各种消息状态。我只需要在IE上增加一个按钮,点击这个按钮,调用我写的js触发一个操作,并给出提示即可。走点弯路没关系,下次也许用的到。 现在改变策略,既然要增加按钮,触发操作。那到internet explorer development上找帮助就可以了,还真找到一篇文章,关于如何add menu items。仔细阅读了解到,IE上的按钮是通过增加注册表项来完成了,随即生成了两个注册表项 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\InternetExplorer\Extensions\{5D78A592-AA1A-4E6F-A808-9214B4A7222A}] "CLSID"="{5D78A592-AA1A-4E6F-A808-9214B4A7222A}" "MenuText"="我要订餐" "MenuCustomize"="我要订餐" "MenuStatusBar"="我要订餐" "ClsidExtension"="" [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\InternetExplorer\Extensions\{BA6C7C63-9EFC-4DA1-B5D9-666A624F4831}] "CLSID"="{5D78A592-AA1A-4E6F-A808-9214B4A7222A}" "MenuText"="取消订餐" "MenuCustomize"="取消订餐" "MenuStatusBar"="取消订餐" "ClsidExtension"="" 导入注册表项,重启IE,在工具中看到这两个选项,我要订饭和取消订饭。只是点击这两个menu item没有任何响应,原因是我们没有给这两个menu item增加增加响应事件。很简单在注册表项中增加一个exec即可,我们要使用之前的js,那就需要写一个页面来执行这个js,比如写如下代码 <html> <head> <metahttp-equiv="Content-Type" content="text/html;charset=utf-8"> <title>订餐系统</title> <meta name="keywords"content=""> <scriptsrc="./dingcan.js"></script> </head> <bodyonload="DingCan.loginAndDingCan()"> <span id="dcdingcan">正在订餐中...</span> </body> 如果我们的exec这样写 "Exec"="file:///D:/Program%20Files/dingcan/dingcan.html"IE会重新打开一个窗口,用户体验不好,这里我们用点小技巧,把.html修改成.hta后缀,整个事件清净了,这样设置之后,点击按钮后,是这个样子的有点大, 在html增加如下代码,设置窗口大小,<scriptlanguage="javascript"> window.resizeTo(200,100) </script>这次是这样的,完美了Hta内容补充:HTA 全名为 HTMLApplication ( HTML 全名为 Hyper Text Markup Language ),HTA 为 HTML 应用程序。HTA格式的文件不需要在浏览器中执行,windows可以直接执行此程序。更详细的内容可以参考HTML Applications: http://msdn.microsoft.com/en-us/library/ms536471%28v=VS.85%29.aspx IE插件实现的很猥琐,只不过他实现了我要的功能。这几个扩展都是皮毛,没有什么高深技术含量,只是希望对大家有所帮助,也希望消除做浏览器扩展的不自信。参考资料 Add menu items http://msdn.microsoft.com/en-us/library/aa753591(v=VS.85).aspx Internet Explorer Development http://msdn.microsoft.com/en-us/library/bb188743.aspx
FF(火狐)浏览器插件已经完成了,确实也很好用,但是有些同学不习惯使用ff浏览器,喜欢使用谷歌浏览器,点击这里查看上篇问题。那么我们就开发一个chrome的扩展,实现订餐功能。 在FF浏览器扩展中,我们使用xul定义FF的界面,使用javascript实现处理逻辑,在谷歌浏览器中,我们同样不需要学习activeX,也不需要学习com,只是使用html和javascript即可实现开发谷歌浏览器扩展的功能,只是为了简单起见,我们这次手动点击按钮,出发订餐操作。 第一步先学习下谷歌插件知识,推荐两篇不错的问题Chrome插件开发之一: 搭建基本结构http://gdfans.net/?p=14&cpage=1,Chrome插件开发之二: 添砖加瓦http://gdfans.net/?p=210 。 相对于firefox浏览器,谷歌浏览器的扩展开发简单的很多。关于基本入门上面两篇文章已经介绍的很详细了。这里顺便说下谷歌扩展管理方面的东东。你可以通过工具-》扩展程序来管理你的扩展,新开发的扩展可以通过 里面的“载入正在开发的扩展程序…”来打开正在开发扩展的目录,即可在扩展程序中管理你的程序。开发完成后,也不用像FF扩展那样需要手工打成zip,再修改为xpi后缀。而在谷歌浏览器中,只需要按下“打包扩展程序”即可完成打包,打的包会有两个,crx后缀的为打包文件,可以直接交给别人安装,pem是私钥文件,下次打包的时候使用。 有了以上知识,我做出来的谷歌浏览器扩展是这个样子的 点击“订”按钮触发登陆网站并执行操作,详细代码点击此处获得。 附上两篇介绍谷歌浏览器扩展的文章 1. 文件列表 本文引用项目 urlcmt 作为开发示例(urlcmt是一个可以对任意网页进行评论的Chrome插件,你可以下载源代码,或安装此插件),为了让插件正常工作,至少需要这几个文件: 它们的作用分别是: icon.png: 插件工具栏图标 manifest.json: 控制整个插件行为的配置文件 popup.html: 点击插件图标后弹出的窗口,是插件的主界面 如果希望插件具有更加合理的结构和功能,则还可以有以下文件: 他们的作用分别是: imgs: 存放插件界面图片 background.html: 在此运行的代码不会因为popup.html窗口消失而停止运行 icon_128.png: 在插件描述中作为插件的Logo main.css: 插件界面元件的样式表 main.js: 插件中可以使用的js函数 2. 文件说明 manifest.json 为整个插件的主控文件,基本功能描述如下: 01 { 02 "name": "urlcmt", 03 "version": "1.0", 04 "description": "网页评论 Comment the web !", 05 "default_locale": "zh_CN", 06 "browser_action": { 07 "default_icon": "icon.png", 08 "popup": "popup.html" 09 }, 10 "icons": { 11 "128": "icon_128.png" 12 }, 13 "permissions": [ 14 "tabs", 15 "http://api.gdfans.net/" 16 ] 17 } 其中: 第2行: 所有代码思想的核心,它就是插件的名称! 第3行: 插件版本,发布插件时会生成一串密文,那时会用到 第4行: 描述信息,会显示在插件属性里 第5行: 默认编码为中文 第7行: 指定插件图标的路径 第8行: 指定 popup.html 文件的路径 第11行: 指定 128 像素大小的图标的路径 第14行: 此权限支持读取标签(tab)中的信息 第15行: 此权限支持向 http://api.gdfans.net/ 发送 Ajax 请求 popop.html 为整个插件的界面,代码如下: 01 <!DOCTYPE html> 02 <metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"/> 03 <linkrel="stylesheet"href="main.css"/> 04 <scripttype="text/javascript"src="./url.js"></script> 05 <scripttype="text/javascript"src="./main.js"></script> 06 07 <bodyonload="init()"> 08 <divid="pop_msg"class="align_center colo_aaa valign_center"></div> 09 <divclass="submit_form align_center"> 10 <formid="cmt_submit_form"onsubmit="submit_cmt();return false;"action="#"method="POST"> 11 <textarearows="1"cols="6"id="cnt"class="align_left"onfocus="show_submit_form();"maxlength="200"></textarea> 12 <br/> 13 <divclass="align_right"> 14 <inputtype="submit"id="sbmt"value="submit"> 15 </div> 16 </form> 17 </div> 18 <divid="cmt_cnt"></div> 19 </body> 这里注意: 第1行: 加上这个以后,显示插件界面时,不会在底部出现一大片空白区域 其余都和普通 web 页面开发相同,空的 div 供 Ajax 填充从服务端获取的数据 3. 实现功能 – 插件初始化 有了 manifest.json 和 popup.html,就可以实现最基本的 hello world 的功能了,接下来需要为插件增加其它功能,这些功能可以用 Javascript & Ajax 实现,对此项技术不太熟悉的同学,请点击这里 查阅 w3c school 里的教程。 这些 Javascript & Ajax 代码可以写在 main.js 中,因为不仅 popup.html 要用到,后续要介绍的 background.html 中也要用到,所以为了重用起见,还是放在单独的文件里比较好。main.js 中可以包含一个 init() 函数,用以进行插件的初始化工作 1 function init() 2 { 3 // 隐藏编写评论的表单,以增大页面的可用区域 4 hide_submit_form(); 5 // 从服务端获取当前网页的评论数据,并显示在界面上 6 refresh_cmt_cnt(1); 7 }; 然后在 popup.html 的 body 标签中,加上 1 <bodyonload="init()"> 即可。 4. 实现功能 – 调用 Chrome API Chrome为插件提供了可以通过 Javascript 调用的 API,在插件的 Js 代码可以直接使用,例如想获取当前标签中的 URL 地址,并向服务端发送 Ajax 请求获取这个 URL 对应的评论信息,可以这么写: 01 function refresh_cmt_cnt(page_no) 02 { 03 // 创建 Ajax 请求对象 04 varxhr =newXMLHttpRequest(); 05 06 // 使用Chrome提供的tab接口获取当前选中的tab的信息 07 chrome.tabs.getSelected(null,function(tab) { 08 // 当 getSelected 函数执行成功以后会执行到这里 09 varcmt_cnt_obj = document.getElementById("cmt_cnt"); 10 11 // 构造 POST 数据,可以通过 tab.url 来获取标签的 URL 地址 12 // encodeURIComponent 函数用来转义特殊字符以免发生冲突 13 // 在服务端可以用 PHP 函数 urldecode 再转义回来 14 varpost_data ='cmd=1505&alt=json&url='+ 15 encodeURIComponent(tab.url) + 16 '&page_size=5&page_no='+ page_no; 17 18 // 指定提交的目标地址 19 xhr.open("POST","http://api.gdfans.net/", 20 true); 21 xhr.setRequestHeader("Content-Type", 22 "application/x-www-form-urlencoded"); 23 24 xhr.onreadystatechange =function() { 25 if(xhr.readyState == 4) { 26 // 当 Ajax 请求接收完所有返回数据时会执行到这里 27 // 因为服务端返回的数据为 json 格式,因此使用前需要解析以下 28 varresp = JSON.parse(xhr.responseText); 29 if(! resp) { 30 show_popmsg(null,'获取数据失败', 3); 31 returnfalse; 32 } 33 34 // 解析完成以后就可以读取返回的数据了 35 if(resp.result == 1501) { 36 clear_popmsg(); 37 returnfalse; 38 } 39 40 // 将数据显示在页面上 41 for(varkeyin resp.data) { 42 htmltxt += resp.data[key]['cmt'] +', '; 43 } 44 45 cmt_cnt_obj.innerHTML = htmltxt +"\n"; 46 } 47 } 48 49 // 发送 Ajax 请求,Ajax 执行成功以后会调用上面介绍的代码 50 xhr.send(post_data); 51 }); 52 53 returntrue; 54 } 至此一个可以从服务端获取 URL 评论数据的简单插件就制作完成了,只要你熟悉 Web 开发,开发 Chrome 插件就是小菜一碟~ 好了,休息一会,接下来的文章再继续分享其它内容 ^-^ 接着上一篇文章《Chrome插件开发之一:搭建基本结构》开始说起,有了基本结构,urlcmt的基本功能已经实现了,接下来需要对插件进行完善和修饰,下面介绍一些基本结构以外的内容。 1. 关于Background.html popup.html中定义的Javascript变量会在popup.html页面关闭时被释放,那么如何保存一些一直需要使用的变量呢?这就是background.html的作用,background.html页面中定义的javascript变量会在Chrome浏览器生命期中一直存在,可以把需要一直存在的数据保存在这里。 那么我们如何在background.html中保持popup.html页面中的数据呢?可以这么写,先在background.html中定义好变量,例如以下保存用户登录信息的变量 1 var global_email =""; 2 var global_passwd =""; 然后在popup.html中用以下方式来引用这些变量,可以改变或者读取变量的值 1 var bgpg = chrome.extension.getBackgroundPage(); 2 bgpg.global_email = "somebody@domain.com"; 3 bgpg.global_passwd = "password"; 2. 将数据保存在磁盘 Chrome本身没有提供用于保存变量的值的接口,不过可以通过Javascript操作Cookie保存数据,另外还有一种更加方便的保存数据方式,那就是使用HTML5的localStorage功能保存数据,这也是Chrome插件开发手册中推荐的方式,例如我们希望保存刚才在background.html中定义的用户登录信息,只需要执行以下代码即可 1 localStorage['email'] = golbal_email; 2 localStorage['passwd'] = global_passwd; 这样就算关闭了Chrome浏览器,用户的登录数据还是一样不会丢失,下次启动Chrome浏览器时,只需要执行以下代码就又可以获取这些登录数据了 1 global_email = localStorage['email']; 2 global_passwd = localStorage['passwd']; 3. 插件图标右下角的数字 经常可以看见某些插件会在插件图标的右下角显示数字,可以很直观的看到一些信息,还是非常有用的,Chrome插件开发文档称这个数字为badge,可以翻译成徽章的意思。 如果想为自己的Chrome添加这个badge,可以使用以下代码 1 // 在插件图标的右下角显示数字3 2 chrome.browserAction.setBadgeText({text: ‘3’}); 有了上面介绍的这些功能,插件能提供的服务就又进了一步,好了,今天的分享就到这里,下次再介绍Chrome插件开发的其它内容。 参考资料 Chrome插件开发之一: 搭建基本结构http://gdfans.net/?p=14&cpage=1 Chrome插件开发之二: 添砖加瓦http://gdfans.net/?p=210http://gdfans.net/?p=210 Tutorial Google chrome Extentsionshttp://code.google.com/chrome/extensions/getstarted.html
最近开发一个FF的扩展,自动完成公司的订餐操作,主要完成的功能很简单:登陆网站,执行一个特定操作,并在ff的状态栏内显示执行的成功或者失败的状态。以前没有写过FF扩展,需要从头学习,在完成这个扩展过程中,有些收获记录下来,一方面自己记录,另一方面也方便有此需求的同学。在整个开发过程中碰到一些问题,也走了一些弯路,希望对其他同学有所帮助。 由于是第一开发FF扩展,没什么经验,所以,第一步先去搜索些关于FF插件开发的文档。先几乎把所有关于FF插件开发的中文文档看个边,至少了解FF扩展和插件的基本信息,以及插件的基本结构,并在这个过程中在构建自己的开发环境。 构建自己的开发环境,构建自己的firefox开发环境,好处是你可以随便在这个开发环境中折腾,出现问题也不会影响你正常firefox的使用,当然使用这场模式也没问题。这一步主要做的事情有 第一步,firefox启动时,添加-no-remote -P develop 参数,其中develop代表开发者,如果有多个开发者你可以改这个字段就可以。Windows系统修改方法,是右键Firefox图标,属性,在目录里面添加此项即可,如下图 第二步,为了更好的调试插件,修改Firefox某些项配置,具体方法:firefox的地址栏输入about:config回车, 参数:javascript.options.showInConsole= true 作用:将chrome文件中的错误显示在Console里。 参数:nglayout.debug.disable_xul_cache= true 作用:关闭XUL缓存,这允许不重启而改变窗口以及对话的内容。这里,可以使用目录而不是Jar。但是Overlay改变后,overlay文档必须重载(这一项非常有用,修改uxl后不用重启窗口)。 参数:browser.dom.window.dump.enabled= true 作用:这将允许dump()函数输出到stdout里。参考window.dump函数。特权应用也可用nsIConsoleService。 参数:javascript.options.strict= true 作用:启用严格JavaScript错误提示,js出错调试可用。 参数:extensions.logging.enabled= true 作用:这将给出更多关于安装以及更新插件的日志信息。 参数:nglayout.debug.disable_xul_fastload= true (仅限Gecko 2.0+ (Firefox 4.0+)) 参数:dom.report_all_js_exceptions= true 以上参数有些在你的配置项已经存在,可能设置的状态值不对,修改下即可。有些参数可能没有,需要你手动创建这些参数和值。只需要在list中右键-》新建-》布尔即可。 了解FF扩展的基本文件结构 了解ff扩展的基本结构,也对FF扩展相关的文档有所了解。现在最迫切的是想先创建一个自己的扩展,虽然我们现在还是很了解install.rdf,chrome.manifest文件内是什么东东,但是丝毫不妨碍我们创建一个FF的扩展,因为FF已经给我们提供了一个工具,可以生成自己的扩展,作为新手我强烈建议创建一个这样的扩展包,学习FF的扩展目录结构和文件内容。创建ff扩展的链接https://addons.mozilla.org/en-US/developers/tools/builder,访问这个页面,按上面说明每项参数填写即可,和其他网站一样,星号是必填的。填写完成后,点击底部按钮,就会创建你的第一个FF扩展,然后下载下这个zip包,把zip后者,修改成xpi,然后拖到你FF中去,安装,重启,看看你的第一个ff扩展吧,为了看到效果,建议你把这一串属性全都勾选上,这样你可以方便的看到插件所能展现的效果了。 没有写一行代码,第一个FF扩展已经完成了。接下来,我们仔细研究下FF扩展的文件吧 Install.rdf文件,install.rdf文件是FF扩展的安装文件,在这个文件配置FF扩展相关的信息。 <?xmlversion="1.0" encoding="UTF-8"?> <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Descriptionabout="urn:mozilla:install-manifest"> <!—扩展的ID,之前的版本都是guid,现在流行方式是个email地址,要确保这个ID在你的电脑上唯一--> <em:id>ugg_xchj@hotmail.com </em:id> <!—2表示是ff扩展,这个数字不要变--> <em:type>2</em:type> <!—FF扩展的名字--> <em:name>u-mytest-name</em:name> <!—FF扩展的版本号--> <em:version>1</em:version> <!—FF扩展的创建者和贡献者-> <em:creator>ugg</em:creator> <em:contributor>ugg </em:contributor> <!—FF扩展的描述-> <em:description>u-description</em:description> <!—FF扩展的关于部分,可以是一个网址,也可以是扩展内的一个窗口个窗口-> <em:aboutURL>chrome://u-packagename/content/about.xul</em:aboutURL> <!—FF扩展的选项部分,可以是一个网址,也可以是扩展内的一个窗口个窗口,选项部分和扩展内defaults\preferences下的文件有关系,可以通过这块设置一些变量的默认值-> <em:optionsURL>chrome://u-packagename/content/options.xul</em:optionsURL> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!—Firefox,这个guid不能变,表示的是FF--> <em:minVersion>0.3</em:minVersion> <!—最低的支持版本--> <em:maxVersion>8.0a1</em:maxVersion> <!—最高的支持版本--> </Description> </em:targetApplication> </Description> </RDF> 除了这些参数外,install.rdf还有其他的参数,详细的可以从https://developer.mozilla.org/en/install_manifests了解chrome.manifest文件 content u-na chrome/content/ overlay chrome://browser/content/browser.xul chrome://u-na/content/ff-overlay.xul 定义u-na包的content路径,使用插件中的ff-overlay.xul覆盖浏览器中browser.xul。 Chrome.manifest作用不仅仅如此,还可以定义很多内容,关于这个文件的详细描述可以参考https://developer.mozilla.org/en/chrome_registration Xul文件 XUL(XML用户界面语言)是Mozilla的基于XML的语言,可以快速构建应用程序的界面。Mozilla的界面元素有很多比如状态栏,工具栏,按钮等等,需要设置xul才能更改或者控制FF的界面,关于xul的详细介绍参考https://developer.mozilla.org/en/xul。我们可以参考下状态栏的xul控制 <statusbarid="status-bar"> <statusbarpanelid="dcdingcan" label="登陆中...." 右下角状态栏显示信息 context="stockmenu" onclick="DingCan.disPlay('')"点击后触发事件,这个事件有js控制 /> </statusbar> 以上都是最简单的内容,如果要做出花哨需要详细查看MDN文档。有了这些基本知识后,我们回到我之前的需求上。我希望做一个FF扩展,在这个扩展中可以设置登录用户的账号和密码,当打开FF后,按给定的用户名和密码登录公司订餐网站,执行成功或者失败后在状态下给出提示。最终实现,设置域账号密码 打开ff后的状态显示,, 代码下载,点击此处 实现功能很简单,代码也比较简单,就不做详细介绍了,大概说下我开发过过程碰到的一些问题。 Q:开发过程中的插件如何在FF中调试? A:还记得install.rdf中的<em:id>ugg_xchj@hotmail.com</em:id>字段嘛?在你的电脑C:\Documentsand Settings\用户名\Application Data\Mozilla\Firefox\Profiles\dqfrxoai.default\extensions的目录内创建名字为ugg.xchj@hotmail.com的文件,文件内容为正在开发这个FF扩展的绝对路径,比如D:\m\ffex\myffex\helloword。然后重启FF即可工具-》附件组件管理器中看到你的插件。 Q:为什么我创建的扩展不能加载到FF中 A:主要有几方面,1:确保install.rdf,chrome.manifest文件格式正确,最好的办法是直接修改已有的FF扩展包的install.rdf,chrome.manifest文件,一般这两个文件出错,FF在加载扩展过程会中断,不能再加载其他的组件。2:编码问题,一旦你的文件中出现中文,一定要确保采用utf-8格式保存文件,否则会加载失败,扩展不显示,建议所有的文件都采用utf-8格式。 Q:如果打包xpi文件? A:使用winrar或者winzip打包zip,修改成xpi格式即可。注意,要在包含有install.rdf的文件目录打包,否则会提示xpi包已损坏,不能安装,另外压缩方式选择为存储。 Q:js代码在FF扩展开发有何用处? A:FF扩展开发过程中,使用xul开发FF的扩展的界面,而使用js开发扩展的处理逻辑,在我提供的订餐插件中,使用js登陆网站,执行操作。Js处理逻辑很重要,我使用已经写好的js文件,很容易开发出google的扩展插件,IE的扩展插件。所以,浏览器扩展的开发,更多的是js逻辑的开发。 学习FF扩展,最佳的方式是多看看别人写的FF扩展,下载下别人的扩展,把xpi修改成zip,直接解压看里面的结构,开发就可以,学习过程很快。另外一个好出去是FF的开发社区,多看下MDN文档,比如下面的就是官方创建一个FF扩展的过程。https://developer.mozilla.org/en/Building_an_Extension 参考资料: 开发你的第一个FF插件 http://blog.osqdu.org/develop-your-first-firefox-plugin_129.shtml Install,manifest文件字段说明https://developer.mozilla.org/en/install_manifests 创建一个FF插件 https://developer.mozilla.org/en/Building_an_Extension
Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE MicrosoftInternetExplorer4 鲜为人知的编程真相 当程序员的经历让我知道了一些关于软件编程的事情。下面的这些事情可能会让朋友们对软件开发感到惊讶: 一个程序员用在写程序上的时间大概占他的工作时间的10-20% ,大部分的程序员每天大约能写出10-12 行的能进入最终的产品的代码 — —不管他的技术水平有多高。 好的程序员花去90% 的时间在思考、研究和实验,来找出最优方案 。差的程序员花去90% 的时间在调试问题程序、盲目的修改程序,期望某种写法能可行。 ”一个卓越的车床工可以要求比一个一般的车床工多拿数倍高的工资,但一个卓越的软件写手的价值会10000 倍于一个普通的写手。“ — — 比尔 盖茨 一个好的程序员的效率会是一个普通的程序员的十倍之上。一个伟大的程序员的效率会是一个普通程序员的20-100 倍。这不是夸张 — — 1960 年以来的无数研究都一致的证明了这一点。一个差的程序员不仅仅是没效率 — — 他不仅不能完成任务,写出的大量代码也让别人头痛的没法维护。 伟大的程序员只花很少的时间去写代码 — — 至少指那些最终形成产品的代码。那些要花掉大量时间写代码的程序员都是太懒惰,太自大,太傲慢,不屑用现有的方案去解决老问题。伟大的程序员的精明之处在于懂得欣赏和重复利用通用模式。 好的程序员并不害怕经常的重构(重写)他们的代码以求达到最好效果 。差的程序员写的代码缺乏整体概念,冗余,没有层次,没有模式,导致很难重构。把这些代码扔掉重做也比修改起来容易。 软件遵循熵的定律,跟其它所有东西一样。持续的变更会导致软件腐烂,腐蚀掉对原始设计的完整性概念。软件的腐烂是不可避免的,但程序员在开发软件时没有考虑完整性,将会使软件腐烂的如此之快,以至于软件在还没有完成之前就已经毫无价值了。软件完整性上的熵变可能是软件项目失败最常见的原因。(第二大常见失败原因是做出的不是客户想要的东西。)软件腐烂使开发进度呈指数级速度放缓,大量的软件在失败之前都是面对着突增的时间要求和资金预算。 2004 年的一项研究表明大多数的软件项目(51% )会在关键功能上失败,其中15% 是完全的失败。这比1994 年前有很大的改进,当时是31% 。 尽管大多数软件都是团体开发的,但这并不是一项民主的活动。通常,一个人负责设计,其他人负责实现细节。 编程是个很难的工作。是一种剧烈的脑力劳动。 好的程序员7 ×24 小时的思考他们的工作 。他们最重要的程序都是在淋浴时、睡梦中写成的。因为这最重要的工作都是在远离键盘的情况下完成的,所以软件工程不可能通过增加在办公室的工作时间或增加人手来加快进度。
上周末公司组织q2总结会,总结会的最后是一个游戏:把参加总结会的人分成6个组,每个组大概是个人,然后给每个小组发一些报纸和胶带,要求每个小组在10分钟内,搭建一个纸塔,搭的高的即获胜。当然,纸塔最后要自己站着,不能依靠第三方的东西辅助立着。 这是个很意义的题目,首先每个团队是临时组建的,第一步要选择一个组长,然后出设计方案,实施,最后完成整个纸塔的搭建,在整个塔的构建过程中,最重要的是有时间限制,所以,选组长,设计方案,实施每个步骤必须快,来不及仔细考虑就要做出决定。所以,最后搭建的纸塔,多数以电视塔形状居多,底座非常大,到半腰的时候,就剩下单出的一个纸棍了,傲立空中,不过对大多数纸塔来说,这个纸棍始终站不起来,因为根基太浅了。 很可惜,我参与这个纸塔游戏的时候,已经快结束,经过10分钟的比赛,大家的纸塔很高,但是几乎没有自力更生站着的。进过初步筛选,组织决定再次给每个小组5分钟(其实从开始就是设计的10+5分钟的方式),用来完善自己的纸塔。这个过程,我是完全参与其中的,不过碰到的问题重重,先是大家觉得这个纸塔不牢固,想一拨人完善这个纸塔,另外一拨人重新搭建一个纸塔,在完善的过程发现报纸不多了,胶带也很少了,大家唯一要做的就是卷直筒,把弯的,断的地方重新固定,要么是底座加固,再看其他小组,面临和解决的问题基本如此。 15分钟过去了,在人工干预下,大部分纸塔都站起来了。讲师当然要挨个点评一下,并结合我们开发所面临的项目逐个点评,在时间资源有限的情况下,如何保证进度,并联想我们项目如何做到,小步快跑做好项目。其实在游戏后期,我已经注意到这个问题,只是联想到的情况不是讲师灌输给我们的思想,而是由于项目在时间紧,资源有限的情况,最后项目很猥琐的上线,就如同人工干预下的纸塔一样,稍微有点风,就可以把纸塔吹到。所以,要想有个正确的思想认识,就必须搭建一个完整的纸塔来。 那么反过来,我们认真自考一下,如何搭建一个稳定,可移植性,高的纸塔?所以,一定要做一个结实的底座,三角形最稳,所以底座做成三角性(图1)。移植性,要求我们必须能方便移动,收放自如,对于三角形来说,我可以做个可闭合的三角形(图2),那么高度,我们要突破传统,做一个从低到上都是都是三角形纸塔(图三)。 (图1) (图2) (图三) 基于以上设计,需要高度合作的一个团队才能完成,并且在最后的移动部署过程中,需要开发辅助工具完成,如果你的纸塔不涉及到移动,可以采用另外一种方式,逐级向上增加如下。 具体到那种设计方式,可以根据我们游戏的限制,做些适应,总的设计方案不便。那么我们看看10个人如何在10分钟内,完成一个3.5米高塔的搭建。这里我们有个前提,你是组长,明确你要做的设计方案,在这个团队中不会出现多头领导的问题。OK,那我们开始 1:从开头起你要准备一块自己的手表,时刻关心时间,根据时间做好人员调动。 2:招集你的队员,讲解设计方案,用上纸和笔在1分钟内讲完,在这一分钟讲的过程中,组员不要闲着,每人负责卷一个小拇指粗细的直筒。 3:讲解完你的设计方案后,你已经得到9个纸筒,开始任务分解。两名同学负责提供胶带,并把胶带做成可以寸长,方便其他成员把纸筒粘好。这个过程大概需要30秒 4:另外两名同学,负责把已经粘好的纸筒,连接粘成一个长纸筒,我们大概每张报纸卷成的纸筒长50cm,做3.5M高度的纸筒需要,7个短纸筒连接而成。2个人,粘一个长纸筒的时间大概是2分钟。其他没有领导新任务的同学,继续卷直筒。 到达这一步,你的团队应该每个人都分配到任务,2名同学负责胶带,2名同学负责做长纸筒,而组长的你不要参与任何具体工作步骤,时刻看着表,关注各个环节的进度,那个环节慢了,调整每个环节。 根据计算公事,我们大概需要350(高度)/50(每个纸筒的高度)*3(条边)+2*3(底座,要牢靠些)=27个报纸小纸筒,再加上我们做两个工具+4+2个小纸筒,我们总共需要33个直筒,考虑到牢固性,使用过程的损害,40个小纸筒肯定能满足我们的需求。40个直筒,需要花费直接计算,每个纸筒需要1分钟,40个纸筒共计40分钟,我们一共有10*10=100分钟的时间资源,根据我们之前的安排, 9个纸筒在1分钟内完成。 之后是5个人卷纸筒,应该会在6分钟内完成其他31个纸筒。 而另外两名同学也会在6分钟的时候,完成三个3.5长纸筒,根据计算得到不会出现。 5:到第五步的时候,时间应该过去7分钟,我们应该已经具有3个长纸筒,还有些未用的纸筒,我们再进行任务划分。原来两名做长纸筒的同学,现在使用纸筒做工具,辅助把纸塔树立起来,胶带同学的任务不便。原卷直筒的同学,抽出4名同学做根据设计方案做纸塔。另外一名同学机动。 6:第9分钟,纸塔已经粘好,帮忙竖起来的工具应该做好了,现在10个人,一块辅助把纸塔树立起来。 7:第10分钟,树立纸塔,完成。具体部署看这里 在树立纸塔的过程,可能会碰到纸塔拦腰折断的情况,不过没关系,我们通过辅助工具把纸塔树立起来,然后把纸塔打开成一个三角体,即可成为最稳定的结构,折断的地方也可以树立起来。 后继,我们移动纸塔也很方便,收起来,在树立就可以。当然如果想加高纸塔一样方便,从底层增加三角体就可以完成增高。 这是我认为最有效的一种叠纸塔的方式,和传统方式叠纸塔的对比,这种方式的优势是 1:精准设计,在叠纸塔之前,需要有个专人负责整体的设计,需仔细考虑每个步骤,传统方式也会有设计,只是设计的不充分,考虑不周不周全。 2:明确分工,以资源为基础,充分利用每个人的每块时间,传统的设计方式,基本上无分工,需要什么的时候,大家一块做而已。 3:执行力,需要团队成员紧密配合,并且在关键点上要有双重备份,比如纸带是两个人,短纸筒做成长纸筒也是两个人,叠纸筒是5个人。传统方式,总是会发现有些人累死,有些人闲死。 4:信念,对于纸塔能够成功,重要的一点是其他9个人认可这种设计方案,并且相信能够成功。不然的话设计方案再好,资源分配再合理,也会因为信念不足,而导致执行力下降,最后无法完成。传统方式由于设计不充分,分工不明确导致大家没有信念不足,从而使执行力不足,导致整个过程窝工。 5:管理,管理真的是门科学,在这个项目中不会因为组长干的多就能成功,而在于组长能够充分发挥其才能,合理调配资源,并严格把握时间,按时间逐步推进工作。而传统方式,根本设计不到管理,一般情况是组长带头干活而已。 6:才能,我们可以看到在一个临时组队的团队中,在彼此不了解的情况下,才能是最重要的,也是事情成功的主要因素,才能不仅仅包括精准的设计,还包括独特的说服能力,还包括很强的前瞻性。而传统模式基本上属于漠视才能的范畴。 那么纸塔放到我们项目开发中,其实优秀的纸塔就如同优秀的项目,我们开发中100%的项目都属于那种摇摇欲坠的纸塔,其原因就是缺少有才能的人。 远一点说,在纸塔这个项目中,除了组长实现自己的抱负,完成纸塔外,其他9个人其实都是可以替代的(这是项目开发的最高境界),也就是说从事平凡的工作,这种工作磨灭的人的创造性和才能,对于人才的培养也是很不利的。纸塔的现实例子就是日企外包,做一个很NB的设计,找一群外包来实现。
IPhone 的toolbar 和 tabbar 的图标资源 http://www.glyphish.com/ 提供130个图标。非常好的资 源。 原文链接: http://www.cocoachina.com/bbs/read.php?tid-13540.html
转眼到3月4号,就是工作6周年了,这个周年,相比和从前一样,没有鲜花没有礼物,静悄悄的过。但是工作6年,内心并不平静。 发现自己对于第一次工作的日期记的特别清楚,也许是因为从学生到社会的转变对自己的影响刻骨铭心,而对后几次的工作变化反而没有什么特殊的感情。 想想刚毕业那会,心比天高,虽然挣得钱少,但是依然努力奋斗,精力充沛。而现在越来越感觉自己颓废了,工作上再也没有从前的冲劲了,体重倒是无情的上去了,有时候想想,这是自己要的生活嘛?每次回答都是NO,想想原因是什么。 之前呆过的公司,不大,百十人。所以,很容易出人头地,而现在的公司几千人,突然感觉自己很渺小,周围都是一些优秀的人。竞争压力很大,也很容易让人疲惫,也许你工作几年下来,还是原来的样子,如果仅仅满足这些,我想人也是幸福的。但是我不想这样,也许在小公司里更能体现价值。 工作前三年,碰到的主要问题是,技术问题,没有经验,没有技术,多么渴望搞好技术,做不到顶尖至少能搞懂技术,于是给自己顶了一个目标,做三年纯粹的技术,于是辞去csdn版主,切断尽可能多的与外界交流,专心做工作,三年的时间基本上已经过去了。我到发现自己,在技术上做的不纯粹,而别的方面也没有进展。 现在面临的问题,已经不是技术问题,而是人生方向的问题了。在技术上做到顶尖真的会很难,以我的智商来看,这一点很难做到了。而目前中国的环境是不会有一个人做三年的的纯粹技术的,所以,我这三年,在技术倒是真的没有太多建树,倒是对项目啊,人员管理的黑暗面有所了解,虽然不敢说多深,但是至少不像3年前想的那么简单了。 我想6年对于大多数人来说,同样也是一个困惑的年头。之前的工作已经熟练,而以后的发展也看不太清楚,比毕业时更加迷茫。我也真该好好想想自己的前途和未来。
开源本身就是一种释放源代码,共享的精神。所以在这种精神指导下,做开源的,大部分是个人或者社团组织的形式。随着开源越来越被大家所认可,有些公司也在开始介入开源这块。介入的方式有很多种 比如google对mozilla支持,通过投入资金,加自己的搜索条。 还有比如google自己开发android,开源系统。 也许是有了google这样的大公司做开源,现在一些公司也在做开源,思想就不那么单纯了。 大部分打着开源的旗号,推广产品,或者出名,比如前几年的360开源事件。 当然也有靠开源发家的,那就是康盛创想依靠discuz论坛,还有比如phpwind,ecshop等等,都是靠一两款开源软件发家,然后成功转向商业应用。 如果一切方向到过来,公司很成功,为了推广某项产品而开源,结果会怎么样?360就是一个很好的说明,公司的既定行为是不会通过开源再来培养一个自己的竞争对手的,这就是说,公司做开源肯定是不会做好的。除非公司想繁荣这个行业,先期可以投入大量的资金,像google一样,但是全球有多少google。 所以,要问以公司名义做开源路能走多远?不远,最长不会超过1年。
牛人是什么样的? 我一直没有看到过牛人?想知道?谁能告诉我。