앱 위변조 제품이 바이너리를 서버에 등록하여 바이너리 사이즈와 info.plist 정보를 저장한 뒤 앱 실행 시 해당 정보를 비교하는 방식이었는데, 이런 형식의 존재 여부가 희미해졌다.


실제 바이너리가 디바이스나 OS 버전에 따라서 달라질 수 있다는 점 때문으로 실제 바이너리의 구성이나 파일 사이즈를 확인하지는 못했지만 이런 증상들이 계속된다고 한다.


그럼, 어떻게 iOS에서 이런 일이 발생하게 된 것일까? (안드로이드의 경우도 이런 형태로 단말에 최적화한 리소스와 바이너리를 구성하게 하는 방향으로 가고 있다.)


http://stackoverflow.com/questions/30722606/what-does-enable-bitcode-do-in-xcode-7

http://www.imore.com/app-thinning-ios-9-explained

http://lowlevelbits.org/bitcode-demystified/



관련해서 스택과 웹에서 관련 내용을 좀 찾아보았다.


관련 이슈는 앱의 용량을 줄이는 슬라이싱(App Slicing)이라는 이슈 아래에 몇 가지 형태의 이슈가 보인다.


아이폰의 경우 단말에 확장 메모리를 추가할 수 없고, 최소 사양의 단말은 항상 16기가 정도를 유지하고 있어서 OS의 업데이트 및 앱 다운로드에 이슈가 되어 왔다.


아마도 이런 문제점을 해소하고 용량의 제약을 해결하기 위해서 그리고 단말의 서로 다른 아키텍처 지원 등이 이슈가 되어 왔던 것 같다.



비트코드의 경우 iOS 9 (Xcode 7)부터 등장한 개념으로 다음과 같이 애플의 문서에 기술되어 있다.



  Bitcode is an intermediate representation of a compiled program. Apps you upload to iTunes Connect that contain bitcode will be compiled and linked on the App Store. Including bitcode will allow Apple to re-optimize your app binary in the future without the need to submit a new version of your app to the store. 





예상해보건대 내가 올린 바이너리를 각 디바이스의 아키텍처 맞도록 다시 컴파일해서 해당 디바이스에 맞도록 바이너리를 생성 후 사용자가 다운로드 받게 해주는 것이다.


내용을 좀 보완을 하자면 향후 추가되는 아키텍처가 있더라도 별도의 바이너리 빌드 후 업로드가 추가적으로 내가 빌드해서 올리지 않아도 될 수도 있다는 것이다.


스택의 글을 읽어보니 비트코드가 re-optimize라는 문구로 인해서 용량이 줄도록 해주는 지 혹은 용량과 관련없이 아키텍처에 최적화에만 맞춰져 있는지 유저간 이야기가 오가는 것을 볼 수 있다.


일단 내 생각에도 이 부분은 용량의 감소보다는 플랫폼에 해당하는 아키텍처에 따른 최적화가 아닐까 생각이 든다.


사용자가 아이튠즈(iTunes)를 통해서 앱을 내려받을 때 사용자의 단말에 적합한 아키텍처에 해당하는 바이너리를 내려줄 수 있다는 이야기인데, 여기에도 약간의 의문이 남는다.


사용자가 비트코드가 적용된 바이너리를 다운받고 사용하면서 iCloud 또는 iTunes 앱을 통해서 앱을 백업하고, 이를 이용하다가 단말의 변경(이전 아키텍처 폰으로 간다든지)할 때에도 모든 앱의 바이너리 적합성을 애플 서버에서 체크하는 지의 여부이다. (아마도 그렇게 구현되었을 것이라고 생각한다.)


만약에 애플이 바이너리를 디바이스에 적합하게 다시 바이너리를 내려준다면 앱 위변조가 사용된 앱들은 검증에 통과하지 못할 것이다.



또한 bitcode 설정이 되었다면 새로운 바이너리를 생성할 때 앱 슬라이싱에 대한 부분이 적용된다고 봐야 한다.


따라서 결국은 적합한 아키텍처를 위한 바이너리 생성을 위해서 bitcode도 앱의 파일 사이즈에 영향은 미치게 될 것이다.


아무튼, 앱 위변조 프로그램의 유용성을 생각해보다가 여기까지 왔는데...실제 이제 앱 위변조 프로그램의 역할이란 단순히 jailbreak를 체크하고 앱의 패키지와 빌드 버전 체크 정도가 아닐까 생각이 든다.


iOS에서는 백신도 앱 위변조도 크게 유용성을 갖지 못하는 것 같다.



iOS는 웹뷰에 문서를 띄워서 볼 수 있다.

웹 주소를 읽어서 문서를 띄울 수도 있고, 로컬 저장소(Document Directory 등)에 있는 파일을 웹뷰에 로드하여 볼 수도 있다.

대부분의 문서 양식(오피스 및 PDF, TXT 등)을 지원하지만 한글(HWP)은 웹뷰에서는 볼 수 없으므로 외부 프로그램을 호출하는 방식으로 열어야 한다.


    // 문서 로드

    NSURL *targetURL = [NSURL URLWithString:self.urlStr];

    NSURLRequest *request = [NSURLRequest requestWithURL:targetURL];

    [_webView loadRequest:request];

    

    // 저장 문서 로드

    NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask ,YES );

    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSString *path = [documentsDirectory stringByAppendingPathComponent:self.fileName];

    [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];



Swift에서 하는 방법은 다음의 링크를 참고한다.





아이폰 작업 시 해상도 및 아이콘 사이즈 확인이 필요할 때 참고할 수 있는 사이트로 아이콘을 만들거나 UI 작업 시 유용하다.


User-Agent 정보를 변경하려고 해보니 다음과 같은 이슈가 있었다.


일반적으로 NSMutableURLRequest를 생성하고, 새로운 항목을 헤더에 추가하는 것은 이슈가 없다.


하지만, 기본적으로 헤더가 가지고 있는 항목인 User-Agent와 같은 경우는 기존의 값에 변경을 가하려면 이런 형태로는 수정한 값으로 반영이 되지 않는다.


    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];

    [request setValue:@"test" forHTTPHeaderField:@"test"];

    [_webView loadRequest:request];



검색을 해보니 NSUserDefault에 UserAgent 값으로 수정할 값을 적용하면, 이후의 헤더 값에 User-Agent가 원하는 형태로 적용이 된다는 것이었다.


    NSDictionary *userAgentDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:@"my user agent string",@"UserAgent", nil];

    [[NSUserDefaults standardUserDefaults] registerDefaults:userAgentDictionary];




그런데, 좀 문제가 발생했다.


웹뷰의 객체를 받아서 현재 웹뷰의 헤더 값을 읽어내고, NSUserDefault에 값을 적용하니 실제로 반영이 되지 않는 것이다. 웹뷰를 한 번 로드하면서 해당 값이 적용되어 버리는 듯이 보였다. 


그래서, 별도의 웹뷰 객체를 하나 생성해서 값을 얻어온 뒤에 저장하는 프로세스를 진행하고 현재의 웹뷰 컨트롤러의 웹뷰를 로드하니 내가 원하는 형태로 User-Agent가 저장되고 로드되었다.


- (void)setUserAgent {


    UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];

    NSString *userAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

    userAgent = [NSString stringWithFormat:@"%@ MY_APP_NAME", userAgent];

    

    NSDictionary *userAgentDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:userAgent,@"UserAgent", nil];

    [[NSUserDefaults standardUserDefaults] registerDefaults:userAgentDictionary];

    

    NSLog(@"userAgent: %@", userAgent);

}


이 형태의 함수를 앱 시작 시 호출하여 적용하니, 내가 원하는대로 기본 User-Agent에 앱 명을 추가한 형태로 로드 되었다.


*. 참조

1) Objective-C에서 설정 방법

http://stackoverflow.com/questions/478387/change-user-agent-in-uiwebview-iphone-sdk


2) Swift에서 설정 방법

http://stackoverflow.com/questions/26219997/how-can-i-set-the-user-agent-header-of-a-uiwebview-in-swift



상태바를 숨기는 게 필요해서 검색해봄


Objective-Cd에서는 해당 뷰컨트롤러에서 다음의 메서드를 호출하여 YES로 적용한다.


- (BOOL)prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO


예)

- (BOOL)prefersStatusBarHidden {

    return YES;

}


Swift에서는 다음과 같이 적용한다.


override var prefersStatusBarHidden: Bool {

    get {

        return true

    }

}



유니버셜 링크 문의가 왔는데, 이런 게 있는 줄도 몰랐네...헛헛헛


서버, 앱, 웹 같이 체크가 필요.


https://okjungsoo.wordpress.com/2016/01/29/유니버셜-링크universal-link/


http://ohgyun.com/706

CentOS 7이 설치된 서버에서 이것 저것 다 해봤다...


근데, IOS만 푸시가 안나간다. 안드로이드는 푸시 전송이 되는데...왜 안되는 걸까?


찾아보니 레드햇 계열의 SELinux 시스템에서는 httpd의 create network connection을 기본적으로 허용하지 않고 있다고 한다.


setsebool -P httpd_can_network_connect=1

와 같이 한 번 설정 변경 후 httpd를 다시 가동하니 정상적으로 작동했다.


앙~ 기모찌


참고:

http://stackoverflow.com/questions/14333013/apple-apns-permission-denied-issue


.p12 파일로 푸시 인증서 생성 한 뒤 진행 (JAVA는 그대로 사용)



 $ openssl pkcs12 -in cert.p12 -out apple_push_notification_production.pem -nodes -clcerts


제어문이 크게 차이는 없지만 생소하기는 하니 플레이 그라운드를 한 번 실행해서 한 번씩 적어본다.



1. for 문


for var i=0; i<10; i++ {

    print("value of i is \(i)");

}


for index in 1...10 {

    print("index is \(index)")

}


이런 형태를 for-in 문이라고 한다. 어레이 형태의 데이터 셋에서 하나씩 비교하여 값을 읽어들일 수 있다.


var count = 0;

for _ in 1...10 {

    count++;

}


언더바(_)는 반복문에서 참조 변수가 필요하지 않을 경우 넣을 수 있다.



2. while 문


var cnt = 0;

while cnt < 100 {

    ++cnt;

}



3. repeat-while 문


var i = 10;

repeat {

    --i;

}

while (i>0)


응? do-while 문이 안보이네...플레이 그라운드에서 자동으로 repeat-while 문으로 변경된다.


반복문의 제어를 위해서 break와 continue 도 동일하게 적용된다.



4. if 문


var a = 10;

if a>9 {

    print("9보다 크다.")

}


if-else 문은 다음과 같이 사용할 수 있다.

if a<=9 {

    print("9보다 작거나 같다.")

}

else if a>10 {

    print("9보다 크다.")

}


API를 체크하는 기본적인 기능이 담겨 있다. 
특정 API를 체크해서 실행이 필요한 경우가 있는데, 다음과 같이 사용한다. 

if #available(iOS 9, OSX 10.10, *) {

    // Use iOS 9 APIs on iOS, and use OS X v10.10 APIs on OS X

} else {

    // Fall back to earlier iOS and OS X APIs

}



5. switch 문

var j = 10

switch (j) {

    case 10:

        print("값은 10");

    case 20:

        print("값은 20");

    default:

        print("10 20 아니다.");

}


일반적인 스위치문은 동일하다. 다음처럼 범위를 지정할수도 있다.

var j = 10

switch (j) {

case 1...10:

    print("값은 1부터 10 사이");

case 11...20:

    print("값은 11부터 20 사이");

default:

    print("1부터 20 사이의 값이 없다.");

}


범위 설정과 where 문 사용으로 좀 더 유연하게 설정할 수 있다.

switch (j) {

case 1...10 where j%2 == 0:

    print("값은 1부터 10 사이의 짝수");

case 11...20 where j%2 == 0:

    print("값은 11부터 20 사이의 짝수");

default:

    print("1부터 20 사이의 값이 없다.");

}


눈치 챘겠지만 break 문이 없다. 그냥 해당 조건을 만나면 케이스문을 실행하고 스위치 문을 종료한다.
이 break 문을 없앤 일반 스위치문과 동일하게 구현하려면 fallthrough 문을 넣어준다.

switch (j) {

case 1...10 where j%2 == 0:

    print("값은 1부터 10 사이의 짝수")

    fallthrough

case 11...20 where j%2 == 0:

    print("값은 11부터 20 사이의 짝수")

    fallthrough

default:

    break    

}


튜플 데이터를 통해서 스위치문을 비교할 수도 있다.


let somePoint = (1, 1)

switch somePoint {

case (0, 0):

    print("(0, 0) is at the origin")

case (_, 0):

    print("(\(somePoint.0), 0) is on the x-axis")

case (0, _):

    print("(0, \(somePoint.1)) is on the y-axis")

case (-2...2, -2...2):

    print("(\(somePoint.0), \(somePoint.1)) is inside the box")

default:

    print("(\(somePoint.0), \(somePoint.1)) is outside of the box")

}


이렇게 임시 변수에 데이터를 바인딩하여 처리할 수도 있다.


let anotherPoint = (2, 0)

switch anotherPoint {

case (let x, 0):

    print("on the x-axis with an x value of \(x)")

case (0, let y):

    print("on the y-axis with a y value of \(y)")

case let (x, y):

    print("somewhere else at (\(x), \(y))")

}

// prints "on the x-axis with an x value of 2"




그냥 입력하다보니 세미콜론을 찍었다가 안찍었다가 했네...그런데, 에러 메시지가 없이 실행된다.

넣어도 되고 않넣어도 되나보다.



Objective-C를 처음으로 개발을 시작해서 그런지 조금 편하다고 느낀다.


하지만 앞으로는 스위프트를 해야 할 것 같다...만약에 계속 프로그램을 개발하게 된다면 말이다.


첫인상

처음 나왔을 때 간단한 코드를 보고 자바 스크립트 같다고 처음에는 느꼈다. 하지만 자바 스크립트와는 다른 듯...

마침표가 없어서 낯설다. 세미콜론이 있어야 문장이 끝나는 느낌인데...없으니 허전하다.


아무튼 첫인상이 주는 느낌은 간단해보이고 낯선 느낌이었는데, PlayGround를 하나 실행하여 줄줄이 쳐본다.



1. 데이터 타입


부호 있는 정수: Int를 쓰면 된다. (Int8, Int16, Int32, Int64)

부호 없는 정수: Uint를 쓰면 된다. (UInt8, UInt16, UInt32, UInt64)


소수: Float, Double 


부울: Bool (true와 false 값을 가진다)


문자: Character


문자열: String


특수문자: 문자열을 다루고 출력에서 많이 사용하는 특수문자는 다음과 같다.

 - \n: 줄 바꿈

 - \r: 캐리지 리턴

 - \t: 탭

 - \\: 역슬래쉬

 - \": 더블 쿼트

 - \': 싱글 쿼트


대충 이정도면 기본 코딩할 수 있겠다.



2. 변수와 상수


var myVariable = 10

let myConstant = 10


var를 붙이면 변수, let를 붙이면 상수가 된다.



3. 변수 선언 / 할당 / 사용 - 옵셔널(optional)


위에서는 선언과 할당을 같이 했다. 변수의 선언만 하고자 할 때에는 옵셔널 타입(optional type)을 사용한다.

변수명 뒤에 콜론을 찍고, 데이터 타입을 적어주는 것을 타입 어노테이션(Type Annotation)이라고 한다. 


var myVariable: Int?

let myConstant: Int?


이 시점의 변수에는 nil의 값을 가지고 있다. 여기에 값을 할당하여 변수가 값을 가지게 되면 랩핑되었다(wrapped)고 표현한다.


myVariable = 10

myConstant = 10


이 옵셔널 타입의 변수를 사용하면 unwrapped 되지 않았다는 메시지가 뜬다. 


print(myVariable)        // Optional(10) 


언랩핑하기 위해서는 해당 변수 뒤에 !를 붙여서 사용할 수 있도록 해주어야 한다.


print(myVariable!)                      // 10


강제로 언랩핑하는 방법이 하나더 있다. 옵셔널 바인딩(optional binding)을 통해서 할당된 값을 임시 변수나 상수에 할당한다.


if var tmpVariable = myVariable {

    print(tmpVariable)        // 10

}


if let tmpConstant = myConstant {

    print(tmpConstant)        // 10

}


이 옵셔널 바인딩은 우선 지정한 옵셔널 변수나 상수가 값을 가지고 있는 지 먼저 체크한다. 그 뒤에 값이 있을 경우 블럭 안의 코드를 실행하도록 한다.


'그런데, 왜 이래야 하나? 번거롭다.' 라고 생각하던 차에 암묵적인 언래핑(Implicitly unwrapped)이 있었다.


var myVariable: Int!

let myConstant: Int!


그렇다. 그냥 이렇게 !를 붙여서 선언하여 사용하면 변수나 상수를 사용 시에 언랩핑을 해줄 필요 없이 사용할 수 있다.


대충 이정도면 변수와 상수를 다룰 수 있겠다.



4. 신기한 튜플(Tuple)


데이터 타입이 다른 값들을 하나로 묶어서 쓸 수 있는 튜플이 있다.


let myTuple = (2, 2.123, "test")

var myTuple2 = (1, 1.1, "test2")

myTuple2 = (2, 2.1, "changed")        // 값이 변경된다.


해당하는 값을 읽어오려면 어레이처럼 0번 인덱스부터 읽어오면 된다.


print(myTuple.0)            // 2


튜플의 값을 하나 꺼내 비교도 할 수 있다.


if myTuple.0 == myTuple2.0 {

    print("\(myTuple.0)와(과) \(myTuple2.0) 값이 같아요.")        // 2와(과) 2의 값이 같아요.

}



튜플의 값을 일괄적으로 다른 변수에 대입할 수도 있다.


var (myInt, myFloat, myString) = myTuple


특정 값을 제외하고 대입할 수도 있다.  이 때는 해당 항목에 '_'(언더바)를 넣어준다.


var (myInt, _, myString) = myTuple


튜플 생성 시에 각 항목에 이름을 넣어줄 수도 있다. 마치 순서있는 딕셔너리(Ordered Dictionary) 같다.


let myTuple3 = (num: 10, height: 10.123, message: "test")


이 때는 값을 이렇게 읽을 수 있다.


print(myTuple3.0)            // 10

print(myTuple3.num)          // 10



책 10 페이지 정도 읽었는데...일단 이정도. 책은 대충보고 애플 문서를 일독해야 겠다.




+ Recent posts