Languages/Java2013/04/17 11:51

JAR file can be dynamically loaded from a running Java program. The dynamic loading is relatively easy. 


public class JarLoader {


@SuppressWarnings("unchecked")

private static Class<OFController> loadJar(String path) {

if ( !path.endsWith(".jar") ) {

// this is not a jar file.

return null;

}

String basename = Basename.get(path, "jar");

File file = new File(path);

if ( file.exists() ) {

ClassLoader loader;

try {

loader = URLClassLoader.newInstance(

new URL[] { file.toURI().toURL() },

JarLoader.class.getClassLoader()

);

return (Class<OFController>)
                                    Class.forName( basename, true, loader );

} catch (MalformedURLException e1) {

return null;

} catch (ClassNotFoundException e) {

Logger.stderr("basename " + basename + " is not found");

e.printStackTrace();

return null;

}

}

return null;

}


public static OFController getController(String path, int num_of_instances) {

Class<OFController> ctrl = loadJar(path);

if ( ctrl == null ) {

return null;

}

try { 

Constructor<OFController> constructor = 

ctrl.getConstructor(new Class[]{ int.class });

return constructor.newInstance( num_of_instances );

} catch (NoSuchMethodException e) {

Logger.stderr("Cannot find constructor for " + path);

return null;

} catch (SecurityException e) {

Logger.stderr("You are not authorized to open " + path);

return null;

} catch (InstantiationException e) {

Logger.stderr("Cannot instantiate controller from the given jar "
                                          + path);

return null;

} catch (IllegalAccessException e) {

Logger.stderr("You are not authorized to access constructor for "
                                           + path);

return null;

} catch (IllegalArgumentException e) {

Logger.stderr("You have passed wrong argument to " + path);

return null;

} catch (InvocationTargetException e) {

Logger.stderr("Wrong invocation target for " + path);

return null;

}

}

}


Above code is from a working system. Basically, all you need to look is the URLClassLoader, loadJar method, and codes  that actually call the constructor of the loaded class. 




In implementing this class, We have used a utiliy class called 'Basename'. As we set the name of the Jar file to the complete path of the class including package name (for example, xxx.yyy.SimpleClass), the class 'Basename' is critical in extracting the class name that would be instantiated. 


public class Basename {

public static String get(String path, String extension) {

String[] delimited = path.split("/");

String filename = delimited[ delimited.length - 1 ];

return filename.substring(0, filename.lastIndexOf("." + extension));

}

}


May the force of JAR files be with you!


저작자 표시 비영리 변경 금지
Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/462 관련글 쓰기

소중한 의견, 감사합니다. ^^

Languages/Java2013/04/15 11:16


Java NIO and reactor pattern.Java NIO and reactor pattern. http://www.moparscape.org/smf/index.php?topic=460976.0


The key component of java NIO is java.nio.channels.Selector. With this, you can easily monitor a specific set of events happening from underlying channels. With this, you can easily write codes to create a socket, bind the socket, listen the socket, and accept a new client connection. 


Following is an example code from a working system. 


Selector accept_selector = Selector.open();


ServerSocketChannel tcp_server = ServerSocketChannel.open();

tcp_server.socket().bind(new InetSocketAddress(6633));

tcp_server.configureBlocking(false);

tcp_server.register( accept_selector, SelectionKey.OP_ACCEPT );


//

// start accept loop

// 

int accept_seq = 0;

while ( !quit ) {

int r = accept_selector.select();


if ( r > 0 ) {

// accept set is ready

Set<SelectionKey> keys = accept_selector.selectedKeys();

for ( Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {

SelectionKey key = i.next();

i.remove();


if ( key.isAcceptable() ) {

int seq = ++accept_seq;

SocketChannel sw_channel = tcp_server.accept();

sw_channel.configureBlocking(false);

sw_channel.socket().setTcpNoDelay(true);

sw_channel.socket().setPerformancePreferences(0,2,3);


some_thread_object.addClient( sw_channel );

}

}

}

}



Above code is very simple, so I think no further explanation is needed. One thing to note is that you can also apply this kind of convention to the socket read. After creating sw_channel, you might pass the channel to a thread that monitors a set of connections. Then, the thread does 'select' on the set of connections, check if there are some readable connections. On the readable connections, you can actually perform 'read'. 


void addClient(SocketChannel client) {

synchronized ( guard ) {

try {

// ...

client.register( 

read_selector.wakeup(), 

SelectionKey.OP_READ | SelectionKey.OP_WRITE, 

null /* attachment */

);

} catch (ClosedChannelException e) {

// channel is closed. 

try {

client.close();

} catch (IOException e1) {

// does nothing.

}

}

}

}


Above code is to register a ClientChannel object to a 'read_selector' object, that the thread object has as its private member. One thing to note is, how the 'guard' object is used to prevent deadlock at client.register() and read_selector.select() call. This is an idiom, so please follow it. 


while ( !quit ) {

try {

// guard idiom to prevent deadlock at client.register() call

synchronized (guard) {}


int r = read_selector.select();

if ( r > 0 ) { // there's something to read.


Set<SelectionKey> keys = read_selector.selectedKeys();

for ( Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) {

SelectionKey key = i.next();

i.remove();

try { 

if ( !key.isValid() ) {

// do something

key.cancel();

conn.close();

continue;

}

if ( key.isWritable() ) {

// do something

}

if (  key.isReadable() && !handleReadEvent(conn) ) {

// do something

key.cancel();

conn.close();

}

} catch ( CancelledKeyException e ) {

e.printStackTrace();

continue;

}

}

}

} catch (IOException e) {

e.printStackTrace();

// just break this watcher.

return;

}

}


Above codes are from the run loop of  'some_thread_object'. Basically, this loop monitors a set of channels that read and write is possible. Once the selector selected a set of keys (each key is mapped to one SocketChannel object) that are readable or writable, you should check teach channel is actually readable using key.isReadable(), or writable using key.isWritable(). If they return true, that means you are able to read from or write to the channel. 


May the NIO be with you!


저작자 표시 비영리 변경 금지
Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/461 관련글 쓰기

소중한 의견, 감사합니다. ^^

Languages/C++2012/09/25 08:00

To remove the cost of frequently creating threads, we sometimes use thread pool. Basically, we save threads in thread pool in suspended status that waits for a signal. (All the threads are put into the pool after they are started and being suspended.) 


If we have to use a thread, we retrieve a thread from the pool, and assign some thread-specific data to the thread and give it a signal to wake up from the suspended status and to finish the job assigned to the thread. 


Below, I showed a declaration of PooledThread, which is a class for thread objects that would be maintained by a thread  pool. 


// pooled_thread.h


class PooledThread : public BJLEE::CThread {


    int id;


    // page to process

    void* data;

    void* context;


    volatile bool quit;


    BJLEE::CCond wait;


public :


    PooledThread(int pid)

        : CThread(false), id(pid), data(0), context(0), quit(false)

    {}


    void signal() {

        wait.lock();

        wait.signal();

        wait.unlock();

    }


    void shutdown() {

        quit = true;

        wait.lock();

        wait.signal();

        wait.unlock();

    }


    int get_id() { return id; }


    void set_data(void* d) { data = d; }

    void set_context(void* c) { context = c; }


    // callback that a subclass must define.

    virtual void process(void* context, void* data) {}


protected:

   void run(void* arg);

};


In implementing this class, I have used BJLEE::CThread class which is a wrapper class for POSIX pthread. (I will not show its implementation here.) By inheriting the class and implementing its pure virtual function 'void run(void* arg)', we can easily implement a pthreaded functions.  The 'run' method is initiated when we call 'void start(void* arg)' method. The argument given to this method is relayed to 'void run(void* arg)' as its parameter.


And I also used the BJLEE::CCond class which is a wrapper class for POSIX pthread-based conditional variable. By acquiring a lock to this object (by calling lock() method) and calling 'wait' method(), a owner thread of this object get into the suspended status. The thread is waken up by other threads that call 'signal()' or 'broadcast()' method after acquiring the lock to the object. 


In this PooledThread class, there are 'signal()' method which is used to a thread to wake up its suspended status, implemented using CCond object. 


'set_data()' and 'set_context()' method are to pass user-specific data to the thread before calling 'signal()'. 


// pooled_thread.cc


#include "pooled_thread.h"

#include "thread_pool.h"


void

PooledThread::run(void* arg) {


    ThreadPool* pool = (ICONThreadPool*)arg;


    wait.lock();

    while ( !quit ) {

        pool->initialized( id );

        wait.wait();


        process(context, data);


        pool->back(this);

    }

    wait.unlock();

}



Above is the implementation of 'void PooledThread::run(void* arg)'. As you can see, using the thread pool pointer given by the argument, it gives a notification that this thread is initialized and ready to accept a 'signal()' call. The 'signal()' method defined in the PooledThread class is to give a wake up call to the thread that is suspended in waiting status by calling 'wait.wait()'. 


Without the 'pool->initialized( id )', it is impossible to prevent a user of the thread pool to get a thread pointer that is not initialized yet from the thread pool. 


// thread_pool.h


class ThreadPool {


    friend class PooledThread;


    std::list<PooledThread*> threads;

    BJLEE::CCond threads_lock;


    // 128 is the max number of threads that this pool can hold :-P

    volatile int init_status[128];


    void initialized(int num) {

        init_status[num] = 1;

    }



public :


    ThreadPool() {}


    template <typename SUB>

    void init(int capacity) {


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

            init_status[i] = 0;     // not initialized

        }


        threads_lock.lock();

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

            SUB* obj = new SUB(i);

            obj->start(this);


            threads.push_back( obj );

        }

        threads_lock.unlock();


    }


    template <typename SUB>

    SUB* get(void* context, void* data) {

        threads_lock.lock();

        while ( threads.empty() ) {

            threads_lock.timedwait(1000);

        }

        SUB* ret = dynamic_cast<SUB*>(threads.front());

        threads.pop_front();

        threads_lock.unlock();


        while ( init_status[ ret->get_id() ] == 0 ) {

            /* spin lock until the thread is in the initialized status */

        }


        ret->set_context( context );

        ret->set_data( data );

        return ret;

    }


    template<typename SUB>

    void back(SUB* obj) {

        threads_lock.lock();

        threads.push_back(obj);

        threads_lock.signal();

        threads_lock.unlock();

    }

};


Above is the implementation of ThreadPool class. It's so simple that I would not explain about it any further.


Below, I showed the test program (which shows you the use case of the ThreadPool class) for this implementation.


#include "pooled_thread.h"

#include "thread_pool.h"


class TT : public PooledThread {

public :


    TT(int id) : PooledThread(id) {}


    void process(void* ctx, void* data) {

        using namespace std;


        struct timeval now1, now2;


        gettimeofday(&now1, NULL);

        unsigned long now1m = now1.tv_sec * 1000000 + now1.tv_usec;


        int sum = 0;

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

            sum += i;

        }


        gettimeofday(&now2, NULL);

        unsigned long now2m = now2.tv_sec * 1000000 + now2.tv_usec;


        printf("elapse = %lu \n", now2m - now1m);

    }

};


int main()

try

{

    ThreadPool p;


    p.init<TT>(5);


    int num = 1000000;


    int data[num];

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

        data[i] = i;

    }


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

        TT* tt = p.get<TT>(0, &data[i]);

        tt->signal();

    }


    sleep(1);

}

catch ( BJLEE::CThreadEx& e )

{

    cout << e << endl;

}



저작자 표시 비영리 변경 금지

'Languages > C++' 카테고리의 다른 글

C++ Thread Pool  (1) 2012/09/25
valgrind, gprof를 사용한 프로파일링  (0) 2012/08/28
배열(array) 선언의 묘미  (0) 2012/06/12
MSVCP100D.dll  (0) 2011/02/17
[C/C++] 쓸만한 메모리 디버깅 툴이 없을 떄  (0) 2010/07/13
cgdb : 텍스트 기반의 gdb 인터페이스  (0) 2007/12/18
Posted by 이병준
TAG C++, pool, Thread

TRACKBACK http://www.buggymind.com/trackback/430 관련글 쓰기

소중한 의견, 감사합니다. ^^

  1. 어린이들, 수원박물관에 푹 빠지다

    5월 5일(일), 제91회 어린이 날이다. 수원의 곳곳에서는 어린이날을 기념하는 많은 행사가 열렸다. 수원시 영통구 창룡대로(이의동) 265에
    [소셜미디어 위기관리] 채선당에서 남

    2013/05/06 17:38 [ ADDR : EDIT/ DEL : REPLY ]

Languages/C++2012/08/28 16:00

valgrind의 경우


valgrind의 경우 기본적으로 instruction 단위의 profiling을 할 수 있다. 컴파일 시 별도 플래그를 줄 필요는 없으므로 편리하다. 디버깅을 위한 -g 옵션만 주면 충분.


valgrind --tool=callgrind <실행파일명>


위와 같이 실행하면 실행이 종료된 후에 callgrind.out.xxxx 와 같은 파일이 생성된다. 이 파일을 다음과 같이 분석한다.


callgrind_annotate --inclusive=yes callground.out.xxxx


이렇게 하면 다음과 같은 화면이 출력된다. 


[ns2@ns2-2 server]$ callgrind_annotate --inclusive=yes callgrind.out.5765

--------------------------------------------------------------------------------

Profile data file 'callgrind.out.5765' (creator: callgrind-3.6.0)

--------------------------------------------------------------------------------

I1 cache:

D1 cache:

LL cache:

Timerange: Basic block 0 - 63600000

Trigger: Program termination

Profiled target:  ./a.out (PID 5765, part 1)

Events recorded:  Ir

Events shown:     Ir

Event sort order: Ir

Thresholds:       99

Include dirs:

User annotated:

Auto-annotation:  off


--------------------------------------------------------------------------------

         Ir

--------------------------------------------------------------------------------

265,452,667  PROGRAM TOTALS


--------------------------------------------------------------------------------

         Ir  file:function

--------------------------------------------------------------------------------

265,452,667  ???:0x0094b850 [/lib/ld-2.12.so]

263,862,639  ???:0x08048580 [/home/ns2/work/server/a.out]

263,861,046  ???:(below main) [/lib/libc-2.12.so]

263,860,819  ???:main [/home/ns2/work/server/a.out]

116,094,779  ???:clock [/lib/libc-2.12.so]

 57,463,494  ???:ftime [/lib/libc-2.12.so]

 56,288,440  ???:sysconf [/lib/libc-2.12.so]

 25,798,839  ???:gettimeofday [/lib/libc-2.12.so]

 23,453,500  ???:clock_gettime [/lib/librt-2.12.so]

 23,453,480  ???:times [/lib/libc-2.12.so]

 15,244,775  ???:__getclktck [/lib/libc-2.12.so]

  7,036,072  ???:_dl_sysinfo_int80 [/lib/ld-2.12.so]

  4,691,824  ???:0x00987b1f [/lib/libc-2.12.so]

  2,345,358  ???:0x00a8f9d8 [/lib/libc-2.12.so]


Ir은 Instruction Read의 약자이다. 얼마나 많은 instruction을 실행하였는지를 보여준다. 


gprof의 경우


gprof의 경우 소스코드를 컴파일 할 때 -g 옵션 뿐 아니라 -pg 옵션도 같이 주고 컴파일해야 한다. 그리고 실행 대상 프로그램은 저절로 종료되거나, 아니면 Ctrl+C와 같은 수단을 동원해 강제 종료되더라도 반드시 exit(0)이나 exit(-1)과 같은 수단을 사용해 정상적으로 termination 되어야 한다. 이를 위해서는 Ctrl+C 시그널 (SIGINT)을 수신하더라도 프로그램이 정상적으로 종료될 수 있도록 signal handler 설정을 하는 등의 작업이 필요할 수 있다. 


void shutdown(int signum) {

    printf("shutting down server...\n");

    exit(0);

}


...


int main(int argc, const char** argv)

{

    //

    // set signal handlers - expecially for Ctrl+C

    //

    struct sigaction new_act;

    sigemptyset(&new_act.sa_mask);

    new_act.sa_flags = SA_RESTART;

    new_act.sa_handler = SIG_IGN;

    sigaction(SIGPIPE,&new_act,0);

    new_act.sa_handler = shutdown;

    sigaction(SIGINT,&new_act,0);

    ...


이런 작업이 된 후에는 그냥 프로그램을 실행하고 종료시키기만 하면 gmon.out이라는 파일에 profiling 결과가 저장된다. 이 파일을 살펴보려면 gprof <실행파일명> gmon.out 과 같이 하면 된다. 


그러면 다음과 같은 결과를 볼 수 있다. (너무 길어서 부분적으로만 실었다.)


Each sample counts as 0.01 seconds.

  %   cumulative   self              self     total

 time   seconds   seconds    calls  ms/call  ms/call  name

 42.35      2.49     2.49 232115570     0.00     0.00  BJLEE::CCond::timedwait(int)

 25.00      3.96     1.47 232123443     0.00     0.00  BJLEE::CCond::unlock()

 10.88      4.60     0.64     1511     0.42     3.64  Switch::run(void*)

 10.71      5.23     0.63        7    90.00    90.00  BJLEE::CThread::CThread(bool)

  9.18      5.77     0.54 232123413     0.00     0.00  BJLEE::CCond::lock()

  1.87      5.88     0.11        1   110.00   110.00  BJLEE::CCond::wait()

  0.00      5.88     0.00     5579     0.00     0.00  BJLEE::CMutex::lock() const

[이하 생략]


유용한 링크 1 (gprof): http://whaleboy.tistory.com/158

유용한 링크 2 (gprof): http://unixgameserver.springnote.com/pages/678390.xhtml

gprof 출력 읽는 법: http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html#SEC5



저작자 표시 비영리 변경 금지

'Languages > C++' 카테고리의 다른 글

C++ Thread Pool  (1) 2012/09/25
valgrind, gprof를 사용한 프로파일링  (0) 2012/08/28
배열(array) 선언의 묘미  (0) 2012/06/12
MSVCP100D.dll  (0) 2011/02/17
[C/C++] 쓸만한 메모리 디버깅 툴이 없을 떄  (0) 2010/07/13
cgdb : 텍스트 기반의 gdb 인터페이스  (0) 2007/12/18
Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/423 관련글 쓰기

소중한 의견, 감사합니다. ^^

Languages/C++2012/06/12 18:10

배열은 흔히 동종의 데이터를 일렬로 저장하기 위해 많이 쓰인다. C/C++에서 배열 선언을 하는 방법을 모르시는 분들은 별로 없을 것이니 생략.


배열 선언이 기막힌 효과를 발휘하는 때는, 파일에 저장되는 데이터를 읽어오거나 네트워크를 통해 전송되는 데이터를 읽어오는 순간이다. 왜 그런지를 잘 모르시겠다면 이 글을 차근차근 읽어보자. 


네트워크를 통해 전송되는 데이터나, 파일로부터 읽어들이는 데이터는 항상 그 길이가 고정되어 있을 수도 있지만, 때로는 그 뒤에 선택적 데이터들이 붙어서 오는 경우가 있다. IP 헤더는 통상 20바이트로 알려져 있지만, 그 뒤에 선택적인 헤더 필드들이 더 올수도 있다. 그런 경우에는 데이터 량은 더 늘어난다. 





만일 여러분이 이런 데이터를 무작정 파일이나 네트워크 비트 스트림으로부터 읽어서, 다음 배열에 저장해 뒀다고 해 보자.


unsigned char buffer[4096];


이 데이터를 파싱하는 가장 손쉬운 방법은, 이 데이터의 형태를 모사하는 구조체를 만들어, 배열 이름을 해당 구조체에 대한 포인터로 형변환 하는 것이다. 


struct data_format {

    uint32_t length;

    uint32_t data;

    uint32_t optional_data_length;

};


위의 구조체에서 첫 번째 필드는 데이터의 전체 길이, 두 번째 필드는 데이터, 세 번째 필드는 선택적 데이터의 길이를 나타낸다. 선택적 데이터는 unsigned char 배열이라고 해보자.


optional_data_length == 0인 상황에서는 그냥 다음과 같이 해 버리면 파싱이 얼추 끝난다. (ntohs, ntohl 등을 해 줘야 할 수도 있는데, 그건 알아서들 하시기 바란다.)


data_format* dt = (data_format*) buffer;


저렇게 한 다음에 dt->length 하거나 dt->data 하면 된다. 그런데 문제는 optional_data_length > 0 인 순간 발생.


data_format이라는 구조체가 일종의 헤더라면, sizeof(data_format)의 크기 자체는 optional_data_length 값의 크기 여부에 상관 없이 항상 동일하게 유지하고, 그러면서도 optional_data_length > 0 일 때 그 뒤에 오는 선택적 데이터를 배열처럼 자유롭게 사용하고 싶다면 대체 어떻게 해야 하는가?


struct data_format {

    uint32_t length;

    uint32_t data;

    uint32_t optional_data_length;

    unsigned char* optional_data;

};


이렇게 하는 건 답이 될 수 없다. 이렇게 하는 순간 unsigned char* 포인터가 sizeof(data_format)에 반영되기 때문. 거기다 결정적으로 다음과 같이 하면 데이터 파싱이 정상적으로 되지 않는다. 


data_format* dt = (data_format*) buffer;


왜 그러한가? optional_data라는 포인터 변수에 저장되어야 하는 것은 실제로는 주소값인데, 이 주소값이 메모리 내의 어떤 주소를 가리키도록 설정되는 순간 그 주소값이 optional_data_length 다음의 메모리 영역을 덮어 써버리기 때문이다. 그 순간, 네트워크를 통해 전송된 데이터 가운데 4바이트가 잘못된 값으로 바뀌게 된다. 


그러므로 다음과 같이 해야 한다. 


struct data_format {

    uint32_t length;

    uint32_t data;

    uint32_t optional_data_length;

    unsigned char optional_data[];

};


놀랍게도, 이렇게 하면 sizeof(data_format)optional_data[] 를 추가하기 전과 같게 유지된다. 거기다, 다음과 같이 할 수 있다. 


data_format* dt = (data_format*) buffer;

printf("%d\n", dt->optional_data[3]);


포인터 선언 대신, 배열 선언을 구조체 내에 삽입하면 이처럼 큰 이득을 볼 수 있다. 데이터 파싱 작업이 놀랄만큼 간편해 진다는 점에서. (이런것도 까먹다니 나 원 참... ㅋㅋ)


저작자 표시 비영리 변경 금지

'Languages > C++' 카테고리의 다른 글

C++ Thread Pool  (1) 2012/09/25
valgrind, gprof를 사용한 프로파일링  (0) 2012/08/28
배열(array) 선언의 묘미  (0) 2012/06/12
MSVCP100D.dll  (0) 2011/02/17
[C/C++] 쓸만한 메모리 디버깅 툴이 없을 떄  (0) 2010/07/13
cgdb : 텍스트 기반의 gdb 인터페이스  (0) 2007/12/18
Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/420 관련글 쓰기

소중한 의견, 감사합니다. ^^

Languages/Java2011/07/12 18:45
SNMP4J: Cannot Handle InetAddress null response

SNMP4J를 사용해서 SNMP Response를 처리하다 보면, 분명히 있는 테이블의 있는 컬럼인데 그 값을 제대로 읽어오지 못하고 timeout이 나버리는 경우가 있습니다. 가령 다음 그림을 보시면...

SNMP4J library does not handle null values for InetAddress columns or scalar values. Therefore, if you do GET or GETNEXT or GETBULK for the fields, your request will be timed out. But most of the MIB browsers do not. They handle the case without error.


MIB 브라우저로 보면 InetAddress 타입의 Table Column 값이 없을 경우 null 응답을 받아서 잘 보여주고 있는 점을 보실 수 있습니다. 그런데 SNMP4J 라이브러리는 InetAddress 타입 컬럼의 값이 null인 경우를 제대로 처리하지 못합니다. 즉, InetAddress 타입 컬럼의 값이 null로 채워진 응답을 받으면, 그냥 무시해버립니다. 그러다보니 SNMP Agent에서는 응답을 보냈어도 클라이언트 쪽에서는 Timeout 나 버리는 경우가 발생하죠.

Above picture shows that MIB browsers correctly handle null-value for InetAddress fields. But, as I already tolds you, SNMP4j does not.



이 문제를 교정하려면 org.snmp4j.smi.IpAddress 클래스를 다음과 같이 수정해야 합니다.

To remedy this problem, you should fix org.snmp4j.smi.IpAddress class as follows.


이렇게 수정하고 실행해보면 이제 SNMP Agent가 InetAddress 타입 column의 값을 null로 보내도 오류 없이 처리하는 것을 보실 수 있습니다. SNMP4J는 소스코드와 함께 Maven 프로젝트 형식으로 배포되기 때문에, 간단히 재컴파일해서 쓰실 수 있습니다.

As SNMP4J is distributed as a Maven project with source codes, you can easily recompile and detour the timeout problems. :-)


Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/342 관련글 쓰기

소중한 의견, 감사합니다. ^^

Languages/Java2011/03/14 17:04
64비트 환경에 64비트 Java SE JDK를 깔고 Android SDK를 설치하려면 제일 먼저 마주치게 되는 문제가 바로
Java SE Development Kit (JDK) not found 라는 오류 메시지.

이 오류 메시지는 말 그대로 JDK를 찾지 못하는 데서 오는 오류. 이 오류에 대한 해결책은 http://codearetoy.wordpress.com/2010/12/23/jdk-not-found-on-installing-android-sdk/ 에 가 보면 장황하게 나열 되어 있으나 그 아래 부분에도 적혀 있듯이 가장 최근에 보고된 바에 따르면 이 문제를 해결하는 최고로 단순한 해결책은...




Back 버튼을 한 번 눌렀다가 다시 Next 버튼을 누르는 것. -_-



그러면 거짓말처럼 다음으로 넘어갑니다. http://codearetoy.wordpress.com/2010/12/23/jdk-not-found-on-installing-android-sdk/ 에 제시된 Registry-based solution은 그대로 안될 경우에 적용해 보실 것.

참고로 현재 인스톨을 시도하고 있는 플랫폼은 Windows 7 64bit 플랫폼인데, 여기에 SDK를 설치한 후 다른 모든 부분이 정상동작하는 지는 테스트 한 뒤에 다시 포스팅하도록 하겟습니다.

웹을 뒤지다 보면 안드로이드 폰을 Windows 7에 USB로 연결하면 인식이 안된다는 이야기를 접하게 되는데, 같은 문제에 직면하셨다면 http://www.luismajano.com/2010/08/11/install-android-usb-drivers-windows-7-6432-bit/ 이 링크를 참고하셔서 PDANet을 설치하는 것을 고려해 보실 것. 제품에 따라 인식이 바로 될 수도 있고 안 될 수도 있습니다. 안되는 경우에만 도전해보세요.



Posted by 이병준
TAG 64bit, Android, SDK

TRACKBACK http://www.buggymind.com/trackback/312 관련글 쓰기

소중한 의견, 감사합니다. ^^

  1. 우오옷~~ 탁월한 해결책입니다. ㅋㅋ

    2011/03/15 10:29 [ ADDR : EDIT/ DEL : REPLY ]
  2. 오우 정말 탁월합니다~ 저는 jdk 몇번이나 재설치해보고 검색해서 들어왔습니다.. ㅎㅎ

    2011/03/22 19:47 [ ADDR : EDIT/ DEL : REPLY ]
  3. 호잉

    아 이글 보고 해결했네요..
    이거때문에 하루를 삽질했는데.. 이방법 보고 해결되는거 보고 빵터졌습니다..

    2011/03/25 14:17 [ ADDR : EDIT/ DEL : REPLY ]
  4. 낭만고양이님도 IT 쪽인 것을 잊고 있었네요. 저도 스마트폰 APP 관련된 일을 하고 있어서 더 반갑네요...

    2011/04/18 11:30 [ ADDR : EDIT/ DEL : REPLY ]
  5. 요다

    치트키두 아니구..ㅎㅎ 감사합니다.

    2011/06/07 13:39 [ ADDR : EDIT/ DEL : REPLY ]
  6. 궁금이

    허무해요..... ㅠㅠ

    2011/07/04 11:58 [ ADDR : EDIT/ DEL : REPLY ]
  7. 봉팔이

    감사합니다... 한참 웃었네요..

    2011/07/11 12:43 [ ADDR : EDIT/ DEL : REPLY ]
  8. 롸키

    감사합니다^^ 이글 보고 너무 쉽게 해결했네요ㅋㅋㅋ
    큰웃음 주셔서 감사합니다ㅋㅋㅋ

    2011/07/14 12:19 [ ADDR : EDIT/ DEL : REPLY ]
  9. 와우 !

    검색해서 처음 본 글이 이거라 다행이에요 ! ㅋㅋㅋㅋ
    한참 고민할 뻔한걸 금방 해결했습니다 ~ 정말 감사해요 !
    근데 정말 웃겨요 ㅋㅋㅋㅋ

    2011/07/15 10:22 [ ADDR : EDIT/ DEL : REPLY ]
  10. 와하하..

    진짜 이 문제 때문에 더운 여름밤에 땀 삐질삐질 흘리고 있었는데...
    완전 빵 터지며 시원~하게 해결됐습니다..ㅋㅋㅋㅋ
    감사합니다..

    2011/07/23 02:44 [ ADDR : EDIT/ DEL : REPLY ]
  11. 굿`~

    당신은 능력자~~

    2011/08/02 00:10 [ ADDR : EDIT/ DEL : REPLY ]
  12. 헐....

    이걸로 며칠을 삽질을 했는데....jdk, 이클립스 미친듯이 깔고 지우고를 몇번이나 했는데 말이에요....ㅠㅜ

    2011/08/23 22:52 [ ADDR : EDIT/ DEL : REPLY ]
  13. 호호호

    정말 감사해요:) 이것땜에 완전 짜증났었는뎅

    2011/08/27 19:26 [ ADDR : EDIT/ DEL : REPLY ]
  14. 이럴수가

    이럴수가..... JDK를 세번 재 설치 했는데..ㅋㅋ
    너무 확실한 해결책입니다 ㅎㅎ

    2011/09/06 11:11 [ ADDR : EDIT/ DEL : REPLY ]
  15. 아아...

    감사합니다... 한번에 풀리네요 ㅠㅠ JDK만 몇번 재설치했는데..

    2011/09/10 07:42 [ ADDR : EDIT/ DEL : REPLY ]

Languages/C++2011/02/17 11:34

Sometimes when we distribute our program compiled by Visual Studio C++ 2010, the executables complain about the missing file, 'msvcp100d.dll'.

It is because of the standard C++ library MSVCPRTD.lib which is linked to your program. Basically, it imports the DLL 'msvcp100d.dll'. So, when the file is missing, it complains.

The most simple remedy for this problem is changing the property of your project.

Go to your project properties -> configuration attributes -> C/C++ -> code generation -> runtime library. Then, change the /MDd option to /MTd. (If you're compiling in release mode, you should change /MD to /MT). /MDd is default value for almost every Visual C++ Project, especially in Debug mode. Following is the screen shot of the project properties page.


Actually I'm using Korean version of Visual C++. So if you are using English version of Visual C++, above screen shot might not help you much. But I think the option tree shown above MUST have the same structure, so if you can follow the tree, I think I will be fine. :-)

Reference:
http://msdn.microsoft.com/ko-kr/library/abx4dbyh.aspx

'Languages > C++' 카테고리의 다른 글

valgrind, gprof를 사용한 프로파일링  (0) 2012/08/28
배열(array) 선언의 묘미  (0) 2012/06/12
MSVCP100D.dll  (0) 2011/02/17
[C/C++] 쓸만한 메모리 디버깅 툴이 없을 떄  (0) 2010/07/13
cgdb : 텍스트 기반의 gdb 인터페이스  (0) 2007/12/18
VI와 ctags  (0) 2007/12/18
Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/305 관련글 쓰기

소중한 의견, 감사합니다. ^^

Languages/Objective-C2011/02/08 11:02
뷰에 그려지는 내용은, 사실 CALayer 객체 위에 그려집니다. 뷰에 대해서 animation을 적용할 수 있듯이, CALayer 객체에 대해서도 animation을 적용할 수 있습니다.

CALayer에 대한 reference를 찾아보시면 아시겠습니다만, CALayer 객체에는 여러 개의 애니메이션 가능한 프라퍼티들이 있습니다. CALayer는 key-value coding을 지원하기 때문에, 키 값을 사용해 이들 프라퍼티에 애니메이션을 지정할 수 있습니다. 

이런 애니메이션은 적용하기도 간단합니다. 물론 복잡한 애니메이션을 구현하려면 좀 심각한 코딩을 해야 하겠습니다만, 레이어 하나 위에 이미지를 그리고 그 이미지가 깜빡거리게 만드는 정도는 대단히 간단하게 처리할 수 있습니다. 아래의 코드를 보시죠.

UIImage* img = [UIImage imageNamed: @"testimg.png"];

CALayer* sub = [CALayer layer];

sub.contents = img.CGImage;

sub.frame = ...

[self.layer addSublayer:sub];

CABasicAnimation* ani = [CABasicAnimation animationWithKeyPath:@"opacity"];

ani.duration = 2.0;

ani.repeatCount = HUGE_VALF;

ani.autoreverses = YES;

ani.fromValue = [NSNumber numberWithFloat:0.0];

ani.toValue = [NSNumber numberWithFloat:1.0];

ani.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];

[sub addAnimation:ani forKey:@"opacity"];


우선 새 레이어를 하나 만들구요. 그 위에 이미지를 얹습니다. 그런 다음 뷰의 backing layer 위에 해당 레이어를 얹습니다. 그런 다음 애니메이션 객체를 하나 만들어서 해당 애니메이션이 어떻게 실행될 지 지정한 다음에 해당 애니메이션을 새로 만든 레이어에 붙입니다.

유의해서 볼 부분은, 해당 에니메이션이 CALayer의 어떤 프라퍼티에 적용될 것인지를 적용하는 부분입니다. CALayer에는 opacity라는 프라퍼티가 있습니다. 이 프라퍼티는 레이어가 얼마나 투명한지를 결정하는 프라퍼티입니다. 위의 애니메이션은 그 지속 시간(duration)이 2초이며, 영원히(HUGE_VALF) 반복됩니다. opacity 값은 0.0에서 1.0으로 변했다가 다시 원래 상태로 돌아갑니다(autoreverse = YES). 

따라서 위에서 만들어 붙인 레이어에 그려진 이미지는 주기적으로 나타났다 사라지는 것 처럼 보이게 됩니다. 간단하죠?

레이어를 실제로 조작하는 것은 UIView를 가지고 프로그래밍하는 것에 비해 저수준(low-level) 연산처럼 보이기도 합니다만, 잘만 쓰면 뷰를 새로 만들지 않고도 프로그래밍할 수 있기 때문에, 성능 문제를 개선하는 데도 도움을 줄 수 있습니다.

Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/303 관련글 쓰기

  1. 공중곡예사의 생각  삭제

    2011/02/08 11:28TRACKBACK FROM bjlee72's me2day

    [iPhone/iPad] CALayer에 대한 animation

소중한 의견, 감사합니다. ^^

  1. 나그네

    sub.contents = img.CGImage 부분에서 Cannpt convert CGImage to objc_object in argument passing 이 떠요 ㅠㅠ

    2011/04/22 20:54 [ ADDR : EDIT/ DEL : REPLY ]
  2. 부따부따

    제가 깜박이는 애니메이션이 특정 버튼을 누르면 시작하고,

    이 버튼에 대한 프로세스가 끝나면 원래 상태로 돌아가는 것을 구현 하려고 하는데요

    실시간 영상을 처리하는 가운데 overlay에 대해 해당 애니메이션을 지정해줬는데

    좀처럼 쉽게되질 않네요 도움 주시면 감사하겠습니다.

    2011/07/15 17:36 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Objective-C2011/02/08 09:34
보통 비동기적으로 프로그래밍 한다고 하면 많은 분들이 쓰레드 프로그래밍을 떠올립니다. 네. pThread같이 비교적 알기쉬운 쓰레드 메커니즘을 사용하면 아주 간단하게 비동기적 프로그래밍을 할 수 있죠. 그런데 iPhone이나 iPad는 좀 더 간단한 메커니즘을 제공합니다. 쓰레드 프로그래밍을 단 한줄도 하지 않고서도, 시스템이 제공하는 쓰레드의 도움을 받아 비동기 프로그래밍을 할 수 있죠. 그리고 이렇게 프로그래밍 하면 프로그램을 구성하는 클래스 사이에 의존 관계를 꽤 간단히 끊어버릴 수 있습니다.

결론부터 이야기하자면 NSNotification을 이용하자는 것인데요. 프로그램 내의 한 모듈에서 다른 모듈로 어떤 사건의 발생을 통지하여, 보내는 쪽 클래스의 실행 궤적과 받는 쪽의 실행 궤적이 비동기적으로 동시 수행될 수 있도록 하자는 것이죠. 

NSNotification을 이용할 때 많은 분들이 [NSNotificationCenter defaultCenter]로 얻어낸 NSNotificationCenter 객체의 postNotification 메소드를 호출하는 것을 떠올리시는데, NSNotificationCenter의 클래스 레퍼런스에도 나와 있습니다만, NSNotificationCenter를 직접 이용하면 동기적인 처리밖에 안됩니다. 

A notification center delivers notifications to observers synchronously.

무슨 이야긴가 하면, postNotification을 호출하면 해당 notification에 observer로 등록한 객체의 callback 루틴이 수행 종료될 때 까지 그 자리에서 가만히 대기하고 있게 된다는 것입니다. 그러니, 비동기적으로 프로그래밍해서 어느 정도의 병행성(concurrency)을 얻고자 한다면 이렇게 프로그래밍을 하면 안되죠. 

따라서, NSNotificationQueue를 이용해야 합니다.

Notification을 날리는 쪽에서는 다음과 같이 하면 됩니다.

[[NSNotificationQueue defaultQueue] enqueueNotification:[NSNotification notificationWithName:@"dataLoad" object:nilpostingStyle:NSPostWhenIdle];


위의 코드는 NSNotificationQueue에 "dataLoad"라는 이름의 NSNotification 객체를 넣는 코드입니다. 이 객체는 언제 notification observer에게 전달될까요? manual에 따르면, 'run loop가 idle할 때'로 되어 있습니다. NSPostWhenIdle로 지정했으니까요. 

Notification을 받을 쪽 코드는 NSNotificationCenter를 사용할 때와 같습니다. 다음과 같이 하면, "dataLoad"라는 이름의 Notification이 떴을 때 _dataLoad 메소드가 callback으로 수행되도록 할 수 있습니다.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_loadData) name:@"dataLoad" object:nil];


한가지 주의할 것은 Notification을 queue에 집어넣을 때 NSPostNow라고 하면 비동기적으로 notification이 날아가는 게 아니라 동기적으로 처리된다는 점이에요. reference에 실린 아래의 글을 인용하는 것으로, 이번 글은 마치도록 하죠.

A notification queued with NSPostNow is posted immediately after coalescing to the notification center. You queue a notification with NSPostNow (or post one with postNotification:) when you do not require asynchronous calling behavior. For many programming situations, synchronous behavior is not only allowable but desirable: You want the notification center to return after dispatching so you can be sure that observing objects have received and processed the notification. Of course, you should use enqueueNotification... with NSPostNow rather than use postNotification: when there are similar notifications in the queue that you want to remove through coalescing.


물론, 이런 질문도 있을 수 있겠어요. 그럼 그냥 NSPostNow를 써서 큐에 넣는 대신, NSNotificationCenter의 postNotification 메소드를 부르면 되지 않느냐? 그런데 그렇게 하면 유사 이벤트 병합기능을 써먹을 수 없게 되죠. NSNotificationQueue는 (위에도 적혀 있습니다만) 같은 이름의 이벤트가 큐에 들어오면 합쳐버리거든요.

Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/302 관련글 쓰기

소중한 의견, 감사합니다. ^^

  1. 좋은글 잘 읽었습니다.
    그런데, nsthread 에서는 NSPostNow 외에는 호출이 안되네요..
    혹시 가능한 방법을 찾을 수 있겠습니까?

    2011/02/21 18:27 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Objective-C2011/01/20 17:35
UIScrollView를 사용하여 Zooming, 페이지 단위 스크롤링을 고려하려면 주의해야 할 것이 있습니다. 그건 바로 "하나의 UIScrollView 하위 클래스에 Zooming 기능과 스크롤링 관련 기능을 동시에 우겨넣으면 안된다"는 것이죠.

같이 우겨넣어 프로그래밍을 할 수 있을지도 모르겠습니다만, 아마 "굉장히" 프로그래밍하기 어려울 겁니다.

그럼 확대/축소도 되고 좌우로 스크롤링도 되는 PDF 뷰어 같은걸 구현하려면 어떻게 해야 하나요?

가장 간단한 방법은 스크롤 뷰를 중첩(nesting)하는 겁니다. 안쪽 스크롤 뷰에는 확대/축소 기능을 넣고, 바깥쪽 스크롤 뷰에는 좌우 스크롤 기능을 구현하는 것이죠.

확대 축소 기능에 대해서는 지난번 글에 잠깐 다루었는데, 좌우 스크롤 기능과 함께 사용하려면 지난번 글처럼 기능을 무조건 단순화 하면 곤란합니다. 좌우 스크롤하려면 다음과 같은 요구사항을 만족해야 하기 때문이죠.

(1) 끊김없는 좌우 스크롤
(2) 다음번에 나타날 화면의 caching
(3) 페이지 단위 좌우 스크롤

(3)번은 만족시키기가 간단합니다. 스크롤 뷰의 pagingMode를 YES로 설정하면 되거든요. 그렇게 하면 화면 크기를 (엄밀하게 말하면 스크롤 뷰의 bounds 속성 값) 페이지 크기로 인식하고 스크롤을 합니다.

(1)을 할떄는 생각할 것이 좀 많습니다. '끊김 없는'이라는 것은 '좌우 스크롤을 할 때 멈칫 거리는 일이 없어야 함'을 의미하거든요. 모든 페이지를 하나의 스크롤 뷰의 서브 뷰로 몽땅 다 로딩해 버린다음에 스크롤 하면 별문제겠습니다만 그렇게 하는 건 오버헤드가 너무 큽니다. 그러니 보통은 좌우 스크롤을 구현할 때 이렇게 해야 하죠.

a. 스크롤 뷰의 contentSize는 스크롤 뷰 안에 '모든 페이지'를 우겨넣는다고 가정하고 잡습니다.

그래야 하는 이유는? 그래야 스크롤 뷰가 '어디서부터 어디까지 스크롤 해야 하는지' 제대로 인식할 수 있기 때문이죠.

b. 스크롤 뷰 안에 컨텐트를 (그러니까 서브 뷰들을) 추가할 때에는 딱 세 개 (혹은 다섯 개?) 만 합니다.

화면에 보이는 것은 그 중 가운데고, 왼쪽 오른쪽에 있는 것들은 각각 손가락을 오른쪽으로 긁었을 때, 왼쪽으로 긁었을 때 화면에 나타나야 하는 것들이죠.

c. 스크롤 뷰가 실제로 스크롤 되면, 위의 세 개 뷰 중에 왼쪽에 있는 것을 오른쪽으로 옮기고, 가운데 있는 것은 오른쪽, 맨 오른쪽에 있는 것은 폐기처분하거나 (release) 아니면 맨 왼쪽으로 돌려서 재활용합니다. (재활용 할 때에는, 재활용 한 뷰에 '그려졌던' 내용이 잠시라도 화면에 다시 표시되는 일이 없도록 주의해야 합니다.)

이렇게만 하면 서브뷰 딱 세 개만 가지고도 가로 방향 스크롤을 구현할 수 있습니다. (세로 방향도 마찬가지.) 굉장히 간단하게 들리는데, 무슨 문제가 있나요?

보통 '왼쪽으로 스크롤이 일어났다' 아니면 '오른쪽으로 스크롤이 일어났다'는 뭘 가지고 판단하나요? 스크롤 뷰의 delegate 함수 중에 scrollViewDidEndDecelerating 함수를 구현해서 할 수도 있겠지만, 테스트 해 본 바에 따르면 굉장히 페이지를 빠른 속도로 넘기는 경우에 문제가 발생할 가능성이 있습니다.

그래서 보통은 스크롤 뷰의 contentOffset.x 값이 오른쪽이나 왼쪽 페이지의 중간 지점을 넘어갔을 때 '스크롤이 일어났다'고 판단하게 되는데, 그러면 그 때 무슨 일을 해야 하느냐 하면 앞서 말한 대로 페이지들의 위치를 옮기고 재배치하는 등의 작업을 해 줘야 하거든요.

근데 재배치 될 뷰 안의 drawRect나 drawLayer 함수 안에서 뭔가 굉장히 많은 일을 하게 되면 바로 그 순간에 스크롤이 멈칫거리는 현상이 발생하게 됩니다. (Apple의 이미지 스크롤 관련 예제에서도 동일한 문제가 발생해요.) PDF 페이지를 렌더링 하는 것도 PDF 파일에 따라서는 막대한 오버헤드를 유발하기 때문에 마찬가지죠.

이 문제를 극복하기 위해서는 drawRect는 가급적 피하고 drawLayer를 쓰며, layer 클래스로는 CATiledLayer를 쓰는 것이 바람직합니다. CATiledLayer는 렌더링을 할 때 쓰레드를 만들어서 백그라운드에서 렌더링을 하기 때문에, drawLayer가 스크롤을 멈칫거리게 한다던가 하는 일이 적어요.

물론 화면에 뜨는 스피드는 좀 느려지긴 합니다. 이런 trade-off에 대해서는 개발자가 적절히 생각하고 선택을 해야겠죠. (저는 못해 드립니다. ㅎㅎ)
Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/300 관련글 쓰기

  1. 공중곡예사의 생각  삭제

    2011/01/20 17:38TRACKBACK FROM bjlee72's me2day

    [iPhone/iPad] UIScrollView를 사용한 zooming 및 페이지 단위 스크롤링

소중한 의견, 감사합니다. ^^

Languages/Objective-C2011/01/17 16:18
보통 Zoom 효과를 구현할 때 많이 사용하는 것이 UIScrollView입니다. 특히 PDF의 확대 효과에 관해서라면 Apple Developer Center에서 제공하는 ZoomingPDFViewer 예제를 많이들 참고하시죠.

이 예제에서는 세 개의 뷰를 사용해서 Zoom 효과를 구현합니다. 일단 백그라운드에 pdf를 먼저 그리구요. 그 앞에 zooming을 구현할 pdf view 두 개를 둡니다. 하나는 확대 이전 뷰, 하나는 확대 다음 뷰. (간단히 설명하자면 그렇습니다) zooming이 진행중인 동안에는 확대 이전 뷰를 사용해서 rendering하다가, 확대가 끝나고 나면 확대 다음 뷰를 확대된 크기만큼 새로 만들어서 (그러기 위해서는 얼마나 확대되었는지를 기억하고 있어야 합니다) 뷰가 보이는 계층 맨 위쪽에 붙여 버리는 것이죠. 

그런데 이 세 개의 뷰가 정말 필요한 걸까요? 이건 ZoomingPDFViewer 예제를 처음 봤을때 제 스스로 던진 질문이었습니다. 이 문제를 풀기 위해서, 과연 zooming이 진행 중일때 scroll view의 content view에 해당하는 뷰 (그러니까 확대 대상인 뷰)에 어떤 일이 벌어지는 지를 살펴봤습니다.

결론부터 이야기하자면, content view의 frame 속성(CGRect 값입니다)이 끊임없이 변합니다. 하지만 content view의 bounds 속성 값은 전혀 변하지 않습니다. 그러니, 다음과 같이 했다고 해 봅시다.

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView

{

    return pdfView;

}


위의 코드는 확대 대상 content view를 scroll view에게 반환하기 위해 구현하는, UIScrollViewDelegate 프로토콜 함수의 코드입니다. pdfView는 self.bounds를 사용해 자기 뷰 내부를 렌더링합니다. 그런데 frame 객체의 크기는 zooming을 할수록 자꾸 커진단 말입니다. 그래도 self.bounds에는 변함이 없으니, 뷰가 커지면 커질수록 뷰 안에 그려진 내용은 해상도가 떨어져 보이게 됩니다. 

그럼 대체 scroll view는 self.bounds는 가만 냅두면서 self.frame은 어떻게 계속 뻥튀기하는 걸까요?

답은 pdfView의 (그러니까 UIView를 상속한 뷰 클래스 객체라면 누구나 갖고 있는) transform 프라퍼티에 숨어 있습니다. 이 프라퍼티의 값이 CGAffineTransformIdentity가 아닐 경우, frame 프라퍼티의 값은 계산된 값입니다. 보통은 frame이나 bounds나 값이 동일하지만, transform 프라퍼티의 값이 CGAffineTransformIdentity가 아닌 경우에는 frame은 bounds 프라퍼티에 transform을 적용해서 구해진 CGRect 값이 되는 것이죠.

그래서 다음과 같은 코드 안에서 frame의 값과 bounds의 값을 찍어보면 그 값이 서로 다르게 나옵니다.

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale

{

// ...

NSLog(@"scale = %f, pdfScale = %f", scale, pdfScale);

NSLog(@"frame = %f, %f, %f, %f", view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);

NSLog(@"bounds = %f, %f, %f, %f", view.bounds.origin.x, view.bounds.origin.y, view.bounds.size.width, view.bounds.size.height);

// ...

}


그러니까 결국 UIScrollView는 content view의 transform 프라퍼티의 값을 적절히 변경해서, content view의 확대/축소를 구현한다는 결론을 내릴 수 있는 것이죠. (거기다 contentOffset 프라퍼티까지 함께 수정/적용해서 말이죠.)

자. 그럼 여벌의 view 없이도 확대 동작을 구현하려면, ZoomingPDFViewer의 코드를 어떻게 개선해야 하는 걸까요? transform 프라퍼티를 적당히 매만지도록 코드를 변경하면 되겠군요. 지금부터는 여러분께서 ZoomingPDFViewer의 코드를 같이 보고 계신다고 가정하고 설명을 진행하겠습니다. 우선, backgroundImageView를 scroll view의 서브 뷰로 붙이는 부분을 아예 없애 버립니다. 필요 없으니까요.

그런 다음, oldPDFView를 참조하는 코드도 전부 없애 버립니다. 역시 필요 없습니다. (그러면 아마 scrollViewWillBeginZooming 안의 코드가 전부 날아가 버릴겁니다.) 

자. 그런 다음에는 pdfView만 필요하니까, PDFScrollView의 initWithFrame 메소드 안에서 pdfView를 생성하는 부분의 코드를 다음과 같이 고칩니다.

pdfView = [[TiledPDFView alloc] initWithFrame:pageRect andScale:pdfScale];

[pdfView setPage:page];

[self addSubview:pdfView];


이렇게 한 다음에, scrollViewDidEndZooming의 코드를 다음과 같이 고칩니다.

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView 

         withView:(UIView *)view atScale:(float)scale

{

// set the new scale factor for the TiledPDFView

pdfScale *=scale;

pdfView.bounds = CGRectMake(0,0,

                      view.frame.size.width, view.frame.size.height);

        // make the frame size to the bounds size

view.transform = CGAffineTransformIdentity

       

[pdfView setNeedsDisplay];

}


그런 다음, TiledPDFView의 initWithFrame의 코드를 다음과 같이 수정합니다.

tiledLayer.levelsOfDetail = 1;

tiledLayer.levelsOfDetailBias = 1;

tiledLayer.tileSize = CGSizeMake(512, 512);


detail 레벨을 1로 주어야 줌이 된 다음에 해상도가 상승되는 것이 보이게 되더군요. 너무 높으면 마치 줌이 되는 동안에서 해상도가 상승되다가, 마지막에 뷰가 쓸데없이 다시 그려지는 것 처럼 보입니다.

이렇게 한 다음에 실행시켜 보면, 이제 뷰 하나만 가지고도 PDF 문서의 zooming, 그러니까 확대/축소가 정상적으로 처리됩니다. scrollViewDidZooming 안에서 bounds의 값이 frame과 같도록, transform 프라퍼티의 값을 강제로 CGAffineTransformIdentity로 변경했기 때문입니다. 

자. 그런데 한가지 문제가 남았습니다. 뭔가요?

네. 아마 이 예제를 한번이라도 실행해 본 분이라면 아실텐데요. 이 문제는 이전 버전이나 현 버전이나 똑같습니다. 이 문제는 content view의 크기가 계속 변하기 때문에 발생하는 문제입니다. content view가 예전과 동일한 instance를 가리키고 있고 bounds의 값에도 변화가 없는 경우에는 scrollViewDidEndZooming 메소드의 atScale:(float)scale 인자값 'scale'의 값이 항상 'content view의 원래 크기 대비, 현재 크기의 비율'을 나타내게 되는데, content view instance가 확대할 때 마다 계속 달라지거나 (종전 예제의 경우) bounds의 값이 똑같이 유지되지 않는 경우(현 예제의 경우)에는 인자값 'scale'의 값이 '종전 크기 대비, 현재 크기의 비율'을 나타내게 된다는 것이죠. 

따라서 scroll view의 maximumZoomScale 프라퍼티나 minimumZoomScale 프라퍼티 값을 제한하는 것으로는 '어디까지 확대하고 어디까지 축소할 것인지'를 결정할 수 없게 됩니다. 

그러니 scrollViewWillBeginZooming과 scrollViewDidEndZooming 메소드 안에서 pdfScale 값을 가지고 minimumZoomScale 프라퍼티와 maximumZoomScale 프라퍼티를 가지고 꽁수를 좀 부려야 됩니다. 

이에 관해서는 각자 알아서 해보시는 것으로 하고, (연습문제라고 해도 좋겠습니다. ㅎㅎ) ZoomingPDFViewer 예제 분석과 개선에 관한 이번 글은 마치도록 하죠. 

Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/299 관련글 쓰기

  1. 공중곡예사의 생각  삭제

    2011/01/17 16:35TRACKBACK FROM bjlee72's me2day

    [iphone/ipad] UIScrollView를 사용한 Zooming PDF Viewer의 구현

소중한 의견, 감사합니다. ^^

  1. 띵호

    좋은 글 잘 읽었습니다.

    님이 남겨주신 문제가 딱 제가 가지고 있는 문제네요 ㅋㅋㅋㅋ
    근데 pdfScale를 가지고 계속 씨름 중인데. 혹시..아닌가요??ㅜㅜ

    2012/05/29 02:27 [ ADDR : EDIT/ DEL : REPLY ]
    • 띵호

      와우 거의 한시간 동안 씨름해봤는데....
      문제를 해결했습니다.
      머가 실마리인지 몰랐는데 minimumzommScale 과 maximumZoomScale 를 가지고 노니 해결되네요 ㅋㅋ

      감사합니다 수고하세요~~

      2012/05/29 03:00 [ ADDR : EDIT/ DEL ]

Languages/Objective-C2011/01/11 15:18
아이패드 보급이 늘어가면서 아이패드 위에 PDF 뷰어같은걸 구현하시는 분들도 많이 늘어가는 것 같군요. 그런 소프트웨어 구현할 때 가장 많이 사용하게 되는 것이 CGContextDrawPDFPage라는 함수죠. 이 함수는 현재의 드로잉 컨텍스트 위에 PDF 페이지를 뿌립니다.

이 함수의 한가지 문제는 호출할 때 마다 굉장히 많은 메모리를 계속 잡아먹는다는 점이죠. 그래서 PDF 파일을 열어놓은 상태로 모든 페이지를 전부 다 보게 되는 경우, 메모리 관련 경고가 뜨게 될 수 있습니다.

이 문제를 해결하는 한가지 방법은 메모리 점유율이 증가하거나 메모리 관련 경고가 뜨는 경우 (그러니까 다시 말해 메인 View Controller의 didReceiveMemoryWarning이 호출되는 경우) PDF 파일을 닫았다가 다시 여는 것이 되겠습니다. 

그런데 그런 코드를 작성하려면 PDF 파일에 대한 레퍼런스 (CGPDFDocumentRef)를 들고 있는 클래스를 어디 한 군데로 제한하는 것이 바람직하죠. 그럴 때에는 Singleton 패턴을 써야 한다는 것은 아마 많은 분들이 알고 계시겠습니다만...

그런데 그냥 CGPDFDocumentRef만 singleton 패턴으로 관리하면 땡인걸까요?

그렇지 않습니다. 한 쪽에서 이 패턴 객체를 통해 레퍼런스를 가져가서 열심히 뭔가 작업을 하고 있는데, 다른 한쪽에서 이 레퍼런스를 닫아버린다거나 하게 되면 프로그램이 당장 돌아가시게 되죠. 

그러니까 결국은 Singleton 패턴을 통해 CGPDFDocumentRef에 대한 접근 자체를 통제해야 합니다. 몇 가지 방법이 있겠습니다. 레퍼런스 카운터를 통해서 모두가 document ref를 전부 다 썼다는 것이 보장되기 전까지는 닫지 않는다거나 하는 것도 한가지 방법이죠.

저는 메모리를 얼마 이상 쓰지 않아야 한다는 요구조건이 있어서, 다음과 같은 싱글턴 클래스를 만들고 block을 통해서 해당 PDF 문서에 대한 작업을 구현하도록 하는 클래스를 만들어서 쓰고 있습니다. 소스코드를 보시면...

//

//  PDFSingleton.h

//  Muine

//

//  Created by bjlee on 11. 1. 11..

//  Copyright 2011 buggymind.com. All rights reserved.

//


#import <Foundation/Foundation.h>



@interface PDFSingleton : NSObject {


}


+ (NSUInteger) do:(NSUInteger(^)(CGPDFDocumentRef))blk;


@end



//

//  PDFSingleton.m

//  Muine

//

//  Created by bjlee on 11. 1. 11..

//  Copyright 2011 buggymind.com. All rights reserved.

//


#import "PDFSingleton.h"



@implementation PDFSingleton


static CGPDFDocumentRef _pdf = NULL;

static int requestCount = 0;


+ (NSUInteger) do:(NSUInteger(^)(CGPDFDocumentRef))blk {


@synchronized ( self ) {

if ( ++requestCount == 6 ) {

CGPDFDocumentRelease(_pdf);

_pdf = NULL;

requestCount = 1;

}

if ( _pdf == NULL ) {

CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("xxx.pdf"), NULL, NULL);

_pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);

CFRelease(pdfURL);

}


return blk(_pdf);

}

}



@end


PDF 문서 페이지가 여섯번 이상 요청되면 강제로 닫았다가 다시 열도록 했구요. 해당 pdf 문서에 대한 사용자 작업은 block으로 처리했습니다. 사용 예를 보시면...

// ctx의 선언은 이 위 어딘가에 있음 ㅎ

[PDFSingleton do:^(CGPDFDocumentRef pdf) {

CGPDFPageRef page = CGPDFDocumentGetPage(pdf, index);

// ...

CGContextDrawPDFPage(ctx, page);

return (NSUInteger)0;

}];


그런데 이 코드에는 문제가 있습니다. 뭘까요?

네. 어떤 한 쓰레드에서 해당 PDF 파일을 사용해 뭔가 심각하고 오래 걸리는 일을 하고 있으면, 다른쪽에서는 그 일이 끝날 때 까지 무작정 기다려야 합니다.

그러니 동시성을 활용해 캐싱이라던가 프리패칭(pre-fetching)같은 것을 하고 싶다면, 이렇게 하면 곤란하겠죠. 연습문제 삼아, 각자 이 문제를 고민해보시기 바랍니다. :-)
Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/298 관련글 쓰기

  1. 공중곡예사의 생각  삭제

    2011/01/11 15:21TRACKBACK FROM bjlee72's me2day

    [iphone/ipad] CGContextDrawPDFPage가 메모리를 잡아먹는다?

소중한 의견, 감사합니다. ^^

Languages/Objective-C2011/01/04 18:46
이건 비단 MPMoviePlayerController에 국한된 이야기는 아닐 수 있습니다. 다만 이 문제가 '비동기적'인 오류를 유발할 수 있으며, 디버깅이 굉장히 까다로운 문제를 야기할 수 있다는 점을 지적하고 싶습니다. 생각해보면 매우 단순한 문제인데, 깨닫기가 어렵죠. 거기다 iOS 3.2버전과 iOS 4.0 버전에서 동작 방식이 다릅니다. (특히 시뮬레이터에서는 더더욱요.)

간단히 문제를 살펴보죠. 제가 MoviePlayer라는 UIView 클래스의 하위 클래스를 만들었다고 치겠습니다. 이 클래스의 생성자 안에서 MPMoviePlayerController의 객체를 하나 만든 다음에, 이 객체를 사용해서 영화를 재생하기 전에 다음과 같은 짓을 했다고 해 보죠.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movieStopped) 
                                                              name:MPMoviePlayerPlaybackDidFinishNotification 
                                                             object:moviePlayer];

이렇게 하면, 영화 재생이 끝나면 self, 그러니까 MoviePlayer 객체의 movieStopped 메소드가 호출됩니다. Notification 메커니즘 덕분이죠. 여기서 moviePlayer는 MPMoviePlayerController 객체입니다.

그런데, iOS 3.2에서는 영화가 정상적으로 재생이 끝난 경우에만 notification이 발생하고, 재생 도중에 moviePlayer에 대해서 [moviePlayer stop]을 날린 경우에 대해서는 Notification이 발생하지 않았었어요.

하지만 iOS 4.0에서는 이야기가 좀 다릅니다. [moviePlayer stop]한 경우에도 Notification이 발생하거든요. (실제 장비에서는 모르겠지만 적어도 시뮬레이터에서는 발생합니다.)

그러니, [moviePlayer stop]을 코드에 넣으려고 하고 있다면, 다음과 같이 해 주어야 합니다. 아니면 movieStopped 메소드 안에 removeObserver를 호출하는 부분을 두거나요. 그래야 안전합니다.

[[NSNotificationCenter defaultCenter] removeObserver:self];

[moviePlayer stop];


왜 그럴까요? 만약에 [moviePlayer stop]을 날린 이후에 MoviePlayer 객체를 dealloc했다고 해 보죠. 그러면 아까 addObserver 할 때 인자로 넘겼던 self가 온데간데 없이 메모리에서 사라지게 되거든요. 그러니 removeObserver를 해주지 않으면 사라진 객체에게 NSNotification이 날아가게 되는 것이죠.

그러면 어떻게 되나요?

(잠시 침묵)

네. 사라진 객체가 메시지를 받기 때문에 프로그램이 쭈욱 뻗습니다. (죽는단 소리죠.)

생각해보면 간단하기 짝이 없는데, 몇 시간의 삽질 과정이 없으면 깨닫기 힘든 경우도 많습니다. 이 글이 여러분의 삽질을 조금이라도 줄여주었으면 좋겠군요. :-)


Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/297 관련글 쓰기

소중한 의견, 감사합니다. ^^

Languages/Objective-C2011/01/04 16:19
[이전 글에 이어서..]

그럼 새로운 UIView 애니메이션은 어떻게 써먹느냐. Ruby 같은 프로그래밍 언어를 보면 함수를 실행할 때 그 함수에 다른 함수 바디를 동적으로 만들어서 넘길 수도 있도록 허용하고 있는데요. Objective-C에서도 이제 그런 식의 프로그래밍이 가능합니다. 우선 developer.apple.com에서 퍼온 API 설명부터 한번 보죠.

animateWithDuration:animations:completion:

Animate changes to one or more views using the specified duration and completion handler.

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
Parameters
duration

The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them.

animations

A block object containing the changes to commit to the views. This is where you programmatically change any animatable properties of the views in your view hierarchy. This block takes no parameters and has no return value. This parameter must not be NULL.

completion

A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL.

Discussion

This method performs the specified animations immediately using the UIViewAnimationOptionCurveEaseInOut and UIViewAnimationOptionTransitionNone animation options.

For example, if you want to fade a view until it is totally transparent and then remove it from your view hierarchy, you could use code similar to the following:


이 함수는 static method이고, 첫 번째 인자로는 Animation 지속시간, 두 번째 인자로는 애니매이션이 수행되는 동안 행해질 작업에 대한 코드 블럭, 그리고 세 번째 인자로는 애니메이션이 끝나면 실행될 코드 블럭이 넘어갑니다.

대충 어떻게 써먹느냐 하면...

[UIView animatedWithDuration:0.2
            animations:^{view.alpha = 0.0;}
            completion:^(BOOL finished){ 
                               if ( finished)
                                  [view removeFromSuperView];
                             }];

위의 코드는 0.2 초 에 걸쳐, 어떤 뷰를 서서히 투명하게 만들고, 투명화 작업이 끝나면 그 뷰를 부모 뷰에서 떼 버리는 코드입니다. 블럭의 시작은 ^으로 표시하고, 인자의 목록은 () 안에, 블럭 코드 바디는 {} 안에 둔다는 것을 알 수 있습니다.

이렇게 코딩하면 앞서 봤던 [UIView beginAnimations], [UIView commitAnimations]를 통해 구현한 경우보다 코드 사이즈가 줄어듭니다. 애니메이션 종료시 호출될 메소드를 따로 구현해 둘 필요가 없거든요. 블럭 문법에 익숙한 분들에게는 코드도 훨씬 더 깔끔해 보이죠.



Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/296 관련글 쓰기

소중한 의견, 감사합니다. ^^