2021년 2월 24일 수요일

[iOS] @synchronized 배타 제어 (객체 동시접근 방지)

참고: https://dolfalf.tistory.com/145

https://aroundck.tistory.com/4705


가장간단한건 

 // self를 키로 락을 검. 어디선가 self로 락을 건경우 락이 해제될 때까지 여기서 기다리게됨.
@synchronized (self) {
  [_mutableItems addObject:object];
}


보통 이런식으로도 씀.

@implementation MyClass

{

// 잠금시 키로 지정하는 인스턴스를 저장할 위치를 제공합니다.

NSObject * _objectForLock;

}


이것을 init 메소드 등의 어딘가 적절한 위치로 초기화합니다.

- (id) init

{

self = [super init];


if (self)

{

// 잠금시 키로 지정하는 인스턴스를 준비합니다.

_objectForLock = [[NSObject alloc] init];

}

return self;


}


ARC 환경이라면 Objective-C 인스턴스는 필요하지 않을 때 출시되므로 뒤처리가 필요하지 않습니다.


ARC 환경이 아닌 경우는 -dealloc 메소드 등으로 잠금을 확보 한 인스턴스를 release하도록합니다.


@synchronized의 인수로 사용합니다.


@synchronized (_objectForLock)

{


}


Effective Object-C 에서는 

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL); 

이렇게도 쓰라고함

2021년 2월 21일 일요일

[iOS] 개발/테스트 배포용 ipa 간단 생성

Product->Archive->Distribute App -> Developement ->쭉쭉 다음으로 넘기고

실행하는 쪽은 Xcode->Devices and Simulators 에서 ipa던져넣으면 된다.

[Unity] IOS check version

 

요약

OS 버전을 확인하는 방법이 Objective-C라고 귀찮은 방법 밖에없는 것인지라고 생각하고있어, 피곤하고 있으면, 그런 일은 없었기 때문에 소개.

환경

  • MacOS 10.12.6
  • Xcode 9.0.1

종래의 방법

기술 량이 많아 귀찮았.

if ([UIDevice currentDevice] .systemVersion.floatValue> = 11.0) { 
    NSLog (@ "iOS11 이상이야"); 
} else { 
    NSLog (@ "iOS11 미만이야"); 
}

간단한 방법

묘사가 적게된다.

if (@available (iOS 11.0 *)) { 
    NSLog (@ "iOS11 이상이야"); 
} else { 
    NSLog (@ "iOS11 미만이야"); 
}

주의 사항

@available(〜)그리고 다른 조건을 함께 기재하면 warning이 나온다.
중첩하면 괜찮아.

NG

Bool isClose = YES; 
if (@available (iOS 11.0 *) && isClose) { 
    NSLog (@ "iOS11 이상이야"); 
}

OK

Bool isClose = YES; 
if (@available (iOS 11.0 *)) { 
    if (isClose) { 
        NSLog (@ "iOS11 이상이야"); 
    } 
}

중첩없이 쓸 수 있으면 좋겠다.

[Unity] 실전 게임 만들기 강의 /한국/

좀비슈터, 미니RPG, 등 강좌 몇개 정리해놈. 

리듬게임(케이디)의 강좌도 정리해놈.

정리잘해논게 맘에듬

 https://ansohxxn.github.io/categories/unity-lesson-1

2021년 2월 20일 토요일

[unity] VisualGraph 3d

 https://m.blog.naver.com/PostView.nhn?blogId=hana100494&logNo=222223197400&targetKeyword=&targetRecommendationCode=1


잘정리되어 있다.



[Unity] 1300 자료./ 일본어 /알차다

 http://tsubakit1.hateblo.jp/

Unity

2021년 2월 17일 수요일

[Unity] GC Alloc를 발생시키지 않는 C# 코딩

 https://qiita.com/sapphire_al2o3/items/4f517523f50e0113af1f


Unity 코드를 쓸 때 GC Alloc가 발생하는 패턴을 조사했다. GC Alloc이 줄어들 패턴 good했지만, 코드가 알기 어려울 경우도 있으므로 반드시 개서를 할 필요는 없다.

Unity 2018.4에서 동작 확인

문자와 숫자의 연결

bad
int  i  =  123 ; 
string  s  =  "num_"  +  i ;
good
int  i  =  123 ; 
string  s  =  "num_"  +  i . ToString ();

string.Concat에 object로 전달되어 박스가되어 버리므로 문자열한다.

4 개의 문자열 연결

bad
string []  num  =  {  "0" ,  "1" ,  "2" ,  "3" ,  "4" ,  "5"  }; 
// 96byte 
s  =  num [ 0 ]; 
s  + =  num [ 1 ]; 
s  + =  num [ 2 ]; 
s  + =  num [ 3 ];
good
string []  num  =  {  "0" ,  "1" ,  "2" ,  "3" ,  "4" ,  "5"  }; 
// 34byte 
string  s  =  num [ 0 ]  +  num [ 1 ]  +  num [ 2 ]  +  num [ 3 ];

연결 할 때마다 새로운 문자열을 생성되는데, 4 개까지 연결라면 Concat (string, string, string, string)이 알려져 번에 연결 할 수있다.

루프에서 문자열 연결

good
var  sb  =  new  System . Text . StringBuilder (); 
for  ( int  i  =  0 ;  i  <  num . Length ;  i ++) 
{ 
    sb . Append ( num [ i ]); 
} 
string  s  =  sb . ToString ();

루프에서 많은 문자열 연결 할 수 있으면 한번씩 문자열이 생성되지 않도록 StringBuilder를 사용한다.

문자열 배열의 연결

good
string []  num  =  {  "0" ,  "1" ,  "2" ,  "3" ,  "4" ,  "5"  }; 
// 38byte 
string  s  =  string . Join ( "" ,  num );

문자열 배열을 연결하는 경우 Join을 사용한다.

string.Format

bad
string []  num  =  new  string [ 10 ]; 
for  ( int  i  =  0 ;  i  <  num . Length ;  i ++) 
{ 
    // 84byte 
    num [ i ]  =  string . Format ( "num_ {0}" ,  i ) ; 
}
good
string []  num  =  new  string [ 10 ]; 
for  ( int  i  =  0 ;  i  <  num . Length ;  i ++) 
{ 
    // 64byte 
    num [ i ]  =  $ "num_ { i . ToString ()} " ; 
}

string.Format보다 문자열 보간 ($ "")를 사용한다. 문자열 보간 쪽이 선명 인수가 문자열 만 있으면 간단한 문자열 연결됩니다 string.Format가 호출되지 않습니다.

StringBuilder에 숫자 추가

var  sb  =  new  System . Text . StringBuilder (); 
int  n  =  100 ; 
sb . Append ( n );

.NET Framework에서는 정수는 ToString되어 추가된다. ToString하지 않으 문자 단위로 추가 할 필요가있다.
https://gist.github.com/sapphire-al2o3/ba7d6a80836a2e5ee117abb4c3d75132

Enum 캐스트

bad
// 40byte 
EnumType  e  =  ( EnumType ) System . Enum . ToObject ( typeof ( EnumType ),  i ); 
// 40byte 
i  =  System . Convert . ToInt32 ( e );
good
// 0byte 
EnumType  e  =  ( EnumType ) i ; 
// 0byte 
i  =  ( int ) e ;

Enum을 Dictionary의 Key하지 (.NET 3.5)

bad
var  d  =  new  Dictionary < EnumType ,  int > ();
good
var  d  =  new  Dictionary < int ,  int > ();

.NET 4라면 박스하지 않기 때문에 Enum을 Key하고 접근해서 GC Alloc는 발생하지 않지만, 저속이므로 int를 Key로하는 것이 좋다.

코루틴 반환

bad
// 20byte 
yield  return  0 ;
good
// 0byte 
yield  return  null ;

0을 반환하는 경우, 박스 화하여 GC Alloc가 발생한다.

코루찐에서 foreach

bad
IEnumrator  Sum ( int []  array ) 
{ 
    int  s  =  0 ; 
    foreach  ( var  a  in  array ) 
    { 
        s  + =  a ; 
        yield  return  null ; 
    } 
}

int []  array  =  {  1 ,  2 ,  3 ,  4 ,  5  }; 
StartCoroutine ( Sum ());
good
IEnumrator  Sum ( int []  array ) 
{ 
    int  s  =  0 ; 
    for  ( int  i  =  0 ;  i  <  array . Length ;  i ++) 
    { 
        s  + =  a [ i ]; 
        yield  return  null ; 
    } 
}

int []  array  =  {  1 ,  2 ,  3 ,  4 ,  5  }; 
StartCoroutine ( Sum ());

foreach보다 for 쪽이 임시 변수가 적기 때문에 코루찐의 크기가 작아진다.

List의 크기 지정을

bad
List < int >  list  =  new  List < int > (); 
// 8.3Kbyte 
for  ( int  i  =  0 ;  i  <  1000 ;  i ++) 
{ 
    list . Add ( i ); 
}
good
List < int >  list  =  new  List < int > ( 1000 ); 
// 4.0Kbyte 
for  ( int  i  =  0 ;  i  <  1000 ;  i ++) 
{ 
    list . Add ( i ); 
}

List와 Dictionary 미리 추가하는 크기를 알고있는 경우 생성 할 때 Capacity를 지정한다.

배열 정렬

bad
int []  array  =  new  int [ 100 ]; 
// 10.9Kbyte 
for  ( int  i  =  0 ;  i  <  100 ;  i ++) 
{ 
    Array . Sort ( array ); 
}
good
int []  array  =  new  int [ 100 ]; 
// 112byte 
for  ( int  i  =  0 ;  i  <  100 ;  i ++) 
{ 
    Array . Sort ( array ,  ( x ,  y )  =>  x  -  y ); 
}

비록 int의 배열에도 람다 식을 지정하지 않으면 불필요한 캐스트가 발생 해 버린다. (.NET Core이라고 괜찮아)

Delegate

bad
void  Log ( int  value )  {  Debug . Log ( value );  }

void  Squared ( int  value ,  Action < void >  callback ) 
{ 
    int  v  =  value  *  2 ; 
    callback ( v ); 
}

int []  num  =  {  0 ,  1 ,  2 ,  3 ,  4  }; 
foreach  ( var  n  in  num ) 
{ 
    Squared ( n ,  Log ); 
}
good
void  Log ( int  value )  {  Debug . Log ( value );  }

void  Squared ( int  value ,  Action < void >  callback ) 
{ 
    int  v  =  value  *  2 ; 
    callback ( v ); 
}

int []  num  =  {  0 ,  1 ,  2 ,  3 ,  4  }; 
var  log  =  Log ; 
foreach  ( var  n  in  num ) 
{ 
    Squared ( n ,  log ); 
}

여러 번 호출 함수는 캐시한다.

람다 식의 범위

bad
List < int >  RemoveList ( List < int >  list ,  int  v ) 
{ 
    if  ( list  ! =  null  &&  list . Count  >  0 ) 
    { 
        return  list . FindAll ( x  =>  x  ! =  v ); 
    } 
    return  null ; 
}
good
List < int >  RemoveList ( List < int >  list ,  int  v ) 
{ 
    if  ( list  ! =  null  &&  list . Count  >  0 ) 
    { 
        int  t  =  v ; 
        return  list . FindAll ( x  =>  x  ! =  t ); 
    } 
    return  null ; 
}

list가 null의 경우는 아무것도하지 않지만 bad 쪽은 함수를 호출하는 것만으로 GC Alloc가 발생한다.
함수에 들어갈 때 인수를 캡처 한 람다 표현식이 생성되어 버리기 때문에 인수를 내부의 범위에 캐싱 캡처하면 범위에 들어 가지 않는 경우는 람다 식을 생성한다.

IList 루프

bad
IList  list  =  new  string [ 10 ]; 
foreach  ( var  e  in  list )  {}
good
IList  list  =  new  string [ 10 ]; 
for  ( int  i  =  0 ;  i  <  list . Count ;  i ++)  {}

인터페이스를 통해 foreach를 사용하면 박스가 발생하기 때문에 for를 사용한다.

멤버의 순서

bad
// 80byte 
class  C 
{ 
    byte  a ; 
    long  b ; 
    byte  c ; 
    long  d ; 
    byte  e ; 
    long  f ; 
    byte  g ; 
    long  h ; 
}
good
// 56byte 
class  C 
{ 
    long  b ; 
    long  d ; 
    long  f ; 
    long  h ; 
    byte  a ; 
    byte  c ; 
    byte  e ; 
    byte  g ; 
}

Unity는 class에도 자동으로 적절한 메모리 레이아웃이되지 않도록 때문에 불필요한 패딩이 들어 가지 않도록 멤버 정의의 순서를 고려한다.

인코딩

bad
string  s  =  "hoge" ; 
int  n  =  System . Text . Encoding . GetEncoding ( "UTF-8" ). GetByteCount ( s );
good
string  s  =  "hoge" ; 
int  n  =  System . Text . Encoding . UTF8 . GetByteCount ( s );

GetEncoding을 호출하면 매번 클래스가 생성되기 때문에 캐시되는 UTF8을 사용한다.

Unity의 API

Object.name

bad
for  ( int  i  =  0 ;  i  <  4 ;  i ++) 
{ 
    Debug . Log ( go . name ); 
}

name 속성에 액세스 할 때마다 GC Alloc가 발생하기 때문에 캐시한다.

Application *** Path

  • Application.persistentDataPath
  • Application.temporaryCachePath

등도 방문 할 때마다 문자열이 생성되어 버리므로 캐시한다.

Renderer.materials

bad
for  ( int  i  =  0 ;  i  <  renderer . materials . length ;  i ++) 
{ 
}
good
foreach  ( var  m  in  renderer . materials ) 
{ 
}

Renderer.materials 속성에 배열을 생성 해 돌려 있기 때문에 루프에서 매번 사용하지 않도록한다.

Input.touches

bad
// 80byte 
foreach  ( var  touch  in  Input . touches ) 
{ 
}
good
// 0byte 
for  ( int  i  =  0 ;  i  <  Input . touchCount ;  i ++) 
{ 
    var  touch  =  Input . GetTouch ( i ); 
}

이쪽도 배열을 생성 해 돌려 주므로 Input.GetTouch을 사용한다.

Animator.GetParameter

bad
for  ( int  i  =  0 ;  i  <  animator . parameterCount ;  i ++) 
{ 
    // animators.parameters [i] .name과 거의 같은 
    Debug . Log ( animator . GetParameter ( i ). name ); 
}
good
foreach  ( var  paramter  in  animator . parameters ) 
{ 
    Debug . Log ( paremter . name ); 
}

Animator.parameters배열을 반환 API이므로 캐시한다.
Animator.GetParameter치아 Animator.parameters를 내부에서 호출있을 뿐이므로 사용하지 않는다.

결과를 배열로 반환 API

good
void  GetComponentsInChildren  ( List < T >  results ) 
void  GetComponentsInChildren  ( List < T >  results ) 
void  GetComponentsInParent  ( bool  includeInactive ,  List < T >  results ) 
int  Physics . RaycastNonAlloc  (...  ,  RaycastHit []  results ,  ...) 
Animator . GetCurrentAnimatorClipInfo  ( int layerIndex ,  List < AnimatorClipInfo >  clips )

몇번이나 호출하는 경우 인수 배열이나 목록을 전달하고 결과를받는 함수를 사용한다.