Reading Excel using Ruby
There’re some libraries dealing with Excel, but they didn’t work in some conditions. So I’ve just written the code for this.
ExcelData uses win32ole module. It means this work only on Windows and Excel needs to be installed.
There’s only a public class method Load returns row array. Each row consists of hash with column name and value string pair.
Load method requires minimum three parameters. Excel filename(excelFilename), sheet name(worksheetName) and first header column name(firstHeaderColumnName). ExcelData finds first row with first header column name and read cell from there. If empty cell is found(both row and column), it stops reading the file.
require 'win32ole'
class ExcelData
public
def ExcelData.Load(excelFilename, worksheetName, firstHeaderColumnName, keySearcher = nil)
xls = nil
ws = nil
excelData = []
begin
xls = WIN32OLE.new('Excel.Application')
xls.visible = false
ws = xls.Workbooks.Open(excelFilename).Worksheets(worksheetName)
excelData = _CollectData(ws, firstHeaderColumnName, keySearcher)
rescue
puts "Failed to open excel file : #{excelFilename}"
ensure
xls.quit if xls != nil
end
return excelData
end
private
def ExcelData._FindFirstDataRowNum(worksheet, firstHeaderColumnName)
rowNum = 1
while (true)
row = worksheet.Range("a#{rowNum}")
break if (row['Value'] != nil && row['Value'] == firstHeaderColumnName)
rowNum += 1
end
return rowNum + 1
end
def ExcelData._FindLastDataRowNum(worksheet, firstDataRowNum)
rowNum = firstDataRowNum
while (true)
row = worksheet.Range("a#{rowNum}")
break if (row['Value'] == nil || row['Value'] == "")
rowNum += 1
end
return rowNum - 1
end
def ExcelData._FindLastDataColChar(worksheet, headerDataRowNum)
colChar = 'a'
while (true)
row = worksheet.Range("#{colChar}#{headerDataRowNum}")
break if (colChar == 'z' || row['Value'] == nil || row['Value'] == "")
colChar.succ!
end
return colChar
end
def ExcelData._CollectData(worksheet, firstHeaderColumnName, keySearcher)
firstDataRowNum = _FindFirstDataRowNum(worksheet, firstHeaderColumnName)
lastDataRowNum = _FindLastDataRowNum(worksheet, firstDataRowNum)
lastDataColChar = _FindLastDataColChar(worksheet, firstDataRowNum - 1)
puts "Collecting excel data : Total #{lastDataRowNum - firstDataRowNum + 1} rows ..."
# Column Names
colNames = []
row = worksheet.Range("a#{firstDataRowNum - 1}:#{lastDataColChar}#{firstDataRowNum - 1}")
row.each do |cell| colNames << cell['Value'] end
# Collect excel data
excelData = []
for rowNum in firstDataRowNum..lastDataRowNum
colIndex = 0
rowHash = {}
row = worksheet.Range("a#{rowNum}:#{lastDataColChar}#{rowNum}")
row.each do |cell|
if (!cell['Value'].to_s.empty? && cell['Value'].to_s =~ /^[0-9.]+/)
rowHash.store(colNames[colIndex], cell['Value'].to_i.to_s)
elsif
rowHash.store(colNames[colIndex], cell['Value'].to_s)
end
colIndex += 1
end
excelData << rowHash
keySearcher.store(rowHash[colNames[0]].to_s, excelData.length - 1) if keySearcher != nil
print "." if excelData.length % 10 == 0
end
puts
return excelData
end
end
You can simply use this code like bellow.
xls = ExcelData.Load('myfile.xls', 'sheet1', 'ITEMID')
Ruby On Rails & 생각
짧은 시간 이었지만, 태어나서 처음으로 웹 프로젝트를 끝냈다. 나에게 ‘끝냈다’는 남들이 사용할 수 있을만한 결과물을 내 놓았다는 의미이다. 이를 기념 삼아 간단히 소회를 풀어보고자 한다.
내가 웹 프로그래밍을 해본 것이라고는 한 십 년쯤 전에 PHP로 뭔가 간단히 끄적여 본 것이 전부였다. 그러다가 Ruby를 접하고 내친김에 Ruby On Rails(이하 RoR)도 시작했지만, 태생이 태생인지라 웹서버와 브라우저간의 통신이라는 다소 미묘한 세계를 이해하는데 상당히 애를 먹었다. 서버프로그래머인 나에게 있어서, 웹 프로그래밍 – 나의 경우에는 좀 더 구체적으로 RoR이 되겠다 – 에서 사용하는 서버와 클라이언트간의 정보전달방법은, 그 추상화의 정도가 너무 심했던 것 같다. 이런 이유로 RoR에 관한 책을…, 그러니깐 다섯 권을 샀다. 물론, Ruby에 관한 책은 별도고. 세 번째 책쯤에서 느낌이 왔고, 네 번째 책에서 감을 잡았다. 그래서 다섯 번째 책은 아직 그냥 있다. 언젠간 읽겠지. 꽤 빨리 읽을 수 있을 것이다.
회사에서 게임데이터를 엑셀로 관리하고 있었다. 같은 실수가 몇 번이고 반복되고 있었고, 기획자는 툴의 필요성을 강조했다. 그래서 내가 RoR을 꺼냈다.
어느 게임회사나, 시간이 넉넉한 곳은 없다. 따라서 게임개발에 사용되는 툴은 그 중요도에 따라 완성도가 결정되는 경우가 많다. 프로그래밍지식이 없는 기획자나 그래픽담당자는 자신이 하는 일이 막노동인줄도 모르고 그냥 열심히 하며, 설사 이를 프로그래머가 알게 되었다 하더라고 전담자가 배치되지 않는 이상 제대로 된 툴이 나오지 않는다. 어지간한 규모가 아니라면, 툴을 전담하는 개발팀을 두는 회사는 거의 없다. 결국, 툴 개발은 게임프로그래머에게 추가작업을 요구하게 된다.
게임데이터를 다루는 툴은 단순해 보이지만, 이것이 DB와 연결되고 데이터 검증과정이 들어가고, 또 사용자 인증 및 히스토리기능까지 넣으려 한다면, 이를 독립된 어플리케이션으로 만드는데 꽤 많은 시간이 소모될 것은 뻔한 일이다. 더구나 원활한 편집을 위한 UI는 게임프로그래머가 구현하기엔 결코 간단한 기능이 아니다.
나는 조인을 사용하는 쿼리문을 제대로 작성할 줄 못하고, 서로 다른 DBMS의 특성도 잘 모른다. Ajax는 거의 들어만 본 수준이다. HTML은 기본적인 것만 알고, 당연히 웹 표준은 딴 세상 이야기다. 프로젝트 시작전이나 지금이나 이 사실은 변함이 없다. RoR이 멋진 것은, 이런 상태에서도 프로젝트를 끝낼 수 있었다는 것이다. 물론, 아는 것이 많을수록 더 괜찮은 프로그래밍을 할 수 있는 것이 당연하겠지만, 그렇지 않아도 in-house툴로는 충분한 결과물을 만들어 낼 수 있다.
이후에, 테스트 삼아 만들어본 또 다른 프로젝트는, 반나절 만에 동작하는 프로토타입을 만들어 낼 수 있었다. 서버의 덤프파일이 생기면 이를 모아서 관리하고 그 통계를 보여주는 웹 프로그램이었다. 이를 웹으로 구현하지 않으면, 파일전송을 위해서 FTP를 사용하든가 아예 이를 구현해야 한다. 그리고 또 이를 정보화하기 위한 작업도 필요하다. 개발자가 덤프분석을 위해 이를 다운받는 건 또 어떻게 할 것인가.
대부분의 게임프로그래머는 웹 프로그래밍에 대해 알지 못한다. 게다가 웹 프로그래밍에 대한 기술적 하대는 게임프로그래머로 하여금 웹 프로그래밍에 대한 관심을 점점 더 멀게 한다. 안타까운 점은, 동일한 목적에 대해서, 어플리케이션개발보다 웹 개발이 훨씬 더 빠르고 효율적인 경우가 많다는 것이다. 더구나, RoR처럼 생산성이 높은 웹 프레임워크를 사용한다면, 그 효과는 배가되기 마련이다. 이 일을 사내의 웹팀에게 넘기지 않고 직접 해야 하는 이유는, 그들도 바쁘거니와, 프로그래밍을 할 줄 아는 사람 중에서는 게임프로그래머가 그 게임과 만들 툴을 가장 잘 이해하고 있기 때문이다. 웹팀이 각 팀에 특화된 툴을 만들어 줄 수 있을 리도 요원하다.
나의 경우는 Ruby가 마음에 들어서 RoR을 사용한 것이지만, 어떤 언어든, 어떤 프레임워크든, 어떤 방식이든, 빨리, 그리고 사용자가 편하게 사용할 수 있는 물건을 내놓는 것이 최고 아니겠나.
마지막으로, 그 툴이 지금 어떻게 지내고 있는지 살짝 덧붙인다. 툴만 완성되면 실수가 없을 것처럼 이야기하던 기획자는 아직 그 툴을 제대로 사용하고 있지 않다. 요청한지 몇 주가 지나도록 툴 설정에 관련된 자료는 오지 않고 있으며, 그럴 의지가 있는지도 잘 모르겠다. 스크립트에 관련된 각종 자료를 정리해주는 것보다, 실수를 하더라도 지금까지의 방식을 고수하는 것이, 차라리 더 편하다고 생각하는 것 같다.
툴이 사용되지 못해도, 이를 통해서 내가 배운 것들로 충분히 행복하다. 애당초, 거기에 더 큰 비중이 있었다. 다만, 계속 부지런 떠는 그들이 마음에 걸릴 뿐이다.
아이러브스쿨은 왜 잊혀졌는가?
한창 주가를 올리던 시기엔, 원하는 친구는 아이러브스쿨을 통해서 다 찾을 수 있을 정도였다. 그러다 좀 있으니 다모임이라는 사이트가 그 자리를 대신하기 시작했고, 동시에 아이러브스쿨은 내리막길을 향해 발걸음을 내딛고 있었다.
보통, 이렇게 회사가 어려워지면, 여러 이유로 회사를 그만두는 사람이 늘어나기 시작한다. 그들 대부분은 그 길로 또 다른 삶을 시작한다. 그 중에는 드물게 스스로의 뒤를 돌아보는 사람도 있기 마련인데, 바로 아이러브스쿨의 개발팀장이었던 서영수님이다.
오늘, 뒤늦게 발견한 그의 소중한 회고는 몸담고 있는 회사에 대해서 많은 것을 생각할 수 있게 해주었다.
다운로드 PDF: 아이러브스쿨은 왜 잊혀졌는가?
Critical Section Block 2
이전 포스트에서 CSBLOCK 매크로를 사용해서 좀 더 간편하게 크리티컬 섹션을 정의할 수 있도록 했었다. 이에 조금 덧붙여서, 어떤 데이터형이든지, 그에 대응하는 크리티컬 섹션 변수를 만들어 줄 수 있도록 하고 싶었다. 이 작업도중 Variadic macro to count number of arguments을 읽게 되었고, 옳타구나 하고 여러 개의 자료형을 매크로의 파라미터로 사용할 수 있도록 수정했다.
다음은 이 아이디어를 구현한 코드이다.
locker 클래스는 주소값에 대응하는 값(이 코드에서는 int형 key를 사용했지만, 윈도우의 크리티컬 섹션을 위해서는 CRITICAL_SECTION 타입의 변수가 될 것이다)을 등록/삭제/검색 한다.
class locker
{
public:
locker()
: _key(0)
{}
~locker()
{
map<unsigned int, int>::iterator itr, itrend;
itr = _users.begin();
itrend = _users.end();
for (; itr != itrend; ++itr)
{
unregister_addr((void*)itr->first);
}
}
int register_addr(void* address)
{
map<unsigned int, int>::iterator itr = _users.find((unsigned int)address);
if (itr != _users.end()) return itr->second;
cout << "register: " << hex << (unsigned int)address << endl;
_users[(unsigned int)address] = ++_key;
return _key - 1;
}
int unregister_addr(void* address)
{
int key = 0;
map<unsigned int, int>::iterator itr = _users.find((unsigned int)address);
if (itr == _users.end()) return key;
key = itr->second;
cout << "unregister: " << hex << (unsigned int)address << endl;
_users.erase((unsigned int)address);
return key;
}
int key(void* addr)
{
map<unsigned int, int>::iterator itr = _users.find((unsigned int)addr);
if (itr == _users.end()) return 0;
return itr->second;
}
private:
map<unsigned int, int> _users;
int _key;
};// class locker
이 클래스에는 약간의 문제가 있는데, _users, _key 변수 역시 크리티컬 섹션이라는 점이다. 따라서 이에 대한 처리도 필요하다. 또한, 주소값에 대한 key를 동적으로 생성하는데 대한 성능상의 문제도 존재한다. 그렇지만, 어디까지나 이는 아이디어를 구현하기 위한 과정으로 판단해서 일단 패스. :)
scope_lock_args 클래스는 CSBLOCK 매크로가 여러 개의 인자를 받을 수 있도록 해준다.
class scope_lock_args
{
friend class scope_lock;
public:
scope_lock_args(int count, ...)
{
va_list addresses;
va_start(addresses, count);
for (int i = 0; i < count; ++i)
_addresses.insert(va_arg(addresses, void*));
va_end(addresses);
}
typedef std::set<void*> data_type;
private:
std::set<void*> _addresses;
};// class scope_lock_args
scope_lock 클래스의 생성자와 소멸자에서 크리티컬 섹션의 시작과 종료를 알린다. 각 데이터는 scoke_lock_args 클래스가 주소에 따라 정렬해서 관리하기 때문에 항상 같은 순서로 락을 걸고 풀 수 있다.
class scope_lock
{
public:
scope_lock(scope_lock_args arg)
: _arg(arg)
{
scope_lock_args::data_type::iterator itr, itrend;
itr = _arg._addresses.begin();
itrend = _arg._addresses.end();
for (; itr != itrend; ++itr)
{
_locker.register_addr(*itr);
cout << "scope_lock::scope_lock " << hex << *itr << endl;
// EnterCriticalSection
}
}
~scope_lock()
{
scope_lock_args::data_type::reverse_iterator itr, itrend;
itr = _arg._addresses.rbegin();
itrend = _arg._addresses.rend();
for (; itr != itrend; ++itr)
{
cout << "scope_lock::~scope_lock " << hex << *itr << endl;
// LeaveCritialSection
}
}
operator bool() const
{
return true;
}
private:
static locker _locker;
scope_lock_args _arg;
};// class scope_lock
locker scope_lock::_locker;
하일라이트. CSBLOCK는 최소 하나부터 최대 세 개까지의 데이터를 인자로 받을 수 있다. 물론, 필요하면 늘릴 수 있다.
#define CSBLOCK(...) CSBLOCK_IMPL(__VA_ARGS__, 3, 2, 1) #define CSBLOCK_IMPL(_1, _2, _3, N, ...) if (scope_lock __sl = scope_lock_args(N, _1, _2, _3))
이제는 CSBLOCK 매크로에 여러 개의 인자를 넣을 수 있고, 그 순서에 상관없이 항상 같은 순서로 락을 사용한다.
#include <iostream>
#include <map>
#include <set>
#include <cstdarg>
using namespace std;
int main()
{
int k = 0;
int l = 0;
CSBLOCK(&k)
{
cout << "<1>" << endl;
CSBLOCK(&l, &k) // 순서 상관 없음
{
cout << "<2>" << endl;
}
CSBLOCK(&k)
{
cout << "<3>" << endl;
}
CSBLOCK(&k, &l) // 순서 상관 없음
{
cout << "<4>" << endl;
}
}
return 0;
}
성능상의 문제로 실제로 사용하기에는 많은 개선이 필요하겠지만, 꽤 매력적이지 않나? ㅋㅋㅋ
C++은 확실히, 이런 것들이 재미있는 것 같다.
Authlogic 사용하기
Authlogic이 쉽고 간단하단다. 그래서 썼다. Railscasts #160 Authlogic를 따라 하는 데, 이거 영 안 된다. 그렇다. 난 레일즈 초보다. ㅠㅠ
Authlogic은 사용자세션을 ActiveRecord클래스와 동일한 방법으로 다룰 수 있도록 Authlogic::Session::Base클래스를 제공한다. 이 클래스를 자동으로 생성하기 위해서는 generate스크립트를 사용할 때 generator로 session을 지정하면 된다.
$ ruby script/generate session user_session
이는 사용자의 로그인/로그아웃을 ActiveRecord클래스가 데이터를 추가하거나 삭제하는 것처럼 처리할 수 있게 해주며, 실제 사용자 정보를 가질 (진짜 ActiveRecord를 상속한)모델클래스도 필요하다. 이 모델클래스를 Authlogic가 사용하게 하기 위해서는 클래스에 acts_as_authentic지시자를 추가해 주어야 한다.
class User < ActiveRecord::Base acts_as_authentic end
데이터모델클래스의 칼럼에는 string타입의 persistence_token이 존재해야 하며, 이 외에 다음과 같은 값을 사용할 수 있다.
t.string :login, :null => false # optional, you can use email instead, or both t.string :email, :null => false # optional, you can use login instead, or both t.string :crypted_password, :null => false # optional, see below t.string :password_salt, :null => false # optional, but highly recommended t.string :persistence_token, :null => false # required t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability # Magic columns, just like ActiveRecord's created_at and updated_at. These are automatically maintained by Authlogic if they are present. t.integer :login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns t.integer :failed_login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns t.datetime :last_request_at # optional, see Authlogic::Session::MagicColumns t.datetime :current_login_at # optional, see Authlogic::Session::MagicColumns t.datetime :last_login_at # optional, see Authlogic::Session::MagicColumns t.string :current_login_ip # optional, see Authlogic::Session::MagicColumns t.string :last_login_ip # optional, see Authlogic::Session::MagicColumns
또한, 이 모델클래스가 Authlogic의 세션모델과 연동되기 위해서는 세션모델의 이름이 데이터모델의 이름에 Session이 덧붙은 형태여야 한다. 즉, 데이터모델이 User라면, 세션모델의 이름은 UserSession이어야 하는 식이다.
이렇게 Authlogic의 사용준비를 마쳐서 User라는 데이터모델클래스와 UserSession라는 세션모델클래스가 마련되었다면, 사용자의 계정정보는 User클래스를 사용하여 저장하고, 세션정보는 UserSession클래스를 사용하여 다룰 수 있게 된다.
음, 이렇게 쓰고 보니 정말 쉬운 것 같기도 하다. – -;
애플의 혁신 – iPhone OS 4

Multitasking, Folders, Unified inbox, iBooks, Enterprise, Game Center, iAd.
‘겨우 이걸 신기술이라고 발표한 거야? 멀티태스킹은 애플의 모바일 디바이스에서만 안 되는 거고, 폴더도 니들만 없던 거고, 오죽하면 통합이 메일을 Top7에 포함시켰냐. iBooks는 자기들 책 팔아먹으려고 만든 게 뻔하고, 엔터프라이즈기능은 니들이 안 해도 아이폰을 사용할 기업이라면 어느 정도 당연히 하지 않겠어? 게임센터는 기존의 서비스를 죽일 테고, 콰트로를 인수했다더니 결국 광고기능을 넣는구먼.’이라고 쓰려고 했다. 스티브잡스가 대단한 건 알았지만, 이 정도로 일반적인 것들로 미디어의 대서특필을 이끌어 낼 수 있다는 게 어이없다고 생각했다. 나 또한 아이폰의 열렬한 지지자인동시에 사용자이지만, 솔직히 이건 아니라고 생각했다. 스티브잡스가 소개하는 iPhone OS 4의 대표적인 7가지 기술에 대한 수많은 기사와 글들을 봤을 땐, 정말인지 저렇게 쓰려고 했다. 진정하고, 키노트 동영상을 보기를 잘했다. 안 그랬으면 난 바보 될 뻔했다.
다른 건 제쳐두고, 세 가지에 놀랐다.
멀티태스킹이 어떻게 배터리킬러가 되지 않을 수 있는지에 대한 건 관심 없다. 그냥 그럴 수도 있다고 생각한다. 정말 그렇다면, 그것도 쬐끔 대단하긴 하다. 개발자에게 이에 대한 API를 제공한다는 것도 괜찮은 아이디어 같다. 내 아버지의 경우라면, 멀티태스킹이 없는 것이 오히려 이익이다. 윈도폰을 사용하시는, 나이에 비해서 매우 기술 친화적인, 작은아버지는 윈도폰의 인터페이스를 전혀 이해하시지 못하시는 듯 하다. 좀 더 정확히 말하면, 노력은 하시지만 이해가 안 된다고 해야겠다. 수십 년 젊은 나도 모르는 판에 오죽하겠나. 삼성이 만든 거지 같은 UI가 없었다면 그나마 나았을 수도 있었겠다. 과거에 내가 사용하던 WM6.1을 OS로 사용하던 스마트폰에서는 현재 어떤 프로그램이 실행 중인지 알 수가 없었다, 물론 설정으로 들어가서 메모리를 확인하면 어떤 프로그램이 돌고 있는지 찾을 수는 있다. 아니면 배터리소모를 감수하고 서드파티앱을 설치하면 PC용 윈도 비슷하게 구현되긴 했다. MS는 이런걸 누구나 알고, 또 할 수 있다고 생각한 것 같다. 그런데 말이지, 스티브잡스는, 혹은 애플은 그렇지 않다는 걸 알고 있었던 듯싶다. 아이폰의 몇 안 되는 버튼인 홈버튼을 더블클릭(이것도 나이가 있는 분들에겐 어려울 수 있지만, 버튼을 하나 늘리는 것보단 훨씬 낫다)하면 실행중인 프로그램이 보인다. 현재화면을 가리지도 않는다. 이건 윈도모바일 세계에선 상상도 할 수 없는 일이다. MS라면 새로운 창을 띄웠지 않았을까. 아니면 좀 더 복잡한 방법을 골랐을 것이다. 물론, 이것도 현재의 것을 버린다는 가정하에서나 가능하지만. 어쨌든, 이런 식의 멀티태스킹이라면 누구나 이해할 수 있을 것이다. 더 이상 언제 실행시켰는지도 모르는 앱을 발견하는 일은 없어지고, 또 그것을 확인하기 위한 복잡한 과정도 없어졌다.
여러 개의 앱을 폴더에 넣어둔다는 개념은 너무 당연해서 그걸 새로운 기능이라고 말하는 것조차 우습지만, 잡스가 보여준 대로라면, 이건 새로운 기능이 맞다. 자꾸 WM이랑 비교하게 된다. 어쩔 수 없다. 지금까지 너무 당연하게 생각하던 것들조차 애플은 더 단순하게 만들었다. WM에서 폴더란 것은, 그리고 모든 PC환경의 폴더는 별도로 만드는 것이다. 그래서 빈 폴더가 존재할 수 있다. 곱씹어보면, 빈 폴더가 존재해야 할 이유는 없다. 그래서 iPhone OS 4의 폴더기능에는 빈 폴더가 없다. 죽이는 발상이다. 서로 다른 앱의 아이콘을 합치면 자동으로 폴더가 만들어진다. 게다가 폴더이름은 앱의 카테고리를 참조해서 자동으로 기본값이 정해진다. 폴더를 펼치면? 기존의 화면에 폴더의 내용이 보인다. 지금까지 폴더를 클릭하면 그 내용이 그 창에서 표시되는 것이 일반적이었다. 당연히 기존의 화면은 보이지 않는다. 적어도 WM6.1에서는 그랬다. ‘화면이 작아서’라고 이해했지만, 아이폰에서는 그렇지 않다. 작은 화면은 여전해도, 폴더의 내용은 팝업으로 펼쳐진다. 폴더의 앱이 많으면? 그래서 9개로 최대개수를 제한하는 듯 하다. 확실치는 않지만, 그렇다고 하더라도, 아이콘의 수를 제한하는 것이 기존의 화면을 가리는 것 보단 훨씬 낫다. 더 이상 뒤로 가기 위한 버튼을 만들지 않아도 되니깐.
애드몹을 이용하여 앱에 광고를 게재하면 클릭당 0.01불을 준단다. 10원이 조금 넘는 돈이다. 근데 이걸 누가 클릭하지. – -; 누구도 광고로부터 방해 받고 싶어하지 않는다. TV에서 광고를 보지 않아도 된다면 누가 광고를 보겠는가. PC에서라면, 어쩌다 광고를 클릭하기도 한다. 화면이 넓으니깐, 팝업이 튀어나와도 그 정도를 참아줄 아량은 있다. 그러나 스마트폰이라면 얘기가 달라진다. 더 느려질게 뻔하고, 더욱이 튀어나온 브라우저를 꺼야 한다. 당연히 난 한번도 아이폰에서 광고를 클릭해 본적이 없다. 그러나 잡스가 시연한 iAd형식의 광고라면 얘기가 좀 달라진다. 여전히 광고를 클릭하려고 하진 않겠지만, 실수로라도 클릭하는 것에 대한 짜증은 훨씬 덜할게 분명하다. 광고에 방해 받고 싶지 않다는 말에는 광고로 내가 사용하던 앱의 실행을 중단해야 한다는 것도 포함된다. 그런데, 간단히 ‘X’버튼을 누르는 것으로 끝난다면? TV광고보다는 훨씬 좋다. 최소한, 클릭에 대한 부담을 상쇄시킬 수 있을 것이다. 이 정도라면, 정말 관심이 있는 건 클릭할 것 같다. 게다가 광고료의 60%를 개발자에게 준단다. 분명, 구글보다는 100배쯤 너그러운 조치다. 구글이 뭔가 바구지 않는다면, 앱개발자는 더 이상 애드몹을 사용하지 않을 것이다. 안드로이드 개발자라면 또 모르겠지만.
키노트 초반, 잡스가 iPad의 성과를 소개하기 전에 보여준 USA Today의 Ed Baig라는 사람의 iPad리뷰는 다음과 같다.
“The iPad is not so much about what you can do – browse, do e-mail, play games, read eboks and more – but how you can do it. That’s where Apple is rewriting the rulebook for mainstream computing.”
잡스횽이 how를 힘주어 말했다. 그럴 만 했다. 애플의 디바이스에 구현된 기능은 대단치 않지만, 그 기능을 사용하기 위한 UI는 분명 ‘혁신적’이라 할만하다.
RoR: remote_form_for와 테이블
루비온레일즈에서 테이블의 별도의 셀에 입력 폼을 나눠 배치했을 때, form_for를 사용하면 문제가 없었지만, remote_form_for를 사용하면 파라미터 값이 넘어오지 않는 문제가 있어서 엄청나게 삽질했다. 인터넷에 검색해보니 다음과 같은 내용이 있다.
결국, 테이블 밖으로 remote_form_for 블록을 빼니 파라미터 전달이 잘 된다. form_for는 왜 문제가 없는지 잘 모르겠지만, 테이블과 폼을 중첩시키는 것이 옳은 방법은 아닌 듯하다.
웹이라는 또 다른 패러다임에 적응하느라 삽질 중. 낯선 것이 너무 많다.
아이폰과 상상력
개발자로서 꽤 괜찮은 점이라면, 다른 사람이 만든 프로그램을 사용할 때, 대략의 사용법을 짐작할 수 있다는 것이다. 예를 들어 마우스 오른쪽 버튼을 누르면 당연히 팝업메뉴가 튀어나온다. 프로그래머가 제정신이라면 당연히 그렇게 만든다. 이점은 스마트폰에서도 비슷하게 동작한다. 윈도 모바일6.1을 운영체제로 사용하는 m480(미라지)에서는 화면을 누른 상태를 유지하면, 팝업메뉴가 튀어나왔다. 즉, 윈도모바일에서는 마우스 왼쪽, 오른쪽 클릭을 탭핑 시간으로 구분한다. 그것도 아니라면, 어딘가 관련된 메뉴를 화면에 보여줘야 한다. 이런 식으로, 윈도기반 UI에는 기본적인 룰이 있고, 대부분의 프로그래머는 이 룰을 잘 따른다. 사실, 그것을 거스르기가 더 힘들다.
이제, 아이폰 얘기다. 사람들이 애플의 UI는 직관적이라고 한다. 이 말이 사실로 받아들여질 수 있었던 데에는 하드웨어적 완성도와 운영체제가 받쳐줬기 때문이지만, 확실히 익히기 쉽다. 아마, 내가 개발자가 아니었다면, 그리고 MS의 UI에 익숙하지 않았더라면 더 쉬웠을 것이다. 아이폰에서 iTunes의 다운로드 목록을 지우는 방법을 모르겠다. 또 다운받은 Podcast를 지우는 방법을 모르겠다. 구글을 뒤져보니 혹자는 PC와 연결해서 iTunes에서 지워야 한단다. 그렇게 했다. 그렇지만 다운로드 목록은 지우지 못했다. 사용자 편의성이 좋아 그렇게 호평 받는다는 iTunes를 욕하며 구글을 좀 더 뒤졌다. 해당 항목을 좌우로 쓱~ 문질러주면 된다 길래 해보니 정말 삭제 메뉴가 떴다. 이로서 애플은 지저분한 ‘삭제’메뉴나 버튼을 만들지 않을 수 있었다. 이는 클릭, 클릭으로 동작하는 윈도모바일(적어도 6.5.2까지는)에서는 애당초 구현이 불가능한 방법이다. 윈도개발자인 나로서는 상상하기 힘들다. 그리고 그런 사용자는 분명히 많다.
어느 것이 좋고 나쁘고를 떠나서, 한가지 분명한 점은 MS의 UI가 나 상상력의 울타리로 작용했다는 점이다. 그리고 그것을 아이폰이 약간 걷어줬다. 이런 경험을 나뿐만이 아닌 많은 사람이 하게 되면서 더 많은 것이 바뀔 것이다. 나는 이런 변화를 즐겁게 바라볼 것이고, 좀 더 변하기 위해서 맥북도 질러야겠다. ㅋㅋ
WPTouch 설치
시대적 요구(?)에 의해서 WPTouch 를 설치했다. 이제는 아이폰에서 아주 예쁘게 보인다. 단, 소스코드는 잘리는 문제가 있는데…, 이런때에는 하단의 옵션을 끄면 일반 브라우저에서 보는대로 보인다.
이상은 대체로 아이폰 유저들을 위한 얘기.
결론? 대세는 아이폰.
이 포스트도 아이 폰에서 WP2어플로 작성 ㅋㅋ
앞으론, 아이폰앱 개발과 관련된 포스트도 쓸 수있는 기회가 있기를…
직접호출, boost::bind, mem_fun의 속도비교
‘boost::bind를 맘 내키는 대로 써도 될까?’라는 의문에 각 경우의 속도를 비교해보기로 했다.
우선, 재물로 사용될 클래스를 만들고…
class test_class
{
public:
test_class()
: _i(10)
{}
void show(int i)
{
++_i;
}
private:
int _i;
};// class test_class
아래는 테스트코드의 일부. TIME_DURATION은 포함되는 블록의 수행시간을 마이크로 초단위로 돌려준다.
const int repeat_num = 100000;
hs_int64 d;
test_class t;
TIME_DURATION(d)
{
for (int i = 0; i < repeat_num; ++i)
t.show(i);
}
cout << "direct call: " << d << endl;
TIME_DURATION(d)
{
for (int i = 0; i < repeat_num; ++i)
boost::bind(&test_class::show, &t, i)();
}
cout << "boost.bind call: " << d << endl;
boost::_bi::bind_t, boost::_bi::list2, boost::arg<1> > > t_mem_boost = boost::bind(&test_class::show, &t, _1);
TIME_DURATION(d)
{
for (int i = 0; i < repeat_num; ++i)
t_mem_boost(i);
}
cout << "boost.bind call via instance: " << d << endl;
TIME_DURATION(d)
{
for (int i = 0; i < repeat_num; ++i)
std::mem_fun(&test_class::show)(&t, i);
}
cout << "mem_fun call: " << d << endl;
std::mem_fun1_t t_mem_stl = std::mem_fun(&test_class::show);
TIME_DURATION(d)
{
for (int i = 0; i < repeat_num; ++i)
t_mem_stl(&t, i);
}
cout << "mem_fun call via instance: " << d << endl;
결과
direct call: 658 boost.bind call: 9152 boost.bind call via instance: 4034 mem_fun call: 1972 mem_fun call via instance: 950
테스트를 반복해도 결과는 대동소이하다. boost::bind는 직접호출보다 10배 이상 느리다. 그나마 mem_fun이 좀 낮다.
따라서…
‘boost::bind를 맘 내키는 대로 써도 될까?’ -> 살살 쓰자