Functional Programming in C# [ Ch. 4, 5, 6 ] __ Oliver Sturm

Chap. 4. Flexible Typing with Generics [9/22]

제네릭 가능 : class, method, interface, delegate
대표적인 것 : 컨테이너 클래스.  다목적 베이스 클래스.

Generic Functions [9/22]

static void SomeThing<T> (T param) { // 함수 이름 뒤에 <타잎> 표시.
}
// Usage ..
SomeThing<int>(33);
SomeThing<string>(" The Parameter ");

<타잎> 변수 생략 가능 : 컴파일러 추론.

Generic Classes

..

Constraining Types 제한.

static void OutputVal<T> ( T value ) where T : ListItem<string> {}   // ListItem<string> 에서 파생되어야..

Other Generic Types

// Delegate
public delegate Rt SomeDelegate<T, Rt> (T param);  // Func<T, Rt> 와 같은 기능..
// Func
public delegate TResult Func<T1, T2, T3, T4, TResule> (T1 p1, T2 p2, T3 p3, T4 pr);
// 대부분의 추상 함수에 사용 가능.  4개 인수와 1개 리턴 값.
// Action 은 리턴 값이 없는 함수에 사용 가능.

Covariance and Contra variance

object[] objList = new object[3];
objList[2] = 10;
string[] strArr = new string[] { "one", "two", "thr" };
objList = strArr; // 이런 할당을 허용할 것인가..
// 좀 더 복잡한..
objList[2] = 10; // Runtime exception .. 어레이가 스트링 형이므로..
int[] intArr = new .... ;
objList = intArr; // intArr 는 밸류타입임.. 이 경우 covariance 는 레퍼런스 타잎만 지원

Variance 는 언어를 더 직관적으로 기능하게 하는 요소다.

List<object> objLst = new ...;
List<string> strLst = new ...;
objLst = strLst; // 허용 안됨.
// IEnumerable<T> 의 예.
public interface IEnumerable<out T> : IEnumerable { }  // retrieved from methods 에서 사용 가능. not passed into them.
IEnumeralbe<object> objSeq = new .. ;
IEnumeralbe<string> strSeq = new .. ;
objSeq = strSeq; // 가능.

4.0 에서 in, out 제레릭에서 사용 가능. 데이터 흐름의 방향을 제어 가능.

Chap. 5. Lazy Listing with Iterators

데이터를 효율적으로 다루기.

The Meaning of Laziness [9/23]

Linq 도 이같은 원리에 근거.

Enumerating Things with .NET

IEnumerable 인터페이스.
MoveNext(), Current(), Reset() …

Implementing Iterator Functions [9/24]

C# 2.0에서 도입된 IEnumerable / IEnumerator 조합을 이용. 제네릭, 넌제네릭 모두 가능.
특별한 형태의 리턴.  yield return 문.  Laziness ..  p49 코드를 볼 것.
리스트를 다 채우는 것이 아니라… 필요할 때 가져온다.. (미리 할 필요가 없다. 중간에 멈추면 후속 값 계산을 안 한다.).

Returning IEnumerator [9/29]

IEnumerable, IEnumerator ..

Chanining Iterators

.. laziness ..

.

Chap. 6. Encapsulating Data in Closures

Scope문제 Closure를 이용 해결.

Constructing Functions Dynamically [9/30]

C# 1.0 : ~ = C ..
delegate, event  : multicast
C# 2.0 : 무명함수.  함수를 만들어서 외부 스코프에 리턴하는 것은 이득이 많음.

The Problem with Scope

엄격한 스코프.
함수 플글에선 변하는 값을 별로 안 좋아함. 가이드 라인은 : { 변하는 값을 전혀 갖지 않음 }
가장 좁은 스코프 안에 집어 넣는 것.
순수 함수는 로컬 변수만 이용. 외부 참조 없음.   => 첫 반응은 incredulity.. 회의..
전역 변수가 않 좋은 이유 : 특정 시점에 어떤 값을 갖을 지 알 수 없음. ..
클래스 멤버는 좁은 스코프.
초기화 과정에서 나중에 쓰일 변수가 있다면 ?  클로져를 이용하는 대안..

How Closures Work

함수를 리턴하는 경우.. 그 안에 로컬 변수..  리턴 된 후 그 함수를 실행시키면 크래쉬. => 컴파일러가 그 값을 보관.
컴파일러는 무명 클래스를 생성하고 인스턴스 생성해서 그 함수를 부른 함수 내부에 둔다.
무명함수는 그 무명 클래스 내부의 함수로 생성. 그 콜러가 지속할 때까지 살아 있는다.  로컬 변수는 무명클래스의 필드로 된 것.
동적 함수는 객체 지향의 메서드 오버로딩과 유사. 하지만, 동적으로 생성됨.

C# in Depth. Jon Skeet. ch. 1

Dynamic languages suck away fluff from your code, leaving just the essence … Right..

The interesting thing is that few of the features that tend to give dynamic lan- guages their lightweight feel have anything to do with being dynamic.

Statically typed languages don’t have to be clumsy and heavyweight.

C# 1 : Java 와 비슷.  추가적인 것 :: properties as a first-class feature in the language, delegates and events, foreach loops, using statements, explicit method overriding, operator overloading, and custom value types, to name a few

1.1 Starting with a simple data type

목적 :: impress rather than educate

1.1.1 The Product type in C# 1

using System.Collections;
public class Product
{

   string name;
   public string Name { get { return name; } } // getter 가 필요하면 정의해야 함 .. fluff ..

   decimal price;
   public decimal Price { get { return price; } }

   public Product(string name, decimal price)
   {
      this.name = name;
      this.price = price;
   }

   public static ArrayList GetSampleProducts()
   {
      ArrayList list = new ArrayList(); // 뭐가 들어가는 지 모른다.
      list.Add(new Product("West Side Story", 9.99m));
      list.Add(new Product("Assassins", 14.99m));
      list.Add(new Product("Frogs", 13.99m));
      list.Add(new Product("Sweeney Todd", 10.99m));
      return list;</pre>
}
public override string ToString()
 {
 return string.Format("{0}: {1}", name, price);
 }
}

1.1.2 Strongly typed collections in C# 2  __ generic ..

public class Product
{
   string name;
   public string Name
   {
      get { return name; }
      private set { name = value; } // ***
}
decimal price;
public decimal Price
{
      get { return price; }
      private set { price = value; }</pre>
}
public Product(string name, decimal price)
{
 Name = name;
 Price = price;
}

public static List GetSampleProducts() {
 List list = new List();
 // type 명시.. 리스트에서 가져올 때 캐스팅 필요 없음.
 list.Add(new Product("West Side Story", 9.99m));
 list.Add(new Product("Assassins", 14.99m));
 list.Add(new Product("Frogs", 13.99m));
 list.Add(new Product("Sweeney Todd", 10.99m));
 return list;
}
public override string ToString()
{
 return string.Format("{0}: {1}", name, price);
}
}

1.1.3 Automatically implemented properties in C# 3

using System.Collections.Generic;
class Product
{
  public string Name { get; private set; } // 코드, 변수도 없다. Name 은 함수명.
  public decimal Price { get; private set; }
  public Product(string name, decimal price)
  {
    Name = name;
    Price = price;
  }
  Product() {}
  public static List GetSampleProducts()
  {
    return new List
    {
      new Product { Name="West Side Story", Price = 9.99m },
      new Product { Name="Assassins", Price=14.99m },
      new Product { Name="Frogs", Price=13.99m },
      new Product { Name="Sweeney Todd", Price=10.99m}</pre>
 };
 }
 public override string ToString()
 {
 return string.Format("{0}: {1}", Name, Price); }
 }
}

1.1.4 Named arguments in C# 4

readonly decimal price;
public decimal Price { get { return price; } }
public Product(string name, decimal price)
{
  this.name = name;
  this.price = price;
}
new Product(name: "West Side Story", price: 9.99m);  // 이렇게 인수 이름을 붙여 초기화.

1.2 Sorting and filtering

1.2.1 Sorting products by name

ArrayList products = Product.GetSampleProducts(); // C# 1</pre>
List products = Product.GetSampleProducts(); // C# 2
List products = Product.GetSampleProducts(); // C# 3
products.Sort(delegate(Product x, Product y)
 { return x.Name.CompareTo(y.Name); }
);

1.2.2 Querying collections

List products = Product.GetSampleProducts();
Predicate test = delegate(Product p) { return p.Price > 10m; };
Listmatches = products.FindAll(test);
Action print = Console.WriteLine;  // method group conversion (C# 2)
matches.ForEach(print);
// Differently ...
List products = Product.GetSampleProducts();
products.FindAll(delegate(Product p) { return p.Price > 10;}) .ForEach(Console.WriteLine);  // 노이지 하다. { } 딜리깃.
// C# 3
List products = Product.GetSampleProducts();
foreach (Product product in products.Where(p => p.price > 10)) {
  Console.WriteLine(product);
}

1.3 Handling an absence of data

1.3.1 Representing an unknown price

Nullable
decimal? price;
public decimal? Price
{
  get { return price; }
  private set { prie = value; }
}
..
Price == null // 조건식..

1.3.2 Optional parameters and default values

public Product(string name, decimal? price = null)
{
  this.name = name;
  this.price = price;
}

1.4 Introducing LINQ

C# 3..

1.4.1 Query expressions and in-process queries

List products = Product.GetSampleProducts();
List suppliers = Supplier.GetSampleSuppliers();
var filtered = from p in products
               join s in suppliers
                   on p.SupplierID equals s.SupplierID
               where p.Price > 10
               orderby s.Name, p.Name
               select new { SupplierName = s.Name, ProductName = p.Name };
foreach (var v in filtered)
{
    Console.WriteLine("Supplier={0}; Product={1}",
                 v.SupplierName, v.ProductName);
}

1.4.2 Querying XML

1.4.3 LINQ to SQL

..

1.5 COM and dynamic typing

c# 4 : Com, DLR (Dynamic Language Runtime)

1.5.1 Simplifying COM interoperability

1.5.2 Interoperating with a dynamic language

ScriptEngine engine = Python.CreateEngine();
ScriptScope scope = engine.ExecuteFile("FindProducts.py");
dynamic products = scope.GetVariable("products");
foreach (dynamic product in products)
{
  Console.WriteLine("{0}: {1}", product.ProductName, product.Price);
  // 여기서 에러가 날지도 모르지만.. 어떻게든 진행함.
}

1.6 Dissecting the .NET platform

1.6.1 C#, the language

IL -> JIT compiler.

1.6.2 Runtime

런타임 부분은 IL 의 작은 부분..  CLI : Common Language Infrastructure, CLI 의 런타임은 CLR ( ~~ ~~ 런타임) 으로 불림..  이 책에서는 마소 의 구현을 가리킴.
언어의 일부는 런타임 레벨에 보이지 않지만, 다른 것들은 구분을 넘는다.  … 어레이와 딜리깃은 런타임에 중요.

1.6.3 Framework libraries

 

__

안드로이드 구글 플레이어 올리기..

안드로이드는 50메가 제한이 있음..

.. 어떻게 유니티에서 파일을 나눠서 apk 파일을 만들 것인가 ?  ==> 애셋 번들을 만들라.  방법 잘 정리된 블로그 글.

.. 어떻게 나눠진 파일을 올리는가 ?

zip 으로 압축해서 올림.  android\obb\패키지명\main.버전코드.패키지명.obb 이런식으로 된다고?..  파일명은 신경 꺼라.  바로가기

안드로이드 개발자 사이트 번역

APK Expansion Files (확장파일)

구글 플레이는 현재 50메가 이하의 파일을 요구한다. 대부분의 어플에게 이 용량은  코드와 애셋을 합쳐 충분할 것이다. 하지만 고퀄리티 그래픽이나 미디어 파일, 큰 애셋을 갖는 어플은 더 많은 공간을 요구한다. 이전에는 50메가 이상의 경우 사용자가 앱을 오픈할 때  개발자가 추가적인 리소스를 호스팅하고 다운로드를 제공해야 했다.  추가적인 파일을 호스팅하는 것은 비용이 들고 UX 도 이상적이지 않다. 이 과정을 쉽게하기 위해 구글 플레이는 2개의 대용량 파일을 붙일 수 있게 했다.

구글 플레이는 확장파일을 비용 없이 제공한다.  확장파일은 기기의 공유 스토리지 위치 (SD 카드 또는 USB-마운터블 파티션 – 즉 “외장” 공간) 에 저장된다. 대부분의 기기에서 구글 플레이는 확장 파일을 APK 파일 다운로드와 동시에 받아 사용자가 처음 열었을 때 모든 것이 다 준비되도록 한다.  하지만, 어떤 경우에는 어플이 구글 플레이로부터 어플 시작 시 파일을 다운로드 해야 한다.

Overview

구글 플레이 안드로이드 개발 콘솔을 통해서 APK를 올릴 때 마다 하나 또는 두개의 확장파일을 추가하는 옵션이 주어진다. 각 파일은 2기가 까지이며 어떤 포맷이든 허용되나 다운로드 시 bandwidth 를 유지할 수 있는 압축된 파일을 추천한다. 개념적으로 각 확장파일은 다른 역할을 한다.

# ‘main’ 확장파일은 추가적인 리소스에 대한 주된 확장파일이다.

# ‘patch’ 확장파일은 메인 파일에 대한 부가적인 파일로 작은 업데이트 용도의 파일이다.

두 확장 파일을 어떤 방법으로든 쓸 수 있지만, 메인 파일이 중요한 애셋을 포함하고 업데이트는 가끔 하도록 권장한다. 패치 파일은 더 작고 패치를 위한 파일..

하지만, 어플이 새 패치 파일만을 업데이트 했더라도, 개발자는 매니패스트 의 versionCode 를 업데이트 해서 새로운 APK 를 올려야 한다.

\/\/ Note : 패치 확장파일은 실질적으론 메인 확장 파일과 동일함. 어떻게 사용해도 무방. 시스템은 앱의 패칭을 위해 패치 확장파일을 사용하지 않음. 패치는 스스로 해야 함.

File name format

각 확장 파일은 zip, pdf, mp4 등 어떤 포맷도 가능하다. 파일 타입에 관계 없이 구글 플레이는 opaque binary blobs 로 간주하고 다음과 같은 기준으로 리네임한다.

[main|patch].<expansion-version>.<package-name>.obb

세가지 콤포넌트.

main  or  patch
파일이 메인인지 패치인지 구별. 각 APK 에 하나의 메인과 하나의 패치만 가능.

<expansion-version>
확장이 ‘첫번째’ 참조되는 정수의 버전 코드  . (어플의 android:versionCode 값과 일치)
‘첫번째’ 는 개발자 콘솔이 업로드된 확장 파일을 재사용하는 것(새로운 APK 파일과 함께)을 허용하기 때문에 강조되었음.  이것은 처음 파일을 올렸을 때의 버전을 유지한다.

<package-name>
어플의 자바-스타일 패키지 이름.

예>   main.314159.com.example.app.obb  이렇게 됨.

Storage location

구글 플레이가 확장파일을 다운로드 할 때, 시스템의 공유 공간에 저장한다. 제대로 작동되기 위해서 파일을 지우거나, 옮기거나, 이름을 바꾸지 말것. 어플이 구글 플레이에서 다운로드 해야 할 때, 사용자는 바로 같은 곳에 저장해야 한다.

정해진 위치는 다음과 같다.

<shared-storage>/Android/obb/<package-name>/

# <shared-storage> 는 공유 공간이며 getExternalStorageDirectory() 로 받는다.

# <package-name>  은 getPackageName() 으로 얻음.

각 어플에는 메인/패치 파일이 가능. 이전 버전은 새로운 확장 파일로 업데이트하면 overwrite 됨.

파일을 언-팩 해야하면 .obb 파일을 지우지 말고 같은 디렉토리에 풀린 데이터를 저장하지 말라.  압축 해제된 파일은 getExternalFilesDir() 에 지정된 곳에 저장해야 한다. 하지만, 가능하면 해제 과정 없이 확장 파일을 바로 사용하는 것이 제일 좋다. 예를 들면 집 파일을 바로 읽는 라이브러리 같은 경우..

/\/\ Note : APK 파일과는 다르게 어떤 파일이라도 저장 가능하다.

/\/\ Tip : 미디어 파일을 집 압축 한다면 미디어 플레이백 콜을 오프셋, 길이 조절로 부를 수 있다. (MediaPlayer.setDataSource() &  SoundPool.load() ) 압축 해재 없이. 이 과정을 통해 부가적인 압축을 하지 않아도 된다. 예를 들어 집 툴을 쓸 때 -n 옵션을 써서 압축을 안 할 수 있다.

Download process

대부분의 경우 확장 파일은 APK 파일과 함께 다운로드, 저장 된다. 하지만, 어떤 경우에은 구글 플레이는 확장 파일을 다운로드 하지 못하거나 사용자가 이전에 내려받은 파일을 지울 수 있다. 이러한 경우에 대비하여 어플은 파일 자체를 다운로드할 수 있어야 한다.  구글 플레이가 제공하는 URL 을 통해서.

다운로드 프로세스

1. 사용자가 구글 플레이에서 앱 선택.
2. 구글 플레이가 확장파일을 다운로드 할 수 있으면 (대부분의 경우) APK 와 함께 파일 다운로드. 확장 파일을 다운로드 할 수 없을 때는 APK 만 한다.
3. 사용자가 앱을 런칭할 때, 앱은 확장 파일이 있는 지 확인해야 함.  있으면 레디 투 고. 없으면 구글 플레이의 HTTP 에서 다운로드 해야 함. 앱은 구글 플레이 클라이언트에 Application Licensing 서비스를 이용하여 요청해야 한다. 이것은 이름, 파일 크기, URL 에 대응함. 이 정보로 파일을 다운로드 하고 정당한 위치에 저장한다.

주의 : 파일 다운로드 코드를 포함하는 것이 중요하다. 라이브러리 제공했으니 최소의 작업량으로 가능할 것.

Development checklist

내용 요약

1. 50메가 이상이 필요한 지 판단. 가능한 한 작게.. 여러 기기를 위해서라면 복수의 APK  를 고려하라.
2. 메인 확장 파일로 뭘 뺄 지 결정.
3. 기기의 ‘공유 저장 위치’ 로부터 읽어 들이도록 개발. 파일 포맷이 무관하면 ‘집’ 해서 APK Expansion Zip Library 를 이용하도록.
4. 초기에 확장 파일 유무 검사. 없으면 다운로드 받도록. Downloader Library  를 사용하면 간단하다.
테스팅.

Rules and Limitations

애플 앱 리뷰 페이지…

애플 링크 메이커  페이지

 

itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=473894134

http://itunes.apple.com/kr/app/guess-what/id473894134?mt=8

NSString *review_link = @”itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=473894134″;

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:review_link]];

-(void) applicationWillEnterForeground:(UIApplication*)application

IAP plugin 설명..

일단 iTunesConnect 에서 제품 셋업을 하고 테스트 유저 1 ~ 2를 생성하면 테스트 할 준비가 된 것.

씬에 StoreKitManager.cs 스크립트가 연결된 게임 오브젝트가 있어야 한다.

네이티브 코드로 부터의 콜백함수는 그 클래스의 메소드를 부를 것이다.

애플은 구매를 ‘초기화’ 하기 전에 먼저 ‘리트리브’ 하는 앱들을 리젝 시켜왔다.

사용자에게 구매를 허용하기 전에 항상 requestProductData 를 불러주도록.

Server Product model Overview Image

스토어킷은 비동기적으로 이루어지므로 실패 유무의 콜백 함수를 유심히 봐야 한다.

중요한 함수는  productPuchased()  임..  이것이 오면 구매가 원활히 이루어진 것임.

StoreKitManager 네이티브로부터의 콜백을 모두 노출함.

[ storeKitEventListener ] 프리팹을 당신의 씬에 가져다 놓으면 미리 연결된 함수를 통해 모니터링 가능하다.

Xcode 의 로그를 보는 것이 가장 도움이 됨.

또한, StoreKitManager  프리팹은 처음 로딩 씬에 추가되어 있어야 함.

아래는 애플의 가이드라인.  다음의 이유로 product identifier 가 in valid 로 리턴될 수 있음.

# 테스팅 하지 않는 애플 아이디는 완전히 로그아웃 할 것.. ( 세팅 -> 스토어 -> 사인 아웃 )

# 금융 동의에 완전히 서명하지 않음.

# 적당한 ‘제품 아이디’를 쓰지 않음.

# IAP 제품을 for sale 로 클리어 하지 않음.

# 제품 수정이 모든 서버에 전파 되지 않았음.

# 아이튠즈 커넥트의 제품을 리젝했음.

# 앱 리뷰가 준비될때까지 바이너리를 업로드 하지 말것.  개발용 바이너리가 있으면 리젝될 것임.  방법은 :: 앱내구매 없이 올려서 승인을 받고,  바이너리가 승인을 받으면 앱내구매 기능을 테스트 한다. ??

앱내구매 테스트 팁

# 번들 아이디를 유니티 /   Xcode 에서 이중 체크 할 것.

# 프로비저닝 파일 유효

# 테스트 유저만 아이튠즈 커넥트 포탈을 사용할 것.

# 테스트 사이에는 앱을 지울 것..  requestProductData 로부터 데이터를 받을  수 있으므로.

샘플소스는 작동 안될 것임.. 참고용.