Tuesday, August 28, 2018
Wednesday, July 17, 2013
聊聊Service吧!
聊聊Service吧!
我們在一個大型系統裡面,常常會用到許多Service,這些Service通常來講會以Singleton的形式存在(或者他的變形,比方說在Spring裡面就是@Autowire)。這些系統用的Service通常會有一個共通的界面,比方說,這些Service都是CRUD服務的話,大概會長這樣…
interface IService {
String getServiceName();
String getServiceID();
}
interface ICRUDService<Target> extends IService {
long create(Target t) throw ServiceException;
T read(long uuid) throw ServiceException;
long update(Target t) throw ServiceException;
bool delete(long uuid) throw ServiceException;
}
淺顯易懂沒有任何問題。假設我們有一個Service叫做NameCard service,我們可能就會這樣實作…
public class NameCardService implements ICRUDService<NameCard> {
// ICRUDService implementation is here
…
…
…
//NameCardService only public method
public void sort();
public List<NameCard> getDuplicated();
}
當我們需要取用這個服務的時候,有很多種不同的做法。最普通的Singleton風格就是
NameCardService service = NameCardService.getInst();
而Spring風格則會是
@Autowire
NameCardService service;
而Android風格則會是
NameCardService service = (NameCardService)this.getSystemService(NAME_CARD_SERVICE);
前兩者其實都沒什麼型別安全上的問題,第一個做法會有Scaling上的問題,我們必須要在getInst()裡面檢查太多東西(別忘了,getInst()是static,沒辦法被包進interface -- 這點顯然obj-c是好一點),比方說有沒有權限拿取啊,該做哪些logging阿....balabalaba,這些通通都是duplicated code -- 因為他們沒辦法被寫入interface。第二種作法則是綁死在Spring,而且我想應該不用我說,大家也知道Spring新增一個服務並沒有那麼簡單直觀。
所以Android選擇了3,但是很明顯地,這邊存在一個無法在編譯期檢查出來的型別問題,也就是說,你可能無法阻止別人寫成這樣
NameCardService service = (NameCardService)this.getSystemService(TELEPHONY_SERVICE);
因為它getSystemService傳回來的是一個類似IService的東西,這等於要強迫使用者使用「正確的Context」「正確的Service Handle」去轉型成「正確的Service」
以一個寫Framework的人來講,這真是惡夢 - 誰曉得Framework user這些工程師會不會拿一些莫名其妙的錯用來當作bug開給你?
//以下當然會執行期爆掉
((NameCardService)this.getSystemService(TELEPHONY_SERVICE)).sort();
//我必須對某些喜歡這種懶人寫法的人致敬,這些人專注於寫出讓人(包含自己)看不懂的code的努力,實在是令人太欽佩了。
重點在於Java來講,用任何一個Native型別去當作Handle都是一種頗令人困惑而且不智的行為。什麼是Handle?其實就相當於map裡面的key,用這個key去取得value(在這裡就是指Service)。有興趣的人可以看看Android的getSystemService(...)裡面傳入的參數是什麼?結論會很讓你噴飯,他傳入的Handle索引居然是一個字串…不過這有他的道理,他傳入的字串是一組class的qualified name,這可以讓他很輕易地「無中生有」出一組service -- 我們不在這裡討論這種規格外的做法。另外,傳入string還有一個非常糟糕的用途--它可以把要傳給service的參數包在一部分的String傳進去,很方便,很好用,我們也可以想像這是維護上的多大的災難。
有沒有什麼方法至少可以逼使用者不用轉型,甚至在編輯器裡面就可以輕鬆地幫你指出你拿到的是什麼Service而不會出錯呢?比方說我們能不能在編譯時期就檢查出下面這寫法對不對
//拿出來就是NameCardService,不需要轉型
BaseCRUDService.getService(???????????).sort();
//拿出來就是Telephony Service,不需要轉型
BaseCRUDService.getService(???????????).getTeleponyStatus();
甚至Eclipse就可以幫你檢查出來了!這乾五可能!?
廢話,當然可以,不然我寫這篇幹嘛 XD
這秘密在於我們Handle的選擇。String?這顯然不對,long?這當然也不行,特殊規格的class?我沒想到要怎麼用這種方法,也許誰來發明一下
我們Handler選用的是Java Generic可以直接支援的Class<?>
老實講我一直覺得Java Generic雖然靈活度不比C++的Template,不過Generic裡面的"?"實在是一個很無賴的東西,它可以做出很多平常根本想當想不到的方法。
首先,我們為了讓所有的CRUD Service都會「註冊自己」,讓自己可以被CURDService找到,所以我們必須要有一個「所有的CRUDService都會跑得到的地方」--我們當然第一個想到的就是constructor,所以我們要讓所有的CRUD Service從implements ICRUDService改成extend BaseCRUDService
abstract class BaseCRUDService<T> implements ICRUDService<T> {
BaseCRUDService() {
//現在我們有一個地方可以讓所有的CRUD Service都跑到了
}
}
public class NameCardService extends BaseCRUDService<NameCard> {
…
…
…
}
這邊有一份實作,可以先給大家參考一下,出來差不多就是我剛剛講的結果。所有的Service只要繼承BaseCRUDService,就可以用BaseCRUDService.getService(Class<? extends BaseCRUDService> clz);拿出正確的Service,而且可以直接編譯期判斷出該型別,並且可以用編譯器的自動完成去呼叫該Service獨有的method。
import java.util.HashMap;
public abstract class BaseCRUDService<MarshelBean extends ICRUDBean> implements ICRUDService<MarshelBean> {
protected BaseCRUDService() {
registerSelf(this);
}
@SuppressWarnings({ "unchecked" })
private <T extends ICRUDService<?>> void registerSelf(T service) {
serviceMap.put( (Class<? extends ICRUDService<?>>) service.getClass(), service);
}
/**
* For thread safety, it can be ConcurrentHashMap...as long as I don't think it is needed.
* Since it is single thread write one time only, HashMap can be good enough in any case.
*/
static HashMap<Class<? extends ICRUDService<?>>, ICRUDService<?>> serviceMap = new HashMap<Class<? extends ICRUDService<?>>, ICRUDService<?>>();
@SuppressWarnings("unchecked")
static public <T extends ICRUDService<?> > T getService(Class<T> targetService) {
return (T) serviceMap.get(targetService);
}
public abstract <T extends ICRUDService<?>, F extends ICRUDBean> T handle(F targetBean);
}
這份實作神奇的地方在於,只要任何服務繼承它,那它就可以被「型別安全的」取出來。這點很重要,請跟著我重複念兩次。另外,這份實作是取自於我自己寫的一個PP通訊界面,所有CRUD都會至少有一個Marshel(管理)的對象,這個被管理的對象一定會繼承自IBean,如果你不知道他是幹嘛的,請完全不用理他。
class PeopleRecordService extends BaseCRUDService<PeopleRecord> {
…
…
…
void people_record_only_function();
}
class CarRecordService extends BaseCRUDService<CarRecord> {
…
…
…
void car_record_only_function();
}
好,我們把這個東西setup了,該試試看他的威力了。
//PASS!
BaseCRUDService.getService(PeopleRecordService.class).people_record_only_function();
//Compile Time error!
BaseCRUDService.getService(PeopleRecordService.class).car_record_only_function();
//或著試試看下面這邊自動完成會幫你秀出什麼東西?
BaseCRUDService.getService(CarRecordService.class).;
That's it~ 沒有強制轉型,沒有奇怪的Context問題,更沒有duplicated static method,世界真是美好...
Wednesday, January 09, 2013
Singleton的愛恨情仇
Monday, November 05, 2012
Java System Call problem -- in Linux
Process p = Runtime.getRuntime().exec("ps -al");
//然後讀取InputStream做分析…
這是通解,不過這有個很大的問題。事實上, Runtime.getRuntime().exec在Linu
比方說,以我們這個case,2G。所以每call一次syst
目前我們是採用jni的方式,把system call轉移到c的部分去做。因為它是一個獨立的pid,所以不
Sunday, May 27, 2012
Bohemian like you
原來某人體內的怪獸已經那麼大了(遠目)
我非常喜歡這首歌清淡平淡的味道。Why can't I just simply like you?
Bohemian like you
Dandy Warhols
You got a great car.
Yeah, what's wrong with it today?
I used to have one too,
Maybe I'll come and have a look.
I really love your hairdo, yeah.
I'm glad you like mine too,
See we're looking pretty cool.
Getcha!
So what do you do?
Oh yeah, I wait tables too.
No I haven't heard your band
Cause you guys are pretty new.
But if you dig on Vegan food.
Well come over to my work
I'll have them cook you something that you'll really love.
Cause I like you,
Yeah I like you.
And I'm feeling so Bohemian like you,
Yeah I like you,
Yeah I like you,
And I feel wahoo, wahoo, wahoo!
Wait. Who's that guy just hanging at your pad?
He's lookin' kinda bummed.
Yeah you broke up that's too bad.
I guess it's fair if he always pays the rent
And he doesn't get all bent
About sleepin' on the couch when I'm there.
Cause I like you,
Yeah I like you.
And I'm feeling so Bohemian like you.
Yeah I like you.
Yeah I like you
And I feel wahoo, wahoo, wahoo!
I'm getting wise
And I feel so bohemian like you.
It's you that I want so please,
Just a casual, casual easy thing.
Is it? It is for me
And I like you
Yeah I like you
And I like you, I like you, I like you,
Yeah I like you.
And I feel wahoo, wahoo, wahoo!
Sunday, May 20, 2012
Epicurean
其實我對很多美食主義者有意見。或者說,自稱是美食主義者的人有意見。
我尤其難以理解為什麼某些自稱美食主義者的會說「因為吃過了XXX店的OOO,所以其他的OOO我再也難以下嚥了」,這種跟許多壓根不懂咖啡的人硬說「市面上大多數咖啡簡直是黑苦的液體,根本不能喝」在自稱是咖啡鑑賞大師的人一樣等級。
先不談這些老兄大姐到底懂不懂咖啡,舌頭夠不夠天賦靈敏。事實上,假設真的是一個好的美食主義者,擁有如此有天賦的味蕾,那為什麼僅能嚐得出別人的缺點,卻無法分辨出它口中「不入流的貨色」有哪些特點,有哪些是別人沒有的味道呢?我不否認,你要我說屎有什麼別人沒有的香氣,我真的說不出口,不過要是有人覺得世界上如此多的廚坊(請注意,還沒夠格到稱為廚房),都只能作出這些人口中的屎,只有最高級的米其林餐廳做出來的鵝肝醬,這些人才能描述他有多香氣澎湃多美味...
我覺得這些人還是不要自稱美食家比較好,這種人我一般稱之為滿口屎味。
咖啡也是。我曾經喝過初學者手沖出過熱焦氣滿口的單品,也喝過真正的高手火候十足,賽風下水剛剛好時間的醇品。每滴咖啡,都有一定的特殊香氣,都有其他人做不出來的神奇口感。
噢我還不敢自稱咖啡達人,只是味覺靈敏了點。
Epicurean還有一個意思,就是享樂主義者。有篇很有趣的文章,我曾經分享給大家過。看看,是不是跟美食主義果然是該共用同一個單字呢? :)
同學,你其實可以不用周遊世界
可以不用去追求別人描述的所有人性解脫,再回來說台北空氣多汙濁,生活多繁忙,多世俗。當連路邊小店的特殊風味都沒辦法品味,當連附近的小山小水都沒有仔細觀察,那,你有什麼自信那麼可以自稱一個Epicurean? 還是只是一種自以為Epicurean的自High以及自以為的超脫感?
Tuesday, May 15, 2012
Roxette - She doesn't live here anymore
- She Doesn't Live Here Anymore
Lyrics:Per Gessle Music:Per Gessle&Mats M.P.Persson
We grew up together, we've been here forever.
Barefoot in the summer, cold in stormy weather.
She taught me all there is, like magic and love,
lots of forgotten words.
But now, she doesn't live here anymore.
She's off and she's gone, she doesn't live here anymore.
I'm sorry that's all there is to it,
I'm sorry that's all.
She dressed just like a painter,
a female street Picasso and I,
I will remember the heaven in her laughter.
And the scent of her sweet perfume
when we made love under an August moon
But now, she doesn't live here anymore.
She's off and she's gone, she doesn't live here anymore.
I'm sorry that's all there is to it,
I'm sorry that's all.
But now, she doesn't live here anymore.
She's off and she's gone, she doesn't live here anymore
She's gone since long, so long, she doesn't live here anymore.
She took off and left home, she doesn't live here anymore.
I'm sorry that's all there is to it, I'm sorry that's all.
That's all.
-
她教會了我一切,如魔幻般的愛情,無法用言語表達的感覺
Thursday, March 15, 2012
Java socket程式 -- 絕對避免println() / PrintStream!
珍惜生命 遠離println / printstream(全文完)
--------
基本上Client來說,看到這裡應該就夠了,下面是比較詳細的說明。換行(也就是println會幫你做的事情)會幫你插入一個換行符號,而這個換行符號在各個系統都不一樣,所以基本上各個系統輸出都不同。比方說Windows就是CRLF(難得反而windows是正統的) 而unix系的含OSX絕大多數都是CR。
這是java少數幾個無法跨平台的地方,你可以想像一下同樣一串碼
out.println("This is SPARTA!");
其實在Windows底下是輸出
This is SPARTA!\r\n
而Unix系卻是
This is SPARTA!\r
這個println如果是輸出到螢幕上的話,沒差,大家看起來都一樣
阿輸出到socket就有差了,後者readline會根本讀不到
那當然照慣例就要問了:那要怎麼辦?
簡單,out.print("This is SPARTA!\r\n");
當然,也別忘了out.flush();
請銘記在心,寫Java Socket程式絕對要遠離任何println/printstream,看到他請當瘟神一樣離開他
ok,接下來談談server對應的部分。事實上當你寫server的時候,很多時候你是沒得選擇的。根據我自己的經驗,會犯下這種錯誤的coder其實比率是相當高的,只是絕大多數都是在windows下作業跟跑server,有時候是一些3rd party,server來講你很難做些什麼。畢竟code不是你寫的,你也沒source code,你更沒辦法把寫client的人抓過來狗幹一頓(我個人是稱為「友善的爭論」XD)
既然改不了client,那我們只好改server,讓server吃得下這些錯誤的東西
在談讓server怎麼吃這些東西而不至於拉肚子以前,我先講個瘟腥小故事。
IE7以前的世界,事實上browser是非常「友善」的,友善到一些明明是錯的東西他照樣吃。比方說有<p>沒</p>,或者<body><p> ....... </body></p>照吃不誤。這樣其實大家寫code都很開心,問題是每個browser都用不一樣的方法去解譯它,所以造成了同一組code的效果不見得一樣。
這造成了html界的大混亂,也間接導致了嚴格規範的xhtml誕生。(不過這東西始終沒有來的及正式長大,就被html 5.0取代了)
請注意,我們現在做的事情就跟以前「友善的」server一樣,是不對的,但是這是一種沒辦法中的辦法
說穿了很簡單,我們改用一個字元一個字元的去讀它(沒buffer想必這會很耗力)。我們讀取到\r的時候,我們會打開一個flag, 然後將\r後面多寫一個\n,下一個如果是\n的時候我們就把flag關閉並且跳過這個\n,不是的話關閉flag繼續。
具體的code大約是這樣(我個人是用filterinputstream處理這種東西)
boolean LRFlag = false;
while(char input = in.read()) {
if(input == '\r') {
LRFlag = true;
out.write(input);
out.write('\n');
continue;
}
if(input == '\n' && flag == true) {
flag = false;
continue;
}
flag = false;
out.write(input);
}
繼承fliterinputstream在read裡面插入這一段(也有一種寫法是override skip(),方法大同小異,我是比較喜歡override skip()就是,不過這比較少為人知)
hope it helps
Thursday, December 22, 2011
淺談OAuth in Android -- 以Plurk為例子
OAuth是目前比較熱門的認證機制之一,他有個最大的好處就在於完全不需要在client端輸入賬號密碼,這部分完全會由web端完成。不過,他機制非常的複雜,有些地方做的讓人覺得很龜毛,所以我們在這篇只要淺談就好,其他的部分我會介紹一個函式庫來把它完成。
這邊有一篇非常不錯的OAuth機制介紹,有興趣的朋友可以看看。太細節部分的工作原理我們就不涉足了,我們專心看看最基本的認證步驟
- app開發者跟網站註冊取得API Key跟API Secret
- 開始認證的時候,用這組key/secret/callback(*1)跟網站要一個Request Token(*2)
- 用這組Request Token, callback組合成一個網址,讓使用者進入該網址認證
- 認證成功以後會傳回一個oauth_verifier,通常是一個很好閱讀的字串或者數字
- app端把這組verifier,以及request key跟網站取得半永久性的Access Token(*2)
- 往後對這個網站任何要求(比方說,plurk來講,取得時間軸)使用這個Token即可,app把它存起來備用,就不需要重新跟網站申請認證了。
這幾個步驟其實相當的繁瑣,尤其是對網站要求每次都要做一個全面性的Parameter Sign的動作,裡面牽涉的東西對一個初學者來講真是煩人到爆炸。當然,就會有人會寫一組library來解決這問題:我們今天介紹的就是oauth-signpost library。
基本的取得jar,放進project,設定buildpath這些基礎到爆的東西我們就不討論了。首先,我們要先去Plurk申請一組API Key來作為我們的開發用途。所以我們就有了API Key/Secret了。接下來,回到Plurk API介紹的頁面,他提供了幾個網址供OAuth使用。基本上這些分別是幹嘛的我們就不予深究了,我們只要知道她是要怎麼用。
signpost我們會用到根OAuth有關的一共有兩個部分:OAuthConsumer代表的是我們APP端的所有資料,包含Access Token等等(不過她好像沒幫你存,你要自己存),而OAuthProvider則是代表網站的OAuth認證部分。
static final String PLURK_REQUEST_URL = "http://www.plurk.com/OAuth/request_token";
static final String PLURK_AUTHORIZATION_URL = "http://www.plurk.com/m/authorize";
static final String PLURK_ACCESS_URL = "http://www.plurk.com/OAuth/access_token";
static final String PLURK_CALLBACK_URL = "myplurk:///";
static final String PLURK_CONSUMER_KEY = "你的API Key";
static final String PLURK_CONSUMER_SECRET = "你的API Secret";
我們先把這些東西設定變數,等等會用到。接下來我們設定Provider跟Consumer
mainConsumer = new DefaultOAuthConsumer(PLURK_CONSUMER_KEY, PLURK_CONSUMER_SECRET);
mainProvider = new DefaultOAuthProvider(PLURK_REQUEST_URL, PLURK_ACCESS_URL, PLURK_AUTHORIZATION_URL);
ok,那所有東西差不多就完成了。我們要讓使用者認證的時候,只要開一個url,載入provider提供的url :
String url = mainProvider.retrieveRequestToken(mainConsumer, PLURK_CALLBACK_URL);
private class AuthClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String urlString) {
Log.d("SubPlurkV2", "url : " + urlString);
if(urlString.contains("subplurkv2")) {
Uri url = Uri.parse(urlString);
String verifier = url.getQueryParameter("oauth_verifier");
try {
SystemManager.getInst().getAuthManager().aquireAccessToken(verifier);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finish();
}
return super.shouldOverrideUrlLoading(view, urlString);
}
mainProvider.retrieveAccessToken(mainConsumer, verifier);
Log.d("SubPlurkV2", "Token = " + mainConsumer.getToken() + " and secret = " + mainConsumer.getTokenSecret());
把這祖Token記錄起來,以後會用到。最後,我們來試試看最簡單地拿取時間軸吧
mainConsumer.setAccessToken("U5GNYyH6wVGS", "aCtJ9XVgNTeIpigq3qxsLi70Sv0HbA6h");
URL url = new URL("http://www.plurk.com/APP/Timeline/getPlurks");
HttpURLConnection request = (HttpURLConnection) url.openConnection();
request.setDoOutput(true);
request.setRequestMethod("POST");
mainConsumer.sign(request);
request.connect();
String context = StreamUtil.InputStreamToString(request.getInputStream());
Log.d("SubPlurkV2", "" + context);
have fun! 參考plunk api list繼續實做其他的api吧!
Monday, December 19, 2011
幾個Android裡面Dependency的設定差異 (1)
Android裡面,設定Dependency有幾種方法,不過都各自有一些問題。
官方的做法是把其中一個設定成Lib Project,然後我們就能在Host專案裡面選取這個Lib Project,剩下的ADT會自動幫你搞定。
然後,在要使用這個Library的專案,同樣的地方,下面的checkbox應該就會出現可以選擇的lib名稱,選Add加入就好
這個方法是Android內建的方法,他有些問題跟限制。1. 他會被「Close not related project」關掉,這個簡單,大不了在打開 2. 他在clean build會出錯,要重復clean個幾次。3 被設定成lib的project就不能跑了,所以不能相依於另外一個完整地執行模組(手動開開關關lib開關是可以啦,只是麻煩)。
Wednesday, December 14, 2011
解決static library(.a)在IB以及某些category的靈異狀況。
Interface Builder是個很神奇的東西,裡面很多神秘的機制。其中最為讓人難以裡解的就是他跟dependency的恩怨情仇。對,android的java有更嚴重的問題(在UI上,android甚至沒辦法access到dependency的resource),不過這個Bug完全是因為Interface的linking機制是等到obj編譯完以後才開始找的關係(所以這個bug基本上只會出現在自定的widget class)。
這個bug大概會以這種形式發作:我們的.a(static靜態函示庫)裡面有某個widget class,而引用端的Interface Builder會引用這個class, 於是在某種情況下就出錯了
2011-12-14 13:58:50.999 TestPlatform[294:707] Unknown class MyClassBtn in Interface Builder file.
2011-12-14 13:58:51.010 TestPlatform[294:707] Unknown class MyClass2Btn in Interface Builder file.
這出錯的原因很簡單。因為Interface Builder是從.obj(compile from .a)裡面去獲得class的名字以及資訊,但是他找不到(廢話)。為什麼找不到?因為.obj裡面真的沒有這個symbol。所以IB只好很哀怨地把他設定回預設的class,比方說UIButton。當然,當你真的invoke起這個button又用了只有MyClassBtn才有的method,他就會理所當然地吐一個unregconized selector的runtime exception給你。
所以問題出在linker,自作主張地把這個.a裡面的symbol沖倒馬桶去沒有編譯進去.obj了。linker在編譯.a編譯成.obj的時候,為了一些效能以及檔案大小上的理由,會主動地去尋找其他的.obj裡面到底有沒有真的用到某個symbol,要是他在其他obj裡面找不到,那自然就會理所當然地把它扔了。
然後IB自然就沒辦法在編譯出來的.a找到symbol,一切合情合理。
解決的方法有兩個,我先聊聊第一個比較簡單的方法。為了防止linker愚蠢的(這樣將不太公平,天曉得這東西IB會用到?)把不該丟掉的symbol給沖到馬桶去,所以我們必須要呼叫他的static method來欺騙linker,這東西是有用的,不准丟掉。link show了一個比較hack的方法,你做一個static method,但是什麼都不做,然後在host呼叫他,linker就會被欺騙而不會把.a裡面的symbol丟掉才編成.obj。
其實下面也提到一個更簡單的方法,事實上你呼叫class靜態方法也可以,比方說[MyClassBtn class]。
後面會提到第二種方法,同樣可以解決這問題。不過再看第二種方法以前,我們先看看另外一個靈異現象。
Category也有類似的問題,不過起因不太一樣。當你去Categorize一個Native class(最常見的,恩,NSString)在編譯的時候會被錯誤的當作沒用到的丟棄了--這起因應該主要是因為linker的objc旗標buggy,我猜的--所以同樣的在使用static library時,library裡面native class的category都會無作用(很妙的是,自動完成使用的資料庫是.h,所以自動完成會自動地幫你找到method,但是runtime會因為找不到這個selector炸掉,真悲)。
解決的方法就是前面提過的第二種方法 -- 強制linker「你什麼symbol都不准丟掉」。在宿主的build setting裡面,找到other linker flags,加上-all_load或者-force_load(老實講前後兩者不太一樣,但是我沒有去追究他們到底有什麼細微的不同)。這同樣也可以解決IB找不到symbol的問題。
所以我們可以替這兩種方法做一個總結。第一種方法是藉由對linker額外的提示(但是code會混亂不堪),明確地告訴linker那些東西要額外地放進來。所以.a->.obj並不會include過多的symbol,不會造成.obj又肥又大的問題。當然缺點就是code要用一些非常莫名的東西來提示linker--當然IBOutlet/IBAction都用了,不在乎再多個幾行莫名其妙的code。我們其實可以藉由#define來稍為美化他一下,至少讓他[MyClass class];這行看起來不會那麼奇怪。另外,他對於category造成的問題其實是無解的。
第二種方法則沒有以上的所有缺點,但是會造成.a->.obj非常大--尤其當你的.a包山包海無所不包的時候(比方說我個人非常愛的Utility classes)--然後大通常伴隨的就是慢。不過,在普通的輕量級class裡面,這算是一個可以接受的效能損失。當然,category造成的問題,這是唯一解的樣子。聽說-force_load效能會比-all_load效能好,不過我沒比過。
static library問題多,還是用source code編譯最好(遠目)
Tuesday, December 13, 2011
iOS底下幾個NS Debug用的旗標
首先,先Edit Scheme叫出下面這個畫面
這些Enviroment Varibles分別代表這些意思 :
NSZombieEnabled : 除錯BAD_ACCESS_ERROR用的,可以追蹤過度retain的時候的一些多於資訊
MallocStackLogging : 可以enable一些unhandled exception的call stack資訊。
NSDebugEnabled :設定除錯旗標,可以讓你在程式裡面判斷,比方說
#include <unistd.h>
int main (int argc, char ** argv, char ** envp)
{
#ifdef DEBUGENVIRON
if (!getenv("NSDebugEnabled"))
{
setenv("NSDebugEnabled","1",1);
... set the other variables ...
// Maybe this will complain about an autorelease pool.
char * executablePath = [[[NSBundle mainBundle] executablePath] filesystemRepresentation];
execve(executablePath, argv, environ); abort();
}
#endif
... do what you normally do in main() ...
}
但是老實講除此之外我也不知道他能幹嗎
Ref :
http://stackoverflow.com/questions/3816136/condtional-environment-varialbes-in-xcode
Monday, December 12, 2011
iOS底下啓動Code Trace Stack的方法
XCode4以後,以往的Code Stack變成了一堆address map,所以當Unhandled exception被Throw, 就會出現象如下圖一般的悲慘畫面
這些Trace顯然對追bug幾乎一點幫助都沒有,inspect crashed thread又只能看到一堆assembly,以前那些親切的Exception Stack跑哪去了呢?
其實Stack trace在exception裡面一直都是存在的,我們要做的只是讓unhandled exception被印出來而已。為了達到這個目的,所以我們必須做個小小的手腳。
首先先介紹一個基本的觀念,在C裡面,所有的unhandled exception都有一個最終處理函數,這個函數是可以經由指標切換的。原始預設的函數就是印出一堆有的沒有的以後,執行exit()。我們要做的就是寫一個函數取代掉它
void uncaughtExceptionHandler(NSException *exception) {
NSLog(@"CRASH: %@", exception);
NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
// Internal error reporting
}
接下來我們必須要告訴系統,預設的處理unhandled exception handler函數更改為這個函數
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
return YES;
}
當然,我們有很多地方可以放NSSetUncaughtExceptionHandler,不過我們選擇放在application:didFinishLaunchWithOptions:裡面
然後我們就可以得到下面的結果 :
看,是不是好除錯多了嗎? 至少可以知道exception是從哪裡丟出來的了
