반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

Corona SDK 프로그래밍 테크닉 1

2011. 11. 8. 20:54 | Posted by 솔웅


반응형

이번 시간하고 다음 시간엔 애플리케이션 퍼포먼스 및 메모리 관리에 대해 다룹니다.

아직까지 모바일 기계는 작기 때문에 CPU나 Memory 에 한계가 많습니다. 이로인한 속도 저하나 에러방지를 위해 신경써야할 퍼포먼스 및 메모리 관리에 대해 알아봅니다.


Performance and Optimization


앱 개발 할 때 항상 앱의 퍼포먼스에 대해 신경을 써야 합니다. 특히 아직까지 모바일은 메모리나 배터리 성능에 많은 한계가 있습니다. 

코로나에서는 garbage collection을 통해서 많은 부분을 자동으로 관리해 주고 있습니다.


코로나에서의 메모리 관리는 아래 다섯가지에 대해 고민 하면 됩니다.

1. Display objects

2. Global variables

3. Runtime Listeners

4. Timers

5. Transitions


메모리 낭비와 관련해서 체크할 방법이 있습니다.

아래 코드를 main.lua 파일 맨 아래에 넣으세요.

(main.lua말고 다른 파일에 넣어도 됩니다.)


local monitorMem = function()


    collectgarbage()

    print( "MemUsage: " .. collectgarbage("count") )


    local textMem = system.getInfo( "textureMemoryUsed" ) / 1000000

    print( "TexMem:   " .. textMem )

end


Runtime:addEventListener( "enterFrame", monitorMem )


MemUsage는 Lua 메모리를 나타냅니다. 그리고 TexMem은 그래픽 등이 사용하는 메모리를 나타냅니다. 여기서 중요한 것은 숫자가 얼마나 크냐가 아니고 앱이 진행 되면서 이 숫자가 어떻게 변하느냐를 보시는 겁니다.

스크린 1에서 2로 갔을 때 메모리 변화와 다시 1로 왔을 때의 메모리 변화를 보실 필요가 있습니다. 계속 늘어나기만 하면 어디에선가 메모리 누수가 있다는 얘기입니다.


Display Objects


디스플레이 오브젝트와 관련된 메모리 누수를 막는 방법은 객체를 정리할 때 항상 nil을 대입하는 겁니다.

display.remove( redBall )

redBall = nil



혹시 루핑을 돌면서 계속 객체를 새로 만들 경우 객체 remove를 하지 못하는 경우가 있는데요. 개발자는 항상 객체의 탄생과 소멸까지 책임 져야 합니다.


Global Variables


객체 생성할 때 local 을 붙이지 않으면 그것은 글로벌 변수입니다. 가능한 글로벌 변수보다 로컬 변수를 사용하는 것이 메모리 누수를 막는 방법입니다. 만약 사용하더라도 더이상 그 변수가 필요하지 않으면 반드시 nil 처리를 합니다. 그러면 루아의 garbage collector가 이것을 정리할 수 있습니다.


Runtime Listeners


display object를 remove할 때 이 객체에 붙어 있는 listener도 메모리에서 함께 해제 됩니다. Runtime에 리스너를 달게 되면 개발자가 직접 그 리스너를 remove할 때까지 메모리 해제 되지 않습니다. Runtime/enterFrame 리스너에서 일반적으로 일어나는 메모리 누수는 어느 한 screen에 Runtime 리스너가 달렸는데 그 스크린을 떠날때 이 리스너를 없애지 않는 경우 입니다. 이 경우 다음에 다시 이 스크린으로 돌아오게 되면 Runtime 리스너가 두개가 되기 때문입니다. 


Timers and Transitions


Timer와 Transition이 일반적으로 가장 crash를 많이 유발할 개연성이 있습니다. 예를 들어 time 하고 timer/transition start 사이에 end가 와 버리면 충돌 하게 됩니다. 또한 이것을 nil 처리 하지 않으며 이것은 메모리에 계속 남아 있으면서 좀 더 많은 메모리 누수를 유발합니다.


이것을 관리하는 효율적인 방법은 모든 timer와 transition들을 하나의 테이블(배열)로 관리하는 겁니다. 그러면 더이상 아무런 타이머와 트랜지션이 필요 하지 않을 때 한번에 nil처리 할 수 있습니다.


timerStash = {}

transitionStash = {}


function cancelAllTimers()

    local k, v


    for k,v in pairs(timerStash) do

        timer.cancel( v )

        v = nil; k = nil

    end


    timerStash = nil

    timerStash = {}

end


--


function cancelAllTransitions()

    local k, v


    for k,v in pairs(transitionStash) do

        transition.cancel( v )

        v = nil; k = nil

    end


    transitionStash = nil

    transitionStash = {}

end


아래는 timer와 transition 을 생성할 때의 예 입니다.

timerStash.newTimer = timer.performWithDelay( ...


transitionStash.newTransition = transition.to( myObject { ...


만약 cancel하지 말아야 될 타이머나 트랜지션은 이 테이블에 포함 시키지 않을 수 있습니다.


효과적으로 메모리 사용하기 


- 메모리 누수 방지하기 

- 가능한 리소스 파일 크기를 작게 하기

- 필요한 시점에 리소스 로드하기

- display object가 필요하지 않으면 display hierarchy 에서 remove 하기

나쁜 예)

-- rect is a global variable

rect = display.newRect(0,0,10,10)

rect:setFillColor(255,255,255)

 

local function removeOnTap( event )

   local t = event.target

   local parent = t.parent

 

   -- remove from display hierarchy

   -- but var "rect" still exists

   -- so memory is never freed

   parent:remove( t )

 

   return true

end

 

rect:addEventListener(   "tap", removeOnTap ) 


좋은 예)

-- rect is a local variable

local rect = display.newRect(0,0,10,10)

rect:setFillColor(255,255,255)

 

local function removeOnTap( event )

   local t = event.target

   local parent = t.parent

 

   -- remove from display hierarchy

   parent:remove( t )

 

   return true

end

 

rect:addEventListener(   "tap", removeOnTap ) 


위의 나쁜 예는 rect가 글로벌 변수로 선언이 돼 있기 때문에 parent:remove(t)를 해도 rect는 메모리에서 해제되지 않습니다. 좋은 예처럼 변수를 local로 처리하면 메모리가 깨끗하게 정리 됩니다.


Reducing Power Consumption (파워 소비 줄이기)

모바일 디바이스는 배터리 생명이 짧습니다. 개발자는 배터리 소비를 줄이기 위해 아래와 같은 사항들을 되도록 안 쓰도록 신경을 쓸 수 있습니다.

- Network traffic (Wi-Fi radios and baseband cell radios)

- GPS

- Accelerometers

- Disk accesses (reading/writing to files)


Network

위 요소들 중 네트워크 access가 가장 파워를 많이 사용합니다. 될 수 있는 대로 네트워크 트래픽을 줄여야 합니다.

아래 사항들을 참고 하세요.

- 필요할 때만 외부 네트워크 연결을 한다.

- 가능한 전달할 데이터의 사이즈를 최소화 한다.

- Transmit in bursts. More power is consumed the longer the radio is actively transmitting data. Therefore, transmit the data in bursts instead of spreading out the same data into smaller transmission packets over time.

- 위치 데이터는 GPS,cell, Wi-Fi 네트워크를 통해 얻어 집니다. 이 위치 데이터도 필요한 경우만 사용 합니다.



Graphics


Group objects



Turn off animations for non-visible objects

애니메이션이 non-visible이 될 경우 또는 그 객체가 스크린 밖에 위치할 때에는 가능한한 그 애니메이션을 멈춥니다.


Optimize image size

큰 이미지는 로드 할 때 더 많은 시간과 메모리를 소비합니다. 

이 큰 이미지가 더 이상 필요하지 않으면 parent group에서 이 객체를 remove합니다.

local image = display.newImage( "image.png" )

 

-- do stuff with image

 

image:getParent():remove( image )



Lua: Best Practice


컬 변수를 사용하라

글로벌 변수

for i = 1, 1000000 do 

   local x = math.sin(i) 

end  

컬 변수

local sin = math.sin 

for i = 1, 1000000 do 

   local x = sin(i) 

end 


특히 위와 같이 긴 프를 사용 할 경우 컬로 변수를 선하면 훨씬 가다. 위 드를 예로 들면 글로벌 변수 제는 컬 변수 다 30% 더 실행 시간이 느립다.


함수를 사용할 경우에 함수 밖에서 컬로 변수를 선언 하면 글로벌 변수도가 빠릅다.


글로벌

 function foo (x) 

   for i = 1, 1000000 do 

      x = x + math.sin(i) 

   end 

   return x 

end  


External local

local sin = math.sin 

function foo (x) 

   for i = 1, 1000000 do

      x = x + sin(i) 

   end 

   return x 

end 


이 경우도 글로벌 변수를 사용한 드가 30% 더 속도가 느립다.



x*0.5 x/2 다 빠릅다.

x*x x^2다 빠릅다.


배열에 객체 집어 넣기

t[#t+1] = itemtable.insert(t,item)다 빠릅다.


Constant Folding

i=111+111i=222 와 속도가 같습다. 하면 컴파일러는 그 값을 미리 계하기 때문입다.

1+2+x 는 (1+2) + x 로 주 되서 3+x와 같은 시간이 걸립다.

지만 x+1+2는  (x+1)+2로 주 되서 미리 계산하지 않아서 시간이 더 걸리게 됩다.


Cache properties in a local variable

어떤 값이 계속 사용된다면 Cashe에 올리는 것이 속도가 빠릅다.

Uncached

function foo (o)

   local n = 0

   for i = 1, 1000000 do

      -- lookup o.x each time 

      n = i + n * o.x

   end

   return n

end 


Cached

function foo (o)

   -- cache o.x before the loop

   local x = o.x

   local n = 0

   for i = 1, 1000000 do 

      n = i + n * x

   end 

   return n

end 



다음 시간에도 계속해서 여러 프로그래밍 테크닉에 대해서 알아 보겠습니다.

반응형

Corona SDK Native UI

2011. 11. 7. 23:45 | Posted by 솔웅


반응형

코로나 SDK의 native library는 다양한 native user interface를 제공합니다.

Activity Indicator

native.setActivityIndicator()

native.setActivityIndicator( true );
timer.performWithDelay( 10000,  function()
     native.setActivityIndicator( false )
end)
local bgImg = display.newImage( "background1.png" )

위 소스코드와 같이 true를 하면 아래 화면에서 보이는 것 처럼 디바이스 내의 Activity Indicator가 나옵니다. true인 동안에는 touch 같은 동작을 할 수 없게 됩니다. 이 소스코드는 10초동안 indicator가 true이고 그 이후는 평상시 처럼 동작하는 코드 입니다.



Alert
native.showAlert(title,message[,buttonLabels[,listener]])
popup alert 화면이 뜨고 거 화면에 버튼도 표시 됩니다. 애니메이션 같은 프로그램의 액티비티는 백그라운드에서 그대로 실행이 될 겁니다. 그러니까 게임 같은데서 replay를 위해 모두 멈춰야 되면 이 alert화면을 띄우기 전에 코딩으로 다 처리 해 줘야 되더라구요. 다만 touch 같은 유저와의 interactivity는 alert을 없애기 전 까지는 block 됩니다. button만 빼 놓으면 setActivityIndicator랑 조건이 비슷합니다.

title은 제목이고 message는 텍스트로 표시 될 내용입니다. 그 다음에 버튼을 표시하고 그 버튼을 누르면 실행할 리스너 함수를 넣을 수 있습니다.



아래 간단한 소스코드 보시겠습니다.

native.setActivityIndicator( true );
timer.performWithDelay( 3000,  function()
     native.setActivityIndicator( false )
end)
local bgImg = display.newImage( "background1.png" )

-- Handler that gets notified when the alert closes
local function onComplete( event )
        if "clicked" == event.action then
                local i = event.index
                if 1 == i then
                        -- Do nothing; dialog will simply dismiss
                elseif 2 == i then
                        -- Open URL if "Learn More" (the 2nd button) was clicked
                        system.openURL( "http://coronasdk.tistory.com" )
                end
        end
end
 
-- Show alert with five buttons
local alert = native.showAlert( "Corona", "Dream. Build. Ship.",
                                        { "OK", "Learn More" }, onComplete )

이 소스코드는 OK 와 Learn More 두개의 버튼이 뜨고 OK를 누르면 아무것도 안하고 Learn More를 누르면 위에 있는 웹 사이트로 가도록 한 코드 입니다.

이 showAlert에 넣을 수 있는 버튼의 갯수는 6개 까지 입니다. 이 버튼은 순서에 따라 event.index에 1~6번까지가 할당 됩니다.

event.action 에는 cancelled와 clicked 가 있습니다.

native.cancelAlert()
cancel버튼을 누르면 alert화면이 사라지지만 프로그래밍으로없애려면 이 구문을 씁니다. 예를 들어 alert화면이 뜨고 일정 시간 후에 자동으로 없어지게 만드는 기능 등에 사용 할 수 있겠죠?

-- Dismisses "alert" from previous code sample after 10 seconds
local function cancelMyAlert()
        native.cancelAlert( alert )
end
 
timer.performWithDelay( 10000, cancelMyAlert )


Fonts

native.newFont(name[,size])
원하는 폰트가 있으면 이 함수를 이용해서 폰트를 사용할 수 있습니다.

기본적으로는 아래 두 폰트를 사용할 수 있습니다.
native.systemFont , native.systemFontBold

native.getFontNames()를 통해서 사용 가능한 폰트들을 리턴 받을 수 있습니다.

Text Input

native.newTextField(left,top,width,height[,listener])
single-line textfield를 표시합니다.
앱 스토어에 올린 Spin the Bottle 1 개발 할 때 사용해 봤는데요. 이게 시뮬레이터에서는 지원이 안 됐습니다. 그래서 테스트 할 때마다 디바이스에 인스톨 하면서 테스트 해야 되서 불편하더라구요.
얼마전에 시뮬레이터에서도 지원이 된다고 해서 새 build 버전을 받았는데 아직 안정적이지 않아서 인스톨이 잘 안 되더라구요. 코로나 측에서 테스트 완료하고 곧 공개 할 것 같습니다.

TextField 의 property들은 아래와 같습니다.
object.align : left,center,right 가 있음
object.font : native.newFont()를 리턴합니다.
object.isSecure : 패스워드 같이 문자가 찍히지 않도록 합니다. 디폴트는 false로 문자가 찍히도록 돼 있습니다.
object.size : 텍스트의 크기
object.text : textfiend에 지정된 텍스트를 뿌려줍니다.

아래와 같은 메소드 들이 있습니다.
object:setTextColor(r,g,b[,a]) : 텍스트 칼라를 지정해 줍니다.



Listening for Keyboard Events
스마트폰에서 텍스트를 입력하려면 키보드가 나타납니다.
이 때 그 움직임을 catch 할 수 있는데요. 이것은 event.phase로 구분 합니다.
event.phase = "began" : 키보드가 나타났을 때
event.phase = "ended" : 텍스트 필드가 아니라 다른 필드를 누를 때 같이 사용자가 해당 텍스트 필드의 작업을 멈췄을 떄
event.phase = "submitted" : return 을 눌렀을 때

native.setKeyboardFocus(textField) : 키보드 포커스를 해당 텍스트 필드에 함. nil을 넣으면 포커스를 없애고 키보드를 사라지게합니다.


Supported Keyboard Types
input textfield에는 5가지 타입의 키보드가 지원 됩니다. 프로퍼티 이름은 "inputType"입니다.
numericField = native.newTextField( 50, 150, 220, 36, handlerFunction )
numericField.inputType = "number"

위 소스에서 number는 숫자만 입력할 수 있는 키보드가 나옵니다.
default는 일반적인 키보드 입니다.
phone 은 전화번호 입력을 위한 키보드 입니다.
url 은 웹 주소를 입력할 수 있는 키보드 입니다.
email 은 이메일 주소를 입력 할 수 있는 키보드 입니다.

Multiline Textfields
여러 줄을 입력할 수 있도록 하려면 텍스트 박스를 사용합니다.
native.newTextBox( left, top, width, height )
높이를 넘어가면 스크롤 바가 생깁니다.

프로퍼티들은 아래와 같습니다.
object.align : left,center,right 가 있습니다.
object.font : native.newFont()에 의해 리턴된 폰트 객체 입니다.
object.hasBackground 배경화면이 있으면 true이고 투명하면 false입니다.
object.size 텍스트 크기 입니다.
object.text 텍스트 박스 내의 문자 입니다.

메소드에는 칼라를 지정하는 메소드가 있습니다.
object:setTextColor(r,g,b[,a])


Web PopUps

이 웹 팝업을 사용하려면 아래와 같이 하면 됩니다.
native.showWebPopup( "http://coronasdk.tistory.com" )
특정 크기를 지정해 줄 수도 있습니다.
native.showWebPopup( 10, 10, 300, 300, "http://coronasdk.tistory.com" )

Web popup scaling
웹 팝업의 크기를 설정 하려면 아래와 같이 HTML 파일에 메타태그를 코딩해 넣으시면 됩니다.
<meta name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>

웹 폰트 사이즈를 유지하려면 아래와 같이 CSS 코드를 추가합니다.
<style type="text/css">
html {
        -webkit-text-size-adjust: none;
}
</style>

Web Popup Options
웹 팝업 화면을 조정하려면 아래와 같은 6개의 파라미터로 조절 하실 수 있습니다.
native.showWebPopup( 10, 10, 300, 300, "http://coronasdk.tistory.com", {urlRequest=listener} )

옵션테이블의 아래 프로퍼티들을 사용하실 수 있습니다.

options.baseUrl : 베이스 url을 규정합니다.
options.hasBackground : 팝업에 백그라운드가 있는지 없는지에 대해 컨트롤 합니다.
options.urlRequest : 웹 페이지의 상대 경로를 사용할 수 있습니다. 예) interface/WebOverlay
<form action="corona:close">
        <input type="submit"/>
</form>
사용자가 웹 팝업의  Submit을 누르면 설정된 리스너 함수는 urlRequest 이벤트 객체를 보내집니다.

Removing the Web Popup
native.cancelWebPoppu()으로 팝업을 없앨 수 있습니다.

Handling Web Popup Events
옵션 테이블에 이벤트 핸들러를 urlRequest 프로퍼티에 제공하면 웹 팝업은 유저가 웹 페이지의 링크를 클릭할 때 이 이벤트를 핸들러에 전달됩니다. 아래와 같은 이벤트들이 전달 됩니다.
event.errorCode
event.errorMessage
event.name
event.url

이 이벤트 핸들러는 한번 클릭할 때마다 한번씩 실행됩니다.
function listener( event )
  --if no errors, then execute my code
  if event.errorMessage == nil then
          myOnClickHandler( event.url )
  end
  return true
end
 
local options = {
  baseUrl = system.ResourceDirectory,
  hasBackground = false,
  urlRequest = listener
}
 
native.showWebPopup( "test.html", options )

지난번 코로나에서 광고 다는 법 할 때 말한건데요.
코로나 SDK에서는 현재 공식적으로 inMobi 만 지원합니다.

그런데 이 웹 팝업을 이용해서 admob이나 iAd 같은 앱 광고도 넣을 수 있습니다.
공식적인 방법은 아니고 개발자들 끼리 방법을 개발해서 그 코드를 공유하고 있습니다.

인터넷을 찾아보시면 나올겁니다.
저도 이 방법을 배우게 되면 이곳에 글로 정리해 두겠습니다.

그럼...

반응형

SlideView 구현하기

2011. 11. 4. 22:36 | Posted by 솔웅


반응형
어떤 분이 질문을 하나 올려 주셨습니다.
비밀 댓글로 올려 주신걸로 봐서 구체적인 내용이 알려지길 원하시지는 않는 것 같네요.
그 분의 질문을 보면 SlideView 구현에 대해 알면 해결 될 수 있을 것 같습니다.
그래서 오늘은 SlideView에 대해서 알아 보도록 하겠습니다.


이 이미지는 Corona SDK를 깔면 기본으로 제공되는 샘플 코드 입니다.
사진을 좌우로 움직일수 있고 Terminal에 보면 현재 사진이 몇번 째 인지 그리고 dragDistance는 얼마인지 그리고 총 몇개의 이미지 중 몇번째인지 등의 정보가 나옵니다.

코로나 SDK에서 제공되는 샘플을 보면 원하는 기능을 생각보다 쉽게 구현하는 방법을 알 수 있습니다. 적극적으로 활용하기를 바랍니다.

일단 SlideView를 쉽게 구현하도록 코로나에서는 따로 클래스 파일을 제공합니다.

이 소스 코드도 열어서 분석 해 보시면 그렇게 어렵지는 않습니다.
그건 각자 해 보시고 오늘 글에서는 이 기능을 이용하는 방법을 살펴 보겠습니다.
일단 소스를 보겠습니다.

display.setStatusBar( display.HiddenStatusBar )

local slideView = require("slideView")
   
local myImages = {
    "myPhotos1.png",
    "myPhotos2.png",
    "myPhotos3.png",
    "myPhotos4.png"
}       

--slideView.new( myImages )
--slideView.new( myImages, "bg.png" )
slideView.new( myImages, nil, 40, 60 )

별거 없습니다. 이렇게 단 몇줄로 slideView 기능이 구현 됐습니다.
첫줄은 statusBar를 없애는 것이고
그 다음 줄이 slideView.lua를 이용하기 위해서 require 한 겁니다.
그리고 slideView에 넣을 이미지를 테이블(배열) 에 넣구요.
slideView.new()를 해 주시면 됩니다.
slideView.lua의 36번째 줄을 보시면
function new(imageSet,slideBackground,top,bottom) 으로 함수가 시작합니다.
전달하는 파라미터를 이미지세트와 백그라운드 이미지 그리고 top과 bottom값을 전달 할 수 있습니다.
아까 terminal에 뿌려줬던 텍스트들은 main.lua에 있지 않고 slideView.lua 파일 안에 있네요.
전체 소스 파일은 아래 파일을 다운 받으세요.


다음 예제를 소개해 드리겠습니다.


코로나 샘플 소스코드와 똑 같습니다. 다만 다른 점이 있다면 이미지세트를 불러오는게 아니라 다른 별도의 파일들을 불러오는 겁니다.
display.setStatusBar( display.HiddenStatusBar )

local slideView = require("slideView")
   
local mySlides = {
    "screen1",
    "screen2",
    "screen3",
}       

slideView.new( mySlides )

이것도 소스는 간단합니다. 단지 mySlides라는 테이블(배열)에 파일 이름이 들어가 있습니다.
대표로 screen1을 보시겠습니다.

module(..., package.seeall)

function new()
    local g = display.newGroup()
   
    local background = display.newRect(0,display.screenOriginY, display.contentWidth, display.contentHeight-display.screenOriginY)
    background:setFillColor(222, 222, 222)
    g:insert(background)
   
    local message = display.newText("Screen 1", 0, 0, native.systemFontBold, 16)
    message:setTextColor(0, 0, 0)
    message.x = display.contentWidth*0.5
    message.y = display.contentHeight*0.5
    g:insert(message)
   
    function g:cleanUp()
        g:removeSelf()
    end
   
    return g
end

첫번째는 다른 file(main.lua) 에서 require하려면 설정해 줘야 되는 모듈 부분입니다.
그리고 function new() 함수가 있습니다.
g라는 그룹이 그 다음줄에서 정위 되었구요. 배경 화면으로 사용될 사각형이 그려졌습니다. 이 사각형의 변수 이름은 background이고 color가 정해지고 그룹에 insert됐습니다.
그 다음에는 Screen이라는 텍스트를 message라는 변수에 담아서 display합니다.

이 샘플 예제는 아래 파일을 참조하세요.


Slide View 관련해서 알아 봤는데요.

혹시 코로나로 개발하시거나 공부하시면서 질문 사항 있으시면 주저마시고 댓글로 올려 주세요.

저도 찾아 보면서 공부가 많이 되네요.

혹시 질문 올리신 분께 충분히 답변이 됐는지 모르겠습니다.
더 질문이 있으시면 댓글 달아 주세요.

그럼....
반응형