MyException - 我的异常网
当前位置:我的异常网» XML/SOAP » ASIHTTPRequest跟libxml结合,实现边请求边解析

ASIHTTPRequest跟libxml结合,实现边请求边解析

www.MyException.Cn  网友分享于:2015-08-26  浏览:49次
ASIHTTPRequest和libxml结合,实现边请求边解析
ASIHTTPRequests 是非常强大的 http 异步请求开源框架,libxml 是非常老牌的 C 语言xml函数库。在 http + xml 文件的 javaEE-iPhone 应用中,如何把二者结合起来,实现在异步请求数据的同时,进行xml的同步解析呢?

这涉及到 3 方面的关键知识:

¥  ASIHTTPRequest

这部分的内容可以参考作者另一篇博文《ASIHTTPRequest的使用》。

¥  NSOperation 和 Libxml

这部分内容在作者的一篇博文《使用NSOperation实现异步下载》中也有介绍。

背景知识已经具备,下面让我们继续。

一、准备libxml环境

libxml2 是一个开放源码库,默认情况下iPhone SDK 中已经包括在内。 它是一个基于 C 的 API,所以在使用上比 cocoa 的   NSXML 要麻烦许多(一种类似 c 函数的使用方式),但是该库同时支持 DOM 和 SAX 解析,其解析速度较快,而且占用内存小,是最适合使用在 iphone 上的解析器。 从性能上讲,所有知名的解析器中,TBXML 最快,但在内存占用上,libxml 使用的内存开销是最小的。因此,我们决定使用 libxml 的sax接口。

首先,我们需要在 project 中导入 framework:libxml2.dylib。

虽然 libxml 是 sdk 中自带的,但它的头文件却未放在默认的地方,因此还需要我们设置 project 的 build 选项:HEADER_SEARCH_PATHS = /usr/include/libxml2,否则 libxml 库不可用。

然后,我们就可以在源代码中 #import <libxml/tree.h> 了。

至于 ASIHTTPRequest 的使用环境,请参考 《ASIHTTPRequest的使用》 进行。

二、线程管理

首先,我们肯定要使用线程来进行实现。多线程的操作使用NSOperation子类。

新建 o-c class,命名为 SyncRequestParseOperation ,它必需继承NSOperation。

我们决定不使用继承而使用聚合来让它同时具有 ASIHTTPRequest 和 Xml 解析的功能,因此我们导入了libxml/tree.h 和 ASIHTTPRequest.h 。

由 于服务器使用了GBK 编码,所以我们也使用了 NSStringEncoding 。 kRequestStatus 定义了一个枚举,用来表 示 SyncRequestParseOperation 的不同状态:请求完毕、请求失败、收到数据包。这3种可能状态会被成员变量 status 使用,实际上它是个int。头文件定义如下:

#import <libxml/tree.h>

#import "BaseXmlParser.h"

#import "ASIHTTPRequest.h"

enum kRequestStatus {

kRequestStatusFinished ,

kRequestStatusFailed ,

kRequestStatusDataReceived

};

@interface SyncRequestParseOperation : NSOperation

{

NSURL * _url ;

NSDictionary * _data ;

// 构建 gb2312 的 encoding

NSStringEncoding enc ;

//Xml 解析器指针

xmlParserCtxtPtr _parserContext ;

BaseXmlParser * baseParser ;

id delegate , progressDelegate ;

int status ;

}

@property ( nonatomic , retain ) NSDictionary *data;

@property ( nonatomic , retain ) NSURL *url;

@property ( assign ) int status;

- ( id )initWithURLString:( NSString *) url xmlParser:( BaseXmlParser *) parser delegate:( id )obj;

-( void )setProgressDelegate:( id )progress;

-( void )statusChangedNotify;

@end

 

BaseXmlParser 是一个Xml解析器的基类,我们使用它的子类来进行Xml解析,在其中定义了一些使用 libxml 时特有的结构体和函数声明。有了它,我们就可以在其子类中覆盖某些方法来解析各种不同的XML 文件。

BaseXmlParser 及其子类我们后面会介绍。

delegate和 progressDelegate 保存两个对象的 id 引用。前者是负责响应 SyncRequestParseOperation 类的一些特殊的通知消息,比如某个状态的改变;后者负责根据收到的数据实时进行进度显示。

接下来我们看实现,首先是初始化 init 方法:

initWithURLString :xmlParser: delegate:( id )obj 方法是个便利的初始化方法,分别对3个成员进行初始化,而不必要对它们一一调用setter方法:http请求地址url、解析器、通知消息的委托对象。

- ( id )initWithURLString:( NSString *) url xmlParser:( BaseXmlParser *)parser delegate:( id )obj{

if ( self = [ super init ]) {

_url =[[ NSURL alloc ] initWithString : url ];

delegate =obj;

baseParser =[parser retain ];

// 构建 gb2312 的 encoding

enc = CFStringConvertEncodingToNSStringEncoding ( kCFStringEncodingGB_18030_2000 );

}

return self ;

}

除了init方法外,我们也提供了 setProgressDelegate 方法:

-( void )setProgressDelegate:( id )progress{

progressDelegate =progress;

}

用于对 progressDelegate 进行初始化。

接下来是最主要的部分,NSOperation的生命周期方法:

#pragma mark NSOperation 的生命周期方法

// 开始线程 - 本类的主方法

- ( void )start {

NSLog ( @"operation start!" );

if (![ self isCancelled ]) {

// 创建 XML 解析器指针

          _parserContext = xmlCreatePushParserCtxt (& _saxHandlerStruct , baseParser , NULL , 0 ,NULL );

// 以异步方式处理事件,并设置代理块

__block ASIHTTPRequest *request = [ ASIHTTPRequest requestWithURL : _url ];

// 设置进度代理

if ( progressDelegate != nil ) {

[request setDownloadProgressDelegate : progressDelegate ];

}

 

// 使用 complete 块,在下载完时做一些事情

[request setCompletionBlock :^( void ){

[ self setStatus : kRequestStatusFinished ];

NSLog ( @"request completed!" );

// 添加解析数据(结束),注意最后一个参数 terminate

xmlParseChunk ( _parserContext , NULL , 0 , 1 );

// 添加解析数据(结束),

if ( baseParser != nil ){

[ self setData :[[ baseParser getResult ] copy ]];

} else {

NSLog ( @"baseparser is nil" );

}

 

// 释放 XML 解析器

if ( _parserContext ) {

xmlFreeParserCtxt ( _parserContext ), _parserContext = NULL ;

}

[ self statusChangedNotify ];

}];

// 使用 failed 块,在下载失败时做一些事情

[request setFailedBlock :^( void ){

[ self setStatus : kRequestStatusFailed ];

NSLog ( @"request failed !" );

// 释放 XML 解析器指针

if ( _parserContext ) {

xmlFreeParserCtxt ( _parserContext ), _parserContext = NULL ;

}

[ self statusChangedNotify ];

}];

// 使用 received 块,在接受到数据时做一些事情

[request setDataReceivedBlock :^( NSData * data ){

[ self setStatus : kRequestStatusDataReceived ];

NSLog ( @"received data:%d" , data . length );

// 添加解析数据(结束),注意最后一个参数 terminate

if ( baseParser != nil && baseParser != NULL ){

[ self setData :[[ baseParser getResult ] copy ]];

} else {

NSLog ( @"baseparser is nil" );

}

// 使用 libxml 解析器进行 xml 解析

xmlParseChunk ( _parserContext , ( const char *)[ data bytes], [ data length ], 0 );

[ self statusChangedNotify ];

}];

[request startAsynchronous ];

}

}

// 停止线程

- ( void )cancel

{

[ super cancel ];

}

对 于一个NSOperation 来说,最主要的是start 方法,因为线程在这里启动。由于使用了 ASIHTTPRequest 的异步方式,所以在start方法中我们没有使用 NSRunLoop 循环( 这个问题参考http://www.cocoabuilder.com /archive/cocoa/279826-nsurlrequest-and-nsoperationqueue.html )。因为 ASIHTTPRequest 的startAsynchronous 方法提供了额外的线程。我们在 start 方法中使用了一个ASIHTTPRequest ,利用 BaseXmlParser 解析器来提供一系列符合 libxml 规范的回调函数,以响应 sax 解析事件。当然,由于我们要实现“边接收数据,边解析Xml”的目的,我们在 ASIHTTPRequest 的三个委托块中,就对数据进行了处理(使用 libxml 的函数)。

比较怪异的是对 ASIHTTPRequest 的3个事件委托中使用了块语法,块语法介绍可以参考作者另一篇(翻译)博文《块编程指南》。

为了把3个委托事件通知给delegate,我们需要在3个事件委托块中调用delegate 的相应方法:

// status 状态变化通知

-( void )statusChangedNotify{

if ( delegate != nil ) {

SEL sel= NSSelectorFromString ( @"syncRequestParseStatusNofity:" );

if ([ delegate respondsToSelector :sel]){

[ delegate performSelector :sel withObject : self ]; // 注意冒号说明带 1 个参数

}

}

为了简便,我没有定义新的协议,而只是使用方法名 syncRequestParseStatusNofity: 作为内部协议。如果delegate要想接收通知,就必需实现该方法。作为一种技巧,其中使用了反射机制,避免运行时错误。

 

三、Sax 异步解析

libxml 是C函数库,其中很多函数需要使用令人生畏的结构体定义。为了便于扩展,这些定义被放到了 BaseXmlParser 类中:

#import <Foundation/Foundation.h>

#import <libxml/tree.h>

@interface BaseXmlParser : NSObject {

NSStringEncoding enc ;

NSMutableDictionary *    _root ;

}

// Property

- ( void )startElementLocalName:( const xmlChar *)localname

   prefix:( const xmlChar *)prefix

  URI:( const xmlChar *)URI

nb_namespaces:( int )nb_namespaces

   namespaces:( const xmlChar **)namespaces

nb_attributes:( int )nb_attributes

  nb_defaulted:( int )nb_defaultedslo

   attributes:( const xmlChar **)attributes;

- ( void )endElementLocalName:( const xmlChar *)localname

  prefix:( const xmlChar *)prefix URI:( const xmlChar *)URI;

- ( void )charactersFound:( const xmlChar *)ch

len:( int )len;

-( NSDictionary *)getAtributes:( const xmlChar **)attributes withSize:( int )nb_attributes;

-( NSDictionary *)getResult;

@end

//3 个静态方法的实现,其实是调用了 ctx 的成员方法,其中 ctx 在 _parserContext 初始化时传入

static void startElementHandler(

void * ctx,

const xmlChar * localname,

const xmlChar * prefix,

const xmlChar * URI,

int nb_namespaces,

const xmlChar ** namespaces,

int nb_attributes,

int nb_defaulted,

const xmlChar ** attributes)

{

     [( BaseXmlParser *)ctx

  startElementLocalName :localname

  prefix :prefix URI :URI

  nb_namespaces :nb_namespaces

  namespaces :namespaces

  nb_attributes :nb_attributes

  nb_defaulted :nb_defaulted

  attributes :attributes];

}

 

static void endElementHandler(

  void * ctx,

  const xmlChar * localname,

  const xmlChar * prefix,

  const xmlChar * URI)

{

     [( BaseXmlParser *)ctx

  endElementLocalName :localname

  prefix :prefix

  URI :URI];

}

 

static void charactersFoundHandler(

   void * ctx,

   const xmlChar * ch,

   int len)

{

    [( BaseXmlParser *)ctx

  charactersFound :ch len :len];

}

//libxml 的 xmlSAXHandler 结构体定义,凡是要实现的 handler 函数都写在这里,不准备实现的用 null代替。一般而言,我们只实现其中 3 个就够了

static xmlSAXHandler _saxHandlerStruct = {

    NULL ,              /* internalSubset */

    NULL ,            /* isStandalone   */

    NULL ,            /* hasInternalSubset */

    NULL ,            /* hasExternalSubset */

    NULL ,            /* resolveEntity */

    NULL ,            /* getEntity */

    NULL ,            /* entityDecl */

    NULL ,            /* notationDecl */

    NULL ,            /* attributeDecl */

    NULL ,            /* elementDecl */

    NULL ,            /* unparsedEntityDecl */

    NULL ,            /* setDocumentLocator */

    NULL ,            /* startDocument */

    NULL ,            /* endDocument */

    NULL ,            /* startElement*/

    NULL ,            /* endElement */

    NULL ,            /* reference */

    charactersFoundHandler, /* characters */

    NULL ,              /* ignorableWhitespace */

    NULL ,            /* processingInstruction */

    NULL ,            /* comment */

    NULL ,             /* warning */

    NULL ,            /* error */

    NULL ,              /* fatalError //: unused error() get all the errors */

    NULL ,              /* getParameterEntity */

    NULL ,            /* cdataBlock */

    NULL ,            /* externalSubset */

    XML_SAX2_MAGIC ,  /* initialized 特殊常量,照写 */

    NULL ,            /* private */

    startElementHandler,    /* startElementNs */

    endElementHandler,      /* endElementNs */

    NULL ,              /* serror */

};

 

在 BaseXmlParser 类的头文件中,可以分为两部分。

1.    第一部分是 interface 定义,定义了BaseXmlParser类的成员,包括:

¥  成员变量

enc:基于和前面同样的原因,用于定义GBK编码。

_root:一个Dictionary,用于保存解析后Xml对象,一个xml文档只有一个root 元素,因此用一个Dictionary对象即可。

¥  成员方法

libxml 回调方法:前3个很像是C语言函数的方法其实都是被libxml回调的,它们会在3个静态函数(在第二部分)中调用。

getAttributes方法:这个是一个方便的获取 xml 元素属性的方法。由于本例中的 XML 文档大量使用了属性,所以这个方法很实用。

getResult方法:用于获得 XML 文档解析结果,即 _root 对象。

 

2.    第二部分是 libxml 回调函数和结构体定义,包括:

¥  回调函数

本例我们决定实现3个回调函数,分别用于响应 Sax 解析中的3个事件:

处理 XML 元素开始标记、处理 XML 元素结束标记、处理 XML 元素体。

为了更 OO 一些,我们没有直接在这 3 个函数中写对应的 XML 解析代码,而是调用了类的成员方法进行处理。这样,我们可以在 implement 部分写入具体的代码。

¥  结构体

只 需要填充一个结构体 xmlSAXHandler 即可。这个结构成员数量众多(31个),但我们只需填充你要实现的几个。例如,我们要实现3个回调函 数,那么只消在对应的地方填充这3个函数名即可(此外有一个特殊的成员叫XML_SAX2_MAGIC ,你照填就是了)。为了便于大家理解这些成员所代 表的意义,我们也在旁边做了注释,你可以对照着看。

 

接下来是implement (实现)。

#import "BaseXmlParser.h"

 

@implementation BaseXmlParser

// Property

-( id )init{

if ( self =[ super init ]){

// 构建 gb2312 的 encoding

enc = CFStringConvertEncodingToNSStringEncoding ( kCFStringEncodingGB_18030_2000 );

_root =[[ NSMutableDictionary alloc ] init ];

}

return self ;

}

-( void )dealloc{

[ _root release ], _root = nil ;

[ super dealloc ];

}

// 一个便利方法,用于获取元素的属性值

-( NSDictionary *)getAtributes:( const xmlChar **)attributes withSize:( int )nb_attributes{

NSMutableDictionary * atts=[[ NSMutableDictionary alloc ] init ];

NSString *key,*val;

for ( int i= 0 ; i<nb_attributes; i++){

key = [ NSString stringWithCString :( const char *)attributes[ 0 ] encoding : NSUTF8StringEncoding];

val = [[ NSString alloc ] initWithBytes :( const void *)attributes[ 3 ] length :(attributes[ 4 ] - attributes[ 3 ]) encoding : NSUTF8StringEncoding ];

[atts setObject :val forKey :key];

[key release ],[val release ];

attributes += 5 ; // 指针移动 5 个字符串,到下一个属性

}

return atts;

}

 

//--------------------------//

#pragma mark -- libxml handler ,主要是 3 个回调方法 , 空方法,等待子类实现 --

//--------------------------//

// 解析元素开始标记时触发 , 在这里取元素的属性值

- ( void )startElementLocalName:( const xmlChar *)localname

   prefix:( const xmlChar *)prefix

  URI:( const xmlChar *)URI

nb_namespaces:( int )nb_namespaces

   namespaces:( const xmlChar **)namespaces

nb_attributes:( int )nb_attributes

  nb_defaulted:( int )nb_defaultedslo

   attributes:( const xmlChar **)attributes

{

}

// 解析元素结束标记时触发

- ( void )endElementLocalName:( const xmlChar *)localname

  prefix:( const xmlChar *)prefix URI:( const xmlChar *)URI

{

}

// 解析元素体时触发

- ( void )charactersFound:( const xmlChar *)ch

len:( int )len

{

}

// 返回解析结果

-( NSDictionary *)getResult{

return _root ;

}

@end

 

可 以看到,除了 getAttributes 和  getResult 方法外,我们都没有进行其它方法的实现。这是因为 Sax 解析跟 Dom 解析不同,针对不同的 XML 文档很难使用相同的逻辑解析,因此我们准备把剩下的内容留给子类来实现,这样不同的XML 文档可以通过不同的子类来进行解析,而不用在每个子类中都写一遍那些怪异的 C 回调函数和结构体声明。

我们要解析的 XML 文档可能是这样的:

<root>

<List Name="同事">

<user name="t2" phone="13884831140"/>

<user name="t3" phone="15877103548"/>

<user name="t1" phone="13399459990"/>

</List>

<List Name="好友">

<user name="f2" phone="13828831140"/>

<user name="f3" phone="15886103548"/>

<user name="f1" phone="13019459990"/>

</List>

</root>

也就是说,这是一个通讯录类似的东西。通讯录把电话号码按性质分成不同的组,就像Windows mobile智能手机上的的通讯录,把电话号码按“家庭”、“好友”、“同事”等进行划分。

我们新建一个 BaseXmlParser的子类 TelNoXmlParser ,让这个 TelNoXmlParser 去实现 3 个回调方法:

#import <Foundation/Foundation.h>

#import <libxml/tree.h>

#import "BaseXmlParser.h"

@interface TelNoXmlParser : BaseXmlParser {

BOOL loginSuccess ;

NSMutableArray * groups ,* members ;

NSMutableDictionary * _group ;

NSDictionary * _user ;

}

@end

#import "TelNoXmlParser.h"

 

 

@implementation TelNoXmlParser

 

-( id )init{

if ( self =[ super init ]) {

// 一个 groups 数组,代表了所有 List

groups =[[ NSMutableArray alloc ] init ];

[ _root setObject : groups forKey : @"items" ];

loginSuccess = YES ;

}

return self ;

}

-( void )dealloc{

[ _group release ], _group = nil ;

[ super dealloc ];

}

//--------------------------//

#pragma mark -- libxml handler ,主要是 3 个回调方法 --

//--------------------------//

// 解析元素开始标记时触发 , 在这里取元素的属性值以及设置标志变量

- ( void )startElementLocalName:( const xmlChar *)localname

   prefix:( const xmlChar *)prefix

  URI:( const xmlChar *)URI

nb_namespaces:( int )nb_namespaces

   namespaces:( const xmlChar **)namespaces

nb_attributes:( int )nb_attributes

  nb_defaulted:( int )nb_defaultedslo

   attributes:( const xmlChar **)attributes

{ // 我们关心 8 个元素标签,所以设置了 8 个标志位

    // login_status

    if ( strncmp (( char *)localname, "login_status" , sizeof ( "login_status" )) == 0 ) {

          loginSuccess = NO ;

        return ;

    }

if ( loginSuccess ) {

// List

if ( strncmp (( char *)localname, "List" , sizeof ( "List" )) == 0 ) {

NSDictionary * atts=[ self getAtributes :attributes withSize :nb_attributes]; // 获取 List 的所有属性

_group =[[ NSMutableDictionary alloc ] init ];

members =[[ NSMutableArray alloc ] init ];

[ _group setObject : members forKey : @"members" ];

[ _group setObject :[[ NSString alloc ] initWithString :( NSString *)[atts objectForKey : @"Name" ]]forKey : @"groupname" ];

[ groups addObject : _group ]; // 把 group 加入数组

return ;

}

// user

if ( strncmp (( char *)localname, "user" , sizeof ( "user" )) == 0 ) {

NSDictionary * atts=[ self getAtributes :attributes withSize :nb_attributes]; // 获取 List 的所有属性

_user =[[ NSDictionary alloc ] initWithDictionary :atts];

[ members addObject : _user ];

return ;

}

}

}

// 解析元素结束标记时触发

- ( void )endElementLocalName:( const xmlChar *)localname

  prefix:( const xmlChar *)prefix URI:( const xmlChar *)URI

{

if ( strncmp (( char *)localname, "root" , sizeof ( "root" )) == 0 ){ //root 结束时置  login_status 标志

if ( loginSuccess ) {

[ _root setObject : @"true" forKey : @"login_status" ];

} else {

[ _root setObject : @"false" forKey : @"login_status" ];

}

}

if ( loginSuccess ) {

// 我们还关心 <List> 的结束标记

if ( strncmp (( char *)localname, "List" , sizeof ( "List" )) == 0 ) {

[ _group release ], _group = nil ; // 回收 _group 对象,以便重复利用

} else if ( strncmp (( char *)localname, "user" , sizeof ( "user" )) == 0 ){

[ _user release ], _user = nil ; // 回收 _user 对象,以便重复利用

}

}

}

// 解析元素体时触发

- ( void )charactersFound:( const xmlChar *)ch

len:( int )len

{      

// 没有元素体需要关心

}

@end

接下来我们看如何在 ViewController 中使用。

 

四、在 UI 中测试

在 ViewController 中放入一个按钮和一个 WebView,当点击按钮时,请求http服务器,获取通讯录XML 数据,并解析为 Dictionary 对象。把解析结果显示在 WebView 中。

这是按钮的 touch up inside 事件代码:

-( IBAction )go{

if ( _queue == nil ){

_queue = [[ NSOperationQueue alloc ] init ];

}

[ button setEnabled : NO ];

[ progress setProgress : 0 ];

[ webView loadHTMLString : @"" baseURL :[ NSURL URLWithString : URL ]];

// 构造 xmlparser

TelNoXmlParser * parser=[[ TelNoXmlParser alloc ] init ];

// 把 self 注册为 delegate ,这样 self 必需实现 syncRequestParseStatusNofity: 方法 , 以接收statusChanged 方法

SyncRequestParseOperation * operation=[[ SyncRequestParseOperation alloc ]

  initWithURLString : URL

  xmlParser :parser

  delegate : self ];

// 把 progress 设置为 progressDelegate ,这样会显示进度

[operation setProgressDelegate : progress ];

[parser release ]; // opertaion 已 retain ,可以 release

[ _queue addOperation :operation]; // 开始处理

[operation release ]; // 队列已 retain ,可以 release ;

}

这是异步消息到达时的处理代码,当数据接收完时,我们把解析结果在WebView 中显示:

// 实现 statusChanged 通知方法

-( void )syncRequestParseStatusNofity:( id )sender{

SyncRequestParseOperation * operation=( SyncRequestParseOperation *)sender;

int status=[operation status ];

NSLog ( @"status:%d" ,status);

if (status== kRequestStatusFinished ){ // 如数据接收完成

[ button setEnabled : YES ];

NSDictionary * d=[operation data ];

[ webView loadHTMLString :[d description ] baseURL :[ NSURL URLWithString : URL ]];

}

}

这是程序运行时 WebView 显示效果:

 

注意,当xml 文档比较大时,WebView 的内容是从上到下逐渐刷新的。

这是控制台输出,可以看到服务器响应的数据是被分成很多次下载的:

文章评论

代码女神横空出世
代码女神横空出世
漫画:程序员的工作
漫画:程序员的工作
程序员应该关注的一些事儿
程序员应该关注的一些事儿
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
10个调试和排错的小建议
10个调试和排错的小建议
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
如何成为一名黑客
如何成为一名黑客
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
那些争议最大的编程观点
那些争议最大的编程观点
程序员必看的十大电影
程序员必看的十大电影
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员的鄙视链
程序员的鄙视链
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员都该阅读的书
程序员都该阅读的书
程序员和编码员之间的区别
程序员和编码员之间的区别
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
中美印日四国程序员比较
中美印日四国程序员比较
Java程序员必看电影
Java程序员必看电影
旅行,写作,编程
旅行,写作,编程
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
 程序员的样子
程序员的样子
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
一个程序员的时间管理
一个程序员的时间管理
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
为什么程序员都是夜猫子
为什么程序员都是夜猫子
我是如何打败拖延症的
我是如何打败拖延症的
每天工作4小时的程序员
每天工作4小时的程序员
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有