在上一篇文章cocos2dx获取网络时间(一)中,我们使用CCHttpClient进行网络时间的数据请求和使用rapidjson解析Http请求得到的json数据 。在文章的最后 ,我留下了一个问题,怎么封装一个类来实现我们的方法。
新建一个c++类 ,命名为NetTime,继承自CCNode并重写init()方法。然后引入CCHttpClient和rapidjson需要的头文件。我们的需求是可以返回NetTime的年,月,日,小时,分和秒,分别定义它们的private字段和public方法:
1 #ifndef __NETTIME_H__ 2 #define __NETTIME_H__ 3 #include "cocos2d.h" 4 #include "cocos-ext.h" 5 #include "CocoStudio\Json\rapidjson\rapidjson.h" 6 using namespace cocos2d; 7 using namespace extension; 8 using namespace rapidjson; 9 10 class NetTime:public CCNode 11 { 12 public: 13 CREATE_FUNC(NetTime); 14 int getYear(){ 15 return _year; 16 }; 17 int getMonth(){ 18 return _month; 19 }; 20 int getDay(){ 21 return _day; 22 }; 23 int getHour(){ 24 return _hour; 25 }; 26 int getMinute(){ 27 return _minute; 28 }; 29 int getSecond(){ 30 return _second; 31 }; 32 void getNetTime(); 33 void requestNetTime(); 34 private: 35 virtual bool init(); 36 int _year, _month, _day, _hour, _minute, _second; 37 void onHttpComplete(CCHttpClient * sender, CCHttpResponse * response); 38 void readJson(std::string jsonStr); 39 }; 40 #endif
我们还需要一个requestNetTime方法来发起http请求,并在回调方法里面实现数据的获取和解析,直接把前面的代码拷贝过来即可:
1 #include "NetTime.h" 2 3 bool NetTime::init() 4 { 5 bool bRet = false; 6 do 7 { 8 CC_BREAK_IF(!CCNode::init()); 9 10 bRet = true; 11 } while (0); 12 return bRet; 13 } 14 15 void NetTime::requestNetTime() 16 { 17 CCHttpRequest * request = new CCHttpRequest(); 18 request->setUrl("http://115.159.3.250:1227/WebTime.svc/"); 19 request->setRequestType(CCHttpRequest::kHttpGet); 20 request->setTag("WebTime"); 21 request->setResponseCallback(this, httpresponse_selector(NetTime::onHttpComplete)); 22 CCHttpClient::getInstance()->send(request); 23 } 24 25 void NetTime::onHttpComplete(CCHttpClient * sender, CCHttpResponse * response) 26 { 27 CCHttpClient::getInstance()->release(); 28 if (!response) 29 return; 30 if (0 != strlen(response->getHttpRequest()->getTag())) 31 { 32 CCLog("%s completed", response->getHttpRequest()->getTag()); 33 } 34 int statusCode = response->getResponseCode(); 35 char statusString[64] = {}; 36 sprintf(statusString, "HTTP Status: %d, tag = %s", statusCode, response->getHttpRequest()->getTag()); 37 CCLog("%s", statusString); 38 if (!response->isSucceed()) 39 { 40 CCLog("response failed"); 41 CCLog("error buffer:%s", response->getErrorBuffer()); 42 return; 43 } 44 std::vector<char> * buffer = response->getResponseData(); 45 std::string str; 46 for (unsigned i = 0; i < buffer->size(); i++) 47 { 48 char a = (*buffer)[i]; 49 str.append(1, a); 50 } 51 CCLog("%s", str.c_str()); 52 readJson(str); 53 } 54 55 void NetTime::readJson(std::string jsonStr) 56 { 57 Document doc; 58 doc.Parse<0>(jsonStr.c_str()); 59 if (!doc.IsObject()) 60 return; 61 if (doc.HasMember("Year") && doc.HasMember("Month") && doc.HasMember("Day") && doc.HasMember("Hour") && doc.HasMember("Minute") && doc.HasMember("Second")) 62 { 63 _year = doc["Year"].GetInt(); 64 _month = doc["Month"].GetInt(); 65 _day = doc["Day"].GetInt(); 66 _hour = doc["Hour"].GetInt(); 67 _minute = doc["Minute"].GetInt(); 68 _second = doc["Second"].GetInt(); 69 } 70 }
看到这里 ,读者可能会忍不住要说了,Leandro真是一个大忽悠,如此简单的类封装都要单独拿出来写一篇!
各位客官莫着急,请接着向下看:
我们回到HelloWorldScene.cpp的init方法,注释掉前面发起网络请求的代码,接着使用我们的NetTime类:
1 /* CCHttpRequest * request = new CCHttpRequest(); 2 request->setUrl("http://localhost:23244/NetTime.svc/Time"); 3 request->setRequestType(CCHttpRequest::kHttpGet); 4 request->setTag("WebTime"); 5 request->setResponseCallback(this, httpresponse_selector(HelloWorld::onHttpComplete)); 6 CCHttpClient::getInstance()->send(request);*/ 7 8 NetTime * netTime = NetTime::create(); 9 netTime->requestNetTime(); 10 char timeStr[50]; 11 sprintf(timeStr, "NetTime %d-%d-%d %d:%d:%d", 12 netTime->getYear(), netTime->getMonth(), netTime->getDay(), 13 netTime->getHour(), netTime->getMinute(), netTime->getSecond()); 14 CCLabelTTF *timeLabel = CCLabelTTF::create(timeStr, "Arial", 18); 15 timeLabel->setPosition(ccp(240, 50)); 16 this->addChild(timeLabel);
进行调试:
异常信息为:变量timeStr内存损坏。可以分析得到引发此异常最可能的原因是netTime的getYear(),getMonth()...方法均没有返回正确的值。
再来看上面NetTime类的代码,发现对_year,_month,_day,_hour,_minute,_second的赋值操作均在onHttpComplete()方法,也就是CCHttpClient网络请求的回调方法中。
由此可以断定,在我们使用netTime的getYear(),getMonth()...方法返回字段值的时候 ,onHttpComplete()回调方法并没有执行。那么,为什么回调方法没有执行呢??
通过查看Cocos2dx的api文档,发现对CCHttpClient有这样一句描述:处理异步http请求的单例模式 一旦请求完成,一个在生成请求时被提供的回调函数,会被发到主线程中
到这里就恍然大悟了,原来CCHttpClient的http请求为异步方法,进行http请求并不会堵塞cocos2dx主线程的执行。
这种情况下一个比较好的解决思路是,我们在onHttpComplete方法中对字段进行赋值之后,同样触发一个回调方法,在外部类需要读取网络时间的地方注册该回调方法。
(对C++回调函数不熟悉的可以参考这里:C/C++之回调函数)
为了符合cocos2dx的使用习惯,我们参考cocos2dx中常用的回调方法设计,如CCHttpRequest中的回调。
通过查看CCHttpRequest源码可以发现,首先声明了用于调用回调方法的函数指针和用于注册回调方法的宏:
typedef void (CCObject::*SEL_HttpResponse)(CCHttpClient* client, CCHttpResponse* response);
#define httpresponse_selector(_SELECTOR) (cocos2d::extension::SEL_HttpResponse)(&_SELECTOR)
setResponseCallback进行回调方法注册的实现如下,两个参数分别为回调方法的调用者和函数指针:
inline void setResponseCallback(CCObject* pTarget, SEL_HttpResponse pSelector)
{
_pTarget = pTarget;
_pSelector = pSelector;
if (_pTarget)
{
_pTarget->retain();
}
}
然后通过CCHttpClicent的send(CCHttpRequest* request)方法传入CCHttpRequest的实例,在异步请求Http完成后,调用注册的回调方法返回给我们Http请求的状态和数据。
初步了解了cocos2dx中回调的实现后,回到我们的NetTime中,在NetTime.h头文件中声明函数指针和用于注册的宏定义:
typedef void (CCObject::*SEL_NetTime)(WebTime * pSender);
#define netTime_selector(_SELECTOR) (SEL_NetTime)(&_SELECTOR)
同时声明用于存放调用者实例和函数指针的字段:
SEL_NetTime _pSelector;
CCObject * _pTarget;
通过重载requestNetTime方法传入回调方法的调用者和函数指针:
void NetTime::requestNetTime(CCObject * pTarget, SEL_NetTime pSelector) { requestNetTime(); _pTarget = pTarget; _pSelector = pSelector; }
在onHttpComplete方法的最后调用回调方法,并把NetTime的实例作为回调方法的参数:
1 void NetTime::onHttpComplete(CCHttpClient * sender, CCHttpResponse * response) 2 { 3 CCHttpClient::getInstance()->release(); 4 if (!response) 5 return; 6 if (0 != strlen(response->getHttpRequest()->getTag())) 7 { 8 CCLog("%s completed", response->getHttpRequest()->getTag()); 9 } 10 int statusCode = response->getResponseCode(); 11 char statusString[64] = {}; 12 sprintf(statusString, "HTTP Status: %d, tag = %s", statusCode, response->getHttpRequest()->getTag()); 13 CCLog("%s", statusString); 14 if (!response->isSucceed()) 15 { 16 CCLog("response failed"); 17 CCLog("error buffer:%s", response->getErrorBuffer()); 18 return; 19 } 20 std::vector<char> * buffer = response->getResponseData(); 21 std::string str; 22 for (unsigned i = 0; i < buffer->size(); i++) 23 { 24 char a = (*buffer)[i]; 25 str.append(1, a); 26 } 27 CCLog("%s", str.c_str()); 28 readJson(str); 29 if (_pTarget&&_pSelector) 30 { 31 (_pTarget->*_pSelector)(this); 32 } 33 }
到这里便完成了对NetTime类的封装。
再来修改HelloWorldSecne中对NetTime使用的代码:
1 NetTime * netTime = NetTime::create(); 2 netTime->requestNetTime(this, netTime_selector(HelloWorld::onNetTimeComplete));
onNetTimeComplete代码如下:
1 void HelloWorld::onNetTimeComplete(NetTime * pSender) 2 { 3 char timeStr[50]; 4 sprintf(timeStr, "NetTime %d-%d-%d %d:%d:%d", 5 pSender->getYear(), pSender->getMonth(), pSender->getDay(), 6 pSender->getHour(), pSender->getMinute(), pSender->getSecond()); 7 CCLabelTTF *timeLabel = CCLabelTTF::create(timeStr, "Arial", 18); 8 timeLabel->setPosition(ccp(240, 50)); 9 this->addChild(timeLabel); 10 }
调试结果:
和上一篇文章的结果是一致的。
Demo下载:NetTime(2).zip