Posts Tagged ‘Web’

一個人獨立開發iPhone App的甘苦談…

Wednesday, June 8th, 2011

去年12月,我從原本任職的一家網路公司離職,決心投入開發iPhone App的行列 (其實也不是一離職就決定要這樣的,也是經歷了許多事和想法的轉變)。在這之前,我寫過C/C++、Java、Ruby、PHP、Javascript。碰過手機UI介面開發、Java Game、DB資料庫、Web Server、網頁前端技術。但沒寫過Objective-C(我想應該很多人跟我一樣,因為這套語言實在有點新,又比較封閉),所以在離職的前半年,我大概花了二個月的時間把Apple給開發新手的英文文件K完 (我可以跟你講,很痛苦,尤其周遭沒有半個人可以問,而且Apple的文件又常常喜歡用艱澀的英文寫,不像Google的技術教學文件簡單易懂,常常K到半夜眼睛在酸)。之後再利用下班時間花了大概一個半月寫了一套不難的小App(一個英文教育軟體),可是需要與Server端作溝通,如果不是因為我本身前後端的技術都碰過的話,一個人做可能還真的有點難搞。

離職後,到目前為止已經過了剛好半年了。後來又陸續做了二個App,一個是工具類型的App,另一個是遊戲App。目前正在做另一個遊戲App和另一個App的外國新版本。從去年六月申請IDP(iPhone Developer Program)到現在,剛好整整過了一年,前天我才第一次拿到Apple要匯給我的營收款項,不過很少,只有5千出頭。不過在離職前的半年,其實我是沒有很積極的在寫App的,而離職後的三個月我才比較有新作品上架。所以嚴格來說,從三個月前開始算,才是我真正有獲利的開始。努力了一年,才賺五千塊? 有沒有搞錯!? 我去大公司上班年薪都可以拿到一百多了,我到底在幹嘛? 呵~ 有時候我也會問我自己這個問題,有時候會有點沮喪~ 會覺得自己很傻。但,這就是所謂的機會成本吧。如果你選擇百萬年薪的工作,沒錯! 你每年都可以保證賺一百萬,努力的話甚至更多。但相對的,你也失去了每年可以賺500萬的機會,甚至1000萬、5000萬。我,選擇了後者,因為我覺得我這個年紀還有辦法承受創業失敗的風險。如果再過幾年,也許想創業也沒這個機會了…

一個人寫App到底是什麼感覺? 是寂寞,是艱辛,是享受,是熱血! 大概就是這樣。雖然偶爾還是會想說,要不要回去職場上班算了。但想了想就會想到,回去職場上班,做的東西可能不見得是自己喜歡的,而且生活也會受到許多的約束,每天都要配合固定的時間坐息,尤其台灣的科技公司老闆又喜歡以壓榨員工為樂(我之前待過的公司是都還好,當然,是我自己選的,我寧願錢賺少一點然後做自己喜歡的東西也不願意賣身)。想到如果去上班,就沒辦法再繼續過自由的生活,想開發什麼有趣的App就去做,想寫code寫到半夜2點就寫,想打電動打到半夜3點就打,想偷閒一下去吃吃豆花就去。想想便作罷,覺得如果生活還過的去手頭沒這麼緊的話,我還是想繼續現在的生活與工作模式。

一個人寫App要會什麼? 第一是技術。廢話! 你要先學會Objective-C這套語言(到現在為止,我還是覺得它是我碰過最難上手的語言 ~”~)和Cocoa這套Framework及Xcode這套IDE。不是只要學會這些就夠了,那隨便找一個大學剛畢業的資訊相關科系學生也可以做,他們coding搞不好還寫的比你快。這些只是基本的必要條件,在這之前,你必須累積各種技術和經驗,因為你不知道哪一天寫什麼樣的App會用到。不管是前端還是後端,資料結構還是物件導向設計,你能吸收多少就努力去吸收。沒有人敢說自己每一套程式語言都可以100%上手寫的很好,可是程式語言中,有些共通的觀念是不會變的。例如盡量減少I/O的次數、減少邏輯判斷的複雜度、模組化…等等。這些東西,Apple的官方開發文件不會教,你必須在自己的工作生涯中努力學習和累積。

第二是你要有企劃的能力。意思是,當你想到一個idea或一個遊戲,你要有能力將每一個細節和流程想到,烙印在你腦海裡。並且把一些細節流程,做成一個自己看的懂,配合的開發人員也看的懂的東西(不管你是用什麼方式去呈現)。企劃者本身兼具有技術能力有個好處,在企劃的過程中,你會一併考慮在技術面是不是比較容易做的到,而不會有太天馬行空的做法出現。換做今天是兩個不同角色的人在一起做的話,就會常常出現企劃者想的東西太過不切實際或不好執行,兩個人就必須花許多時間在調整與磨合。另外還有個好處是,由懂技術的開發者來做企劃,他們想到的idea往往會令人大吃一驚。因為他們會很清楚的知道,目前有什麼樣的酷炫新技術可以玩,或者Apple提供的iOS新版本SDK中多了什麼新玩意兒可以嘗試。一個企劃者了解越多新技術、新玩意,對企劃上絕對會有顯著的幫助。

第三是你要有喜愛學習新事物的能力。你不能因為以前是做技術出身的,就只想碰技術,其他跟技術沒關的領域你就不想碰。你去公司上班,可以這樣,公司其實也希望你有專精的能力比較好。但你現在自己就是老闆,是個獨立開發者,許多事情你必須都要自己親自處理。即使你沒碰過、你不會、你不想,你還是要做,你沒辦法說No。以我自己來說,我沒做過音樂、音效方面的處理,就要自己上網去查有什麼音樂製作軟體比較好用,有什麼錄音用的麥克風是大家推薦的,哪裡有可以付費下載的商用音樂音效網站。我不懂行銷(不過我想目前在這App這個領域,也沒多少人懂 ~”~),就去多看網路上的文章,多吸收國外的資訊,多跟以前的同事朋友聊聊。要懂得謙虛的放下自己的身段,重頭開始學,才有辦法繼續走這條路。

第四是你要有尋找Out Sourcing的能力。一個人再怎麼強,也不可能每樣東西都會,一定有自己一個人沒辦法Handle的地方。以我來說,我可以處理技術、UI設計、企劃、音樂、音效、網站、行銷,但唯一我覺得一個人沒辦法做或做不好的,就是美術的部份。而開發一個App,尤其是iPhone的App,美術更是重要。因為那會影響你這個App到底是100人下載還是1萬個人下載,所以我寧願花錢請人做或找伙伴一起做。這時候,人脈就很重要了,還好我的個性雖然有點孤辟但以前在上班時還算人緣不錯,也都有找到可以幫我處理的好伙伴。

最後是你要有一顆熱情、不怕失敗的心。創業這條路不好走,很多成功的人其實之前都失敗過好幾次。先問問自己為什麼想寫App? 想寫什麼樣的App? 原本覺得一個App應該至少要賺100萬,結果只賺1000塊,是不是受的了這種打擊? 這些都清楚的想過之後,而且是肯定的答案。那恭喜你,你應該有辦法可以繼續往下走。我當初開始之前,其實並沒有想那麼多,做就對了。不過還好,這些試煉我都有通過…~”~。一個人創業的初衷是很重要的,當你失意困惑時,它會重新把你帶回正確的路(我不是在講廢話,是真的發生在我身上的經驗)。如果連自己為什麼想要做App的理由都說不出來,那可能只要一做失敗,你就會放棄,回去找工作了。如果你很清楚自己要的是什麼,即使失敗了,你還是有辦法調整方向,繼續往下走。另外,想寫什麼樣的App也是很重要,找自己熟悉的地方開始,會比做自己完全不知道使用者需求的App,更容易成功。我是個從國小就開始打紅白機、國中打街機、高中玩MD、大學後玩PC Game、Online Game和各式遊戲器(Wii、PS2/3、PSP)的骨灰玩家,我一天沒打電動手就會覺得癢。所以我選擇了開發遊戲為我最主要的App類型。因為我會比一般人更熟悉玩家的需要,我可以把自己當成一個玩家般來看待自己寫的遊戲好不好玩,哪裡不好玩就要改,哪裡設計醜就要換。如果做出來的東西,連自己都不喜歡玩,那你也不用想說要去說服別人下載。

hmm… 苦水寫了好多… ~”~。不過,到目前為止,我還是覺得自己很幸福。可以有錢有閒做自己想做的事,朝著自己的夢想前進,家人也很支持我。不像有人,搞不好今天禮拜六還要去血汗工廠加班(謎之音: 按!! 你講話一定要這麼機車嗎? XD 今天加班的人可別跳樓… ~”~;)。即使失敗了,我還是不會後悔這一年的努力,至少我盡力了,會不會成功,就交給老天吧…。

這是我的一些個人心得,有些觀點和想法,我也不敢說自己就是100%正確,我自己也還在學習摸索中。等到哪一天如果我一年可以賺500萬了,我就可以再來寫篇文章屁說:「對! 就是要這樣!! 我講的沒錯~」。希望那天早點到來~~ :mrgreen:

相關文章:
一個人獨立開發iPhone App一年半回顧
一個人獨立開發App七年回顧

HTML5學習筆記

Wednesday, November 24th, 2010

以下內容是根據讀完”Professional JavaScript for Web Developers 2nd Edition“這本書的心得而來。

持續更新中…

querySelector() & querySelectorAll()
提供像CSS selector一般的方式來取得element物件,jQuery framework已經改以這兩個method來實作取得element的方法。速度上比自己用JavaScript去寫一個CSS selector parser快很多。

// get first element with a class of 'selected'
var elm = document.querySelector('.selected');

// get all <strong> elements inside of <p> elements
var elmList = document.querySelectorAll('p strong');
classList property
存在於element,可用來對element做class屬性的操作。有add, remove, has, toggle四個methods。
// add 'selected' class name
elm.classList.add('selected');

// remove 'selected' class name
elm.classList.remove('selected');

// check the element has 'selected' class name?
if( elm.classList.has('selected') ) {
    // do something...
}

// switch the 'selected' class name on target element when user click trigger button (if target element has the class name then remove it, otherwise, add it)
$('triggerBtn').click(function() {
    elm.classList.toggle('selected');
});
postMessage()
可用來做Cross-Document(iframe)之間的溝通。在HTML4,兩個不同的document之間是完全無法做溝通的。而HTML5提供了這個比較安全的做法,來達到資料交換的目的。來源document可以call這個method代入要傳送的資料及來源domain、port資訊等等。而接收方document會收到一個”message” event,可根據來源資訊來決定。

<iframe id="inner_page" src="http://test.domain/inner.html"></iframe>
// in main page
var innerWin = document.getElementById('inner_page').contentWindow;

// send a message, parameters: data, origin
innerWin.postMessage('Thank you!', 'http://test.doamin');
// in inner.html
EventUtil.addHandler(document, 'message', function(event) {
    if( event.origin.indexOf('test.domain') >= 0 ) {
        // display the data
        alert(event.data);

        // send a message back
        event.source.postMessage('You are welcome!', 'http://test.domain');
    }
});
<video> & <audio>
用來做影片、音樂的播放。提供許多property及event可對影片及音樂播放做細微控制。

<!-- embed a video -->
<video src="movie.mpg" id="myVideo">Video player not available</video>

<!-- embed a audio -->
<audio src="song.mp3" id="myAudio">Audio player not available</audio>
<canvas>
可用來在Web Page上繪圖,包括fillRect, lineTo, fillText, drawImage…etc.等。目前除了IE外,其他Browser均有支援。詳見這裡
offline & online event
HTML5新增了offline和online兩個event,可以用來偵測目前的網路連線狀態,而對使用者做不同的UI回應。開發者可以access navigator.online這個boolean值用來做判斷,true代表連線中,false代表連線失敗。

EventUtil.addHandler(window, 'offline', function() {
    // some codes to handle offline status
});

EventUtil.addHandler(window, 'online', function() {
    // some codes to handle online status
});

// make different response by online stauts when user click a trigger button
$('triggerBtn').click(function() {
    if( navigator.online ) {
        // do something...
    } else {
        alert('The connection is lost, please check your network status.');
    }
});
pushState()
這個method可以讓一個AJAX Web Application在某一個時間點塞入一個state object到history queue裡,讓使用者也可以按Back返回鍵回到上一步的狀態。

// add state to history stack
history.pushState({mode: 'edit'}, 'Editing');

// listen for a chance in state
EventUtil.addHandler(window, 'popstate', function(event) {
    var state = event.state;

    if( mode == 'edit' ) {
        // restore page to 'edit' state
    }
});
Database Storage
HTML5提供了client端的database空間讓JavaScript code可自由存取使用,Web App因此可以做出更接近OS Native App般更豐富的功能。

// create database
var db = window.openDatabase('TestDB', '1.0', 'My Test Database', 200000);
// create table
db.transcation(function(tract) {
    var queryStr = 'CREATE TABLE Messages (id REAL UNIQUE, msg TEXT)';
    // parameters: query string, query parameter, success callback, fail callback
    tract.executeSql(queryStr, [],
        function(tract, results) {
            // table create successfully, do something...
        }, function(tract, error) {
            // table create failed, do something...
        }
    );
});
// query data
db.transcation(function(tract) {
    var queryStr = 'SELECT id, msg FROM Messages WHERE id=? and msg=?';
    // parameters: query string, query parameter, success callback, fail callback
    tract.executeSql(queryStr, [queryId, queryMsg],
        function(tract, results) {
            for( var i=0; i<results.rows.length; i++ ) {
                var row = results.rows.item(i);
                alert(row.id + '=' + row.msg);
            }
        }, function(tract, error) {
            alert('the query cannot be done!');
        }
    );
});
Drag & Drop
HTML5提供預設的drag and drop UI支援,所有的element均有一個draggable屬性來決定是否可被拖曳。而image, text和link這三種element的預設draggable值為true,其他為false,也可以手動去更改這個屬性值。而要被drop的element可以去監聽event: dragenter, dragover, dragleave, drop。也可以設定或取得event裡的dataTransfer object資料,和指定其dropEffect及effectAllowed兩個屬性。
var dropElm = document.getElementById('my_drop_panel');

// listen dragenter
EventUtil.addHandler(dropElm, 'dragenter', function(event) {
    // do something...
});
WebSocket
可以在Browser和Server之間打開一條Socket通道,做即時的資料傳輸。用以取代類似像Comet的技術。某些應用如: WebIM, 即時股票指數更新…等,就可以改用這個方式來實現。

// create socket connection
var socket = new WebSocket('ws://test.domain/connect/');

// listen connection status as opened
socket.onopen = function(event) {
    alert('Connection is ready');
};

// listen connection status as closed
socket .onclose = function(event) {
    alert('Connection is closed');
};

// receive data from server
socket.onmessage = function() {
    var data = event.data;
    // do something...
};

// send data to server
socket.send('fp=1');

使用DocumentFragment來加快DOM操作速度

Thursday, November 18th, 2010

DocumentFragment這個物件,一般人好像很少用到。這是一個可以大幅增進Web Page效能物件。廢話不多說,用法如下,假如你有一段code要插入數個element到body裡:

原本的寫法

	for( var i=0; i<100; i++ ) {
		var item = document.createElement('div');
		$(item).text('Element-'+i);
		$(item).css({
			background: 'gold',
			padding: 5,
			margin: 5,
			float: 'left'
		});
		$('body').append(item);
	}

使用DocumentFragment的新寫法

	var fragment = document.createDocumentFragment();
	for( var i=0; i<100; i++ ) {
		var item = document.createElement('div');
		$(item).text('Element-'+i);
		$(item).css({
			background: 'gold',
			padding: 5,
			margin: 5,
			float: 'left'
		});
		fragment.appendChild(item);
	}
	$('body').append(fragment);

用Firefox實測,使用第二種DocumentFragment寫法,速度快了將近一倍 ;-)

之所以用DocumentFragment速度會變快的原因是,DocumentFragment是一個頁面上”不存在“的element物件。所以你對它做任何操作都不會影響到使用者觀看的外觀。第一段code寫法,其實對browser做了100次”更新頁面”的動作,而第二段code只對browser更新一次頁面。這樣說知道速度差在哪了吧…。

另外,當DocumentFragment被插入頁面時,只有它的child element會被加入頁面,它”本身”並不會被加入。

不過,上面兩段code做的事,還可以用下面這段code取代,速度比第二段code更快一倍。做法就是把全部要被插入element的HTML字串組成一個大字串,再一次寫入。但不是每個程式邏輯都適合用這種寫法,例如說如果想要在每個item element被create之後,另外綁定一個callback function或執行某個jQuery plugin的初始化,下面這種寫法就辦不到了。

	var html = '';
	for( var i=0; i<100; i++ ) {
		html += '<div style="background:gold; padding:5px; margin:5px; float:left;">Element-'+i+'</div>';
	}
	$('body').append(html);