채팅 서버로 Rocket chat을 사용하고 있다.

초기 http로 설정하고 이후 https를 적용하고, http -> https 리다이렉션 설정을 했더니 js 로딩시 http가 혼합되는 Mixed Content Error가 발생한다.

해결책을 구글링하다가 발견해서 적용해본자.

Rocket.chat은 도커로 동작 중. 몽고디비에 접속한다.

몽고디비 컨테이너 ID 확인

$ sudo docker ps

CONTAINER ID   IMAGE 
cea589b3ca4a   rocket.chat:latest
809670637dcf   mongo:4.0

컨테이너에 접속하면서 몽고디비에 접속

$ sudo docker exec -it 809670637dcf mongo

 

Rocket.chat 대화가 저장되는 몽고디비의 데이터베이스 사용

> use rocketchat;

설정은 rocketchat_settings 콜렉션에 있다. 이 콜렉션에서 URL 설정값을 확인한다.

db.rocketchat_settings.find({_id: 'Site_Url'});

{ "_id" : "Site_Url", "value" : "http://chat.xxx.yyy" }

Site_Url에서 value 필드의 값을 https로 바꾼다.

> db.rocketchat_settings.update({_id: 'Site_Url'}, { $set: {value: "https://chat.xxx.yyy"}})

 

다시 https로 접속하니 http mixed content 에러가 발생하지 않는다!

 

ps.

관리자 메뉴 일반 > 사이트 URL 에서 설정값 입력이 가능한거 같다. 뭔 삽질인가.

fin.

반응형

안드로이드 스튜디오

Android studio는 애플 실리콘 맥에서 로제타2 기반으로 동작한다. 앱 실행은 실기기에는 잘 되지만 에뮬레이터에서 동작하지 않는 한계가 있다.

 

안드로이드 에뮬레이터

특정 버전에서 앱 동작을 테스트하려면 어쩔 수 없이 기기 대신 에뮬레이터를 써야하는 상황이 발생한다.

구글링을 해보니 애플 실리콘용 에뮬레이터 프리뷰 프로젝트를 발견했다. 사이트 주소는 github.com/741g/android-emulator-m1-preview 이다.

릴리즈 페이지(github.com/741g/android-emulator-m1-preview/releases)로 이동하고 Assets을 확장하면 다음과 같이 다운로드 링크가 나타난다.

dmg 파일을 다운로드하고 설치한다. 

인증되지 않은 개발자 경고가 나타나는데, 시스템 환경 설정 -> 보안 및 개인 정보 보호 앱 동작을 허용하면 에뮬레이터가 동작한다. 다음은 에뮬레이터 이미지다.

 

ADB 오류

처음 에뮬레이터가 동작하면서 다음과 같이 ADB 경로 문제가 발생했다.

에뮬레이터의 사이드 메뉴에서 설정으로 이동하고, Use detected ADB location 항목을 Off하고 직접 adb 경로를 선택한다. Android SDK가 유저 폴더의 라이브러리에 설치되어 있는데 이는 숨겨진 폴더라서 파일 다이얼로그에 나타나지 않는다. Command + Shift + [.] 를 누르면 숨겨진 폴더도 나타난다. 이를 이용해서 adb 경로를 선택한다.

에뮬레이터에 앱 동작

에뮬레이터가 동작하면 안드로이드 스튜디오의 기기 목록에 다음과 같이 Virtual Device로 나타난다. 이제 애플 실리콘 맥에 안드로이드 에뮬레이터를 사용할 수 있다.

 

플레이 서비스

m1 안드로이드 에뮬레이터 프리뷰2로 설치해보니, 플레이 서비스가 설치되지 않아서 앱 동작이 실패했다.

프리뷰 v3에 구글 API 시스템 이미지가 포함되어 있다길래, 이를 이용해서 에뮬레이터에 앱이 동작시켰다. 이제 구글 플레이 서비스가 없다는 오류가 발생하지 않는다.

 

반응형

Apple Silicon 인 m1이 장착된 맥미니를 구입해서 개발 환경을 세팅하고 있다. 이를 기록해놓는다.

Xcode

Native 로 바로 설치 가능하다

Homebrew

설치하기

arm(apple silicon)용과 x86용으로 2개 설치한다.

다음은 arm 용으로 Homebrew를 설치하는 명령이다. /opt/homebrew에 설치된다.

/bin/bash \-c "$(curl \-fsSL [https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh](https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh))"

다음은 x86 용으로 Homebrew 설치하는 명령이다. /usr/local/homebrew에 설치되고 로제타2를 이용해서 동작한다.

arch -x86_64 /bin/bash \-c "$(curl \-fsSL [https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh](https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh))"

설정하기

arm과 x86이 필요한 상황에 따라서 선택해서 사용한다. 그런데 2개 모두 brew 라는 이름이라서 혼동된다. 하나를 변경하는 것이 좋다.

x86용을 ibrew라는 이름으로 사용하기로 결정했다.

~/.zshrc 에 다음과 같이 homebrew 경로와 alias를 입력한다.

alias ibrew='arch -x86_64 /usr/local/bin/brew'
export HOMEBREW="/opt/homebrew/bin"
PATH="$HOMEBREW:$PATH"

참고

https://stackoverflow.com/questions/64882584/how-to-run-the-homebrew-installer-under-rosetta-2-on-m1-macbook

Cocopods

brew를 이용해서 설치했다.

brew install cocoapods

ffi 관련 에러로 제대로 동작하지 않는다.

missing compatible arch in /Library/Ruby/Gems/2.6.0/gems/ffi-1.14.2/lib/ffi_c.bundle - /Library/Ruby/Gems/2.6.0/gems/ffi-1.14.2/lib/ffi_c.bundle

다음과 같이 ffi를 설치한다.

% arch -x86_64 sudo gem install ffi

Pod로 프레임워크 설치할 때는 다음과 같이 입력한다.

% arch -x86_64 pod update

아.. 왜 brew로 cocoapods를 설치했을까...

Node.js

Node.js는 nvm을 이용해서 설치한다.

nvm 설치

nvm 설치하기.

brew install nvm

Node.js 설치하기

v15이상부터 apple silicon을 지원한다. 대신 컴파일 과정이 다소 길다.

nvm install 15

v14는 설치 실패.

반응형

유지보수 중인 앱에 도저히 다크 모드를 지원할 시간과 에너지(+ 예산)가 없어서 다크 모드를 아예 비활성화하기로 한다.

 

Info.plist 에 다음과 같이 User Interface Style 항목을 추가하고 Light를 입력하면 다크 모드가 비활성화된다.

 

 

 

반응형

Xcode11에서 프로젝트를 생성하면 iOS13 이후에서 사용할 수 있는 타입을 사용하기 때문에 iOS 13 이전 버전에서도 동작하는 앱을 작성하려면 약간의 조작을 해야 한다.

 

Xcode11에서 프로젝트를 생성하는 과정에서 스토리보드와 SwiftUI 선택할 수 있다. SwiftUI는 iOS 13 이후부터 사용할 수 있으므로 스토리보드를 선택한다.

 

SwiftUI와 Storyboard 중 선택

 

프로젝트를 생성하고 Deployment info에서 동작 버전을 설정할 수 있다.

 

최소 동작 버전 설정하기

최소 동작 버전을 iOS13 이전으로 낮추면 다음과 같은 에러가 발생한다.

 

버전 에러

 

UIScene, UISceneDelegate 등이 iOS13에 추가된 타입이다 보니 에러가 발생한다. @available 를 사용하라는 권고?가 있지만 다 지워버리도록 하자!

 

프로젝트 내비게이터에서 SceneDelegate.swift 파일 삭제

 

AppDelegate.swift 를 다음과 같이 편집한다.

 

- UIScene에 관련된 코드 제거

- window 프로퍼티 추가

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    // 추가!
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
    
    // 아래 제거
    // MARK: UISceneSession Lifecycle
}

 

Info.plist 에서 Scene에 관련된 항목을 삭제한다.

이제 iOS13 이전 버전에서도 에러 없이 동작한다.

 

과거 버전의 시뮬레이터를 설치하려면 Xcode의 Preferences의 Components 메뉴에서 필요한 버전의 시뮬레이터를 다운로드한다.

 

시뮬레이터 다운로드

시뮬레이터 다운로드를 마치면 Window -> Devices and Simulators 에서 다운로드한 버전에 맞춰서 시뮬레이터 설치한다.

특정 버전의 시뮬레이터 추가하기

 

이제 iOS13 이전 버전에서도 동작한다.

반응형

웹킷의 웹뷰에서 window.webkit.messageHandlers를 이용하면 네이티브 영역(Swift 코드 부분)으로 메세지를 전달할 수 있다.

 

WKUserContentController 를 이용해서 메세지의 이름과 메세지가 전달될 때의 처리할 대상(self)를 등록해야 한다. 다음과 같이 작성하면 될 줄 알았다.....

let contentController = WKUserContentController()
contentController.add(self, name: "hello")
webView.configuration.userContentController = contentController

웹뷰에서 webkit.messageHandlers를 통해서 보내는 메세지는 다음과 같이 작성한 메소드에 메세지 이름과 메세지의 바디 부분을 얻을 수 있다.

extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "hello", let messageBody = message.body as? String {
            print("Hello Message body :", messageBody)
        }
    }
}

다음은 웹뷰에서 webkit.messageHandlers로 메세지를 전달하는 코드다.

window.webkit.messageHandlers.hello.postMessage('Hello WebKit');

코드는 상당히 간단한 편인데 생각대로 동작하지 않았다.

원인을 찾다보니 웹뷰에서 webkit의 값이 undefined이고 그래서 메세지 전달이 안되고 있었다. 다음 그림은 사파리로 웹뷰 콘솔을 확인하는 모습이다. (사파리 -> 개발자용 -> 시뮬레이터 -> Document)

문제가 된 부분은 다음 코드였다.

let contentController = WKUserContentController()
contentController.add(self, name: "hello")
webView.configuration.userContentController = contentController

웹뷰의 configuration.userContentController 와 contentController가 서로 다른 객체였기 때문에 메세지와 메세지 핸들러가 등록되지 않았다. 대충 이러면 되겠지~ 하는 생각으로 코드를 짜다가 몇 시간이나 날린건지..

 

웹뷰의 configuration 객체에는 userContentController가 기본 생성되어 있기 때문에 다음과 같이 작성하면 된다.

let contentController = webView.configuration.userContentController
contentController.add(self, name: "hello")

 

반응형

SwiftUI도 해볼 겸 카탈리나(beta 9)를 설치했다.

 

IP 주소

 

모바히까지는 와아파이 아이콘을 옵션 + 클릭하면 IP 주소가 바로 나타났는데, 카탈리나를 설치하니 더이상 안나온다. 네트워크 환경설정에 가면 나오기는 하는데, 한 번 더 클릭해서 들어가야 하므로 조금(많이) 불편하다.

 

 

 

CocoaPods

 

brew를 이용해서 cocoapods를 설치해서 사용했다. 카탈리나 설치 후 cocoapods 설치는 성공했는데 실행하면 다음과 같은 에러가 발생한다.

/usr/local/bin/pod: /usr/local/Cellar/cocoapods/1.7.5/libexec/bin/pod: /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/bin/ruby: bad interpreter: No such file or directory/usr/local/bin/pod: line 2: /usr/local/Cellar/cocoapods/1.7.5/libexec/bin/pod: Undefined error: 0

구글링해서 다음과 같은 옵션을 주고 설치하라는 글을 보고 설치 시도~. 아까와 같은 에러가 발생하지 않는다.

brew install cocoapods --build-from-source

 

React-Native

 

시뮬레이터

 

Xcode11을 설치했더니 리액티브 네이티브에서 iOS 동작시킬 때 다음과 같은 에러가 발생한다.

error Could not find iPhone X simulator. Run CLI with --verbose flag for more details.

Xcode11에 iPhone X 시뮬레이터가 없어서 발생하는 에러다. 다음과 같이 simulator 옵션을 이용해서 시뮬레이터를 직접 지정해주면 동작한다.

% react-native run-ios --simulator 'iPhone 11'

매번 옵션 주기 귀찮으면 Xcode에서 iPhone X 시뮬레이터를 설치하면 된다.(Window -> Devices and Simulators)

 

 

에러

 

리액트 네이티브(v60)iOS 앱을 동작시키니 아래와 같은 에러가 발생한다.

 

 

구글링을 하니 다음과 같이 node_modules내 리액티 네이티브 코드를 수정하라고 한다. 소스 코드는 node_modules/react-native/React/Base/RCTModuleMethod.mm 93번째 라인.

 

코드

 

반응형

swift로 진행하던 프로젝트를 날 잡고 Swift 1.2로 변경했다. 수정한 내용을 정리해본다.


전역 함수 변경



countElement 함수는 count로 바뀌었다.




Objective-C 타입 호환



Swift 1.1 에서는 NSString과 String 타입 사이에 자동으로 타입 변환이 가능(bridging)했었다.



var str1 : NSString = "NSString String Val"

var str2 : String = str1



Swift 1.2 부터는 NSString과 String 사이에 암시적 타입 변환이 지원되지 않는다. 다음과 같이 as를 이용해서 타입 변환 코드를 작성해야 한다.



var str1 : NSString = "NSString String Val"

var str2 : String = str1 as String


배열도 마찬가지다. Swift 1.1 에서 NSArray의 경우 Array와 임시적으로 타입 변환이 가능했다.



var array1 : NSArray = [1, 2, 3, 4]

var array2 : Array = array1



Swift 1.2 에서는 다음과 같이 NSArray에서 Array 객체를 생성한다.



var array1 : NSArray = [1, 2, 3, 4]

var array2 : Array = Array(array1)




프로퍼티와 메소드


다음과 같이 프로퍼티와 메소드를 작성하면 v1.1 과 v1.2 모두 에러가 발생한다.

class Singer {

  var name : String = "EXID"

  

  func name() -> String {

    return "IU"

  }

}


이 코드를 조금 바꿔보자. 상속을 이용해서 프로퍼티와 메소드를 분리하면 v1.1에서 에러가 발생하지 않는다. 다음은 부모 클래스에 프로퍼티를, 자식 클래스에 메소드를 작성했다.


class Person : NSObject {

  var name : String!

}


class Singer : Person {

  func name() -> String {

    return "IU"

  }

}


Swift 1.2에서는 다음과 같이 에러가 난다. 


셀렉터로 인한 에러이므로 부모 클래스가 NSObject가 아니면 에러가 발생하지 않는다.





타입 변환(down cast)



Swift 1.1까지 타입 캐스팅은 다음과 같았다.


  • as
  • as?


Swift 1.2에 as!가 추가됐다. 이제 다운 캐스팅은 as!를 사용한다.

  • as!


iOS 개발을 하면서 테이블에서 셀 다룰 때 자주 접하게 된다. 재사용 셀을 찾는 코드에서 셀 타입 변환을 as로 했다면,


tableView.dequeueReusableCellWithIdentifier("CustomCell") as CustomWell


Swift 1.2에서 as!를 사용한다.


tableView.dequeueReusableCellWithIdentifier("CustomCell"as

CustomCell

타겟-액션의 액션 메소드에서 센더 타입 캐스팅에서도 as!를 쓰게 된다.


@IBAction func handleSegmentChange(sender : AnyObject) {

  let segmentedControl = sender as! UISegmentedControl

  println("Selection : \(segmentedControl.selectedSegmentIndex)")

}


NSArray나 NSDictionary 에서도 사용하게 된다.



상수 초기화


Swift 1.1 에서 상수는 선언하면서 초기값을 설정해야한다. 1.2 부터 상수를 선언하고 사용하기 전에만 초기값을 설정하면 된다.


let condition = true

let fileName : String


if condition {

    fileName = "image1.png"

}

else {

    fileName = "image2.png"

}




연산자




Swift 1.1에서 |= 연산자를 사용할 수 있다.


var i = true

i |= false



Swift 1.2에서는 안된다. 다음과 같이 작성해야 한다.


var i = true

i = i || false




Set 타입


Set 타입이 생겼다.


var letters = Set<Character>()

var favoriteGenres : Set = ["Rock", "Classical", "Hip hop"]



외부 파라미터 이름


메소드의 파라미터 중 두 번째 파라미터부터 외부 파라미터 이름을 사용해서 호출한다. 다음은 예제로 사용할 코드다. 메소드에 3개의 파라미터가 있고 마지막 파라미터의 타입이 클로저다.


class MyClass {

    func add(val1 : Int, val2 : Int, handler : (Int)->Void ) {

        handler(val1 + val2)

    }

}


Swift 1.1에서, 파라미터 중 마지막 파라미터가 클로저라면 외부 파라미터 이름을 생략할 수 있다.


var obj = MyClass()


obj.add(1, val2: 3, {(result : Int) -> Void in

  println("1 + 3 = \(result)")

})


Swift 1.2에서, 마지막 파라미터가 클로저 타입이라 해도 외부 파라미터 이름을 명시적으로 작성해야 한다.


obj.add(1, val2: 3, handler: {(result : Int) -> Void in

  println("1 + 3 = \(result)")

})



Incremental Build


Xcode 6.3 에서의 가장 큰 변화는 Incremental Build다. 6.3 이전에는 소스 코드 일부만 변경해도 전체를 컴파일 했지만 Xcode 6.3부터는 변경된 부분만 컴파일하는 Incremental Build를 지원한다.


현재 프로젝트에서 빌드 시간 테스트를 해보니 다음과 같다.


테스트 환경 : 5k iMac


Xcode 6.2

전체 빌드 : 46, 44초

코드 일부 수정 후 빌드 : 27, 25초

 Xcode 6.3

전체 빌드 : 53, 55초

코드 일부 수정 후 빌드 : 6, 5.6초


빌드 시간이 확실히 줄었다!



Swift 언어 변환 기능


Xcode에는 Swift 코드 변환 기능을 제공한다.




코드 변환 기능을 사용하면 다음과 코드를 비교하면서 수정할 수 있다.







반응형


노티에 등록된 감시 객체가 삭제되지 않는 문제가 발생! 수 시간의 디버깅 끝에 원인을 찾았다. 노티 센터에 등록한 옵저버와 옵저버를 삭제하는 코드에서의 옵저버 객체가 달랐던 것이다.


감시 객체를 삭제하는 코드는 다음과 같이 작성했다. 즉 self - 뷰 컨트롤러가 옵저버라고 생각하고 짰는데.. 옵저버를 블록 객체로 등록했었다.


NSNotificationCenter.defaultCenter().removeObserver(self)


이 기회에 좀 정리해본다.



== 객체를 옵저버로 사용


노티 발생 여부를 감시하는 옵저버(Observer)를 등록하는 방법은 다음과 같다. 이 메소드에서 노티 센터에 등록되는 옵저버는 observer 파라미터의 객체다.


// 옵저버 객체 : observer

func addObserver(observer: AnyObject, selector aSelector: Selector, name aName: String?, object anObject: AnyObject?)


다음은 viewWillAppear에서 뷰 컨트롤러 객체(self)를 옵저버로 등록하고, viewDidAppear에서 옵저버를 해제하는 코드다. 노티를 2번 발생시키지만 옵저버가 해제된 이후의 알림에는 반응이 없다. 즉 콘솔에 1개의 내용만 출력된다.



class ViewController: UIViewController {

  

  override func viewWillAppear(animated: Bool) {

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleNoti:", name: "NormalNoti", object: nil)

  }

  

  func handleNoti(noti : NSNotification) {

    println("Handling Noti")

  }

  

  override func viewDidAppear(animated: Bool) {    

    NSNotificationCenter.defaultCenter().postNotificationName("NormalNoti", object: nil)

    NSNotificationCenter.defaultCenter().removeObserver(self)

    NSNotificationCenter.defaultCenter().postNotificationName("NormalNoti", object: nil)    

  }

}


== 블록을 옵저버로 사용



다음 메소드는 클로저(블록)을 이용해서 옵저버를 등록하는 메소드다. 이 메소드에서 노티 객체는 메소드에서 반환된 객체가 옵저버가 된다.


// 옵저버 : 반환된 객체

func addObserverForName(name: String?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification!) -> Void) -> NSObjectProtocol


블록을 옵저버로 등록하고 해제하는 코드는 다음과 같다.



class ViewController: UIViewController {

  

  var blockNoti : AnyObject!

  

  override func viewWillAppear(animated: Bool) {

    blockNoti = NSNotificationCenter.defaultCenter().addObserverForName("BlockNoti", object: nil, queue: NSOperationQueue.mainQueue()) { (noti) -> Void in

      println("Block Noti Observer Works")

    }

  }

  

  override func viewDidAppear(animated: Bool) {

    NSNotificationCenter.defaultCenter().postNotificationName("BlockNoti", object: nil)

    NSNotificationCenter.defaultCenter().removeObserver(blockNoti)

    NSNotificationCenter.defaultCenter().postNotificationName("BlockNoti", object: nil)

  }


}





반응형

서버 IP 주소 얻기

function getIPAddress() {


  var interfaces = require('os').networkInterfaces();


  for (var devName in interfaces) {


    var iface = interfaces[devName];



    for (var i = 0; i < iface.length; i++) {


      var alias = iface[i];


      if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal)


        return alias.address;


    }


  }



  return '0.0.0.0';


}


from http://stackoverflow.com/questions/3653065/get-local-ip-address-in-node-js




반응형

'code snippet' 카테고리의 다른 글

[iOS] Swift 1.2  (0) 2015.04.15
[iOS] 노티 옵저버 등록과 해제  (0) 2015.03.13
[Node.js] 서버 IP 얻기  (0) 2015.03.05
[iOS] 뷰의 아웃렛 작성시 weak? strong?  (0) 2014.12.28
[Swift] 문자열 자르기 - substring  (0) 2014.08.10
[Swift] 파일 접근하기  (0) 2014.08.03

+ Recent posts