채팅 서버로 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.

반응형

웹킷의 웹뷰에서 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")

 

반응형

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

스토리보드를 이용해서 뷰의 아웃렛을 작성할 때 다음 그림처럼 Storage 속성에서 Strong과 Weak 중 하나를 선택해야 한다.




Strong은 객체를 소유해서 레퍼런스 카운트가 증가하는 프로퍼티고 Weak은 객체를 소유하지 않는 포인터다.


Swift 에서는 다음과 같이 생성된다.


@IBOutlet var strongView: MyView!

@IBOutlet weak var weakView: MyView!


오브젝티브 씨에서는 다음과 같이 생성된다.


@property (strong, nonatomic) IBOutlet MyView *strongView;

@property (weak, nonatomic) IBOutlet  MyView *weakView;


Strong으로 선택하면, 뷰 컨트롤러에 작성한 프로퍼티가 뷰를 소유하고 있으므로, 뷰를 removeFromSuperview 메소드로 씬에서 삭제해도 메모리에서 해제되지 않는다. 이에 비해서 Weak로 생성한 아웃렛 프로퍼티는 뷰를 소유하지 않으므로, 뷰를 삭제하면 메모리에서 해제된다.


strongView.removeFromSuperview()      // 메모리에서 해제 안됨

weakView.removeFromSuperview()        // 메모리에서 해제



== 샘플 코드


간단하게 Strong과 Weak 로 아웃렛을 연결하고, 클래스의 deinit 메소드를 이용해서 메모리에서 해제될 때 콘솔에 출력하도록 작성했다.


ViewWithStrongAndWeak.zip





== Tip


뷰를 씬에 삭제했다가 다시 추가해야 하는 상황이라면 Strong 으로 작성하고, 그 외에는 Weak로 작성한다.









반응형

Swift 의 문자열 자르기(substring) 기능은 매우 귀찮다!


var str = "Hello, Swift Language"


Swift에서 문자열을 자르는 함수 - substring은 String.Index 를 사용하는데, 이게 임의의 값을 설정할 수가 없다!

advance 함수를 이용해서 일정 거리(distance)만큼 밀어버린 인덱스(String.Index)를 생성해서 잘라내야 한다.


let startIndex = advance(str.startIndex, 1)

let endIndex = advance(str.startIndex, 5)

str.substringToIndex(endIndex)


다음은 범위(Range)로 문자열을 자르는 코드다.


let range = startIndex...endIndex

str.substringWithRange(range)



차라리!!!

다음과 같이 NSString 으로 변환해서 처리하는 게 더 간단하다.


(str as NSString).substringToIndex(5)

(str as NSString).substringWithRange(NSMakeRange(1, 5))



혹은

다음과 같이 String을 확장해서 사용하기도 한다.


extension String {

    subscript(range:Range<Int>) -> String {

        let start = advance(startIndex, range.startIndex, endIndex)

        let end = advance(startIndex, range.endIndex, endIndex)

        return self[start...end]

    }

}


str[1...4]

str[1..<3]

반응형


플레이 그라운드로 스위프트 코드를 작성하다, 이미지 파일 참조가 안 되는 현상이 발생했다.


분명 스위프트 코드와 같은 디렉토리에 파일을 복사하고 상대 경로로 접근하려니, 접근이 안된다.


let fm : NSFileManager = NSFileManager.defaultManager()


let filePath = "./image1.png"

fm.fileExistsAtPath(filePath)


스위프트 파일이 저장된 위치와 스위프트 파일이 실행되는 경로가 다른 점이 그 원인이다. 실행 디렉토리를 찾아보자!


fm.currentDirectoryPath



다음과 같이 라이브러리 폴더가 찍힌다.


/Users/USER/Library/Containers/com.apple.dt.playground.stub.OSX.PLAYGROUND-FILE-NAME/Data



다음과 같이 라이브러리 폴더가 찍힌다.


/Users/USER/Library/Containers/com.apple.dt.playground.stub.OSX.PLAYGROUND-FILE-NAME/Data


스위프트 실행 환경을 iOS로 하니까 시뮬레이터 위치다.


/Users/USER/Library/Developer/CoreSimulator/Devices/842AA796-DAA0-4EA4-A9F4-AB1C3BD00A12/data


결국 절대 경로로 해결..




반응형

json 파싱 예제 코드 


플레이그라운드에서 json을 파싱해서 제목과 가수 이름만 출력하도록 작성했다.


json은 애플의 25 Top Song RSS의 json 버전을 이용.



다음은 코드.


Objective-C 에 비해서 타입 체크가 너무 강해졌다.

let urlStr = "http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/topsongs/limit=25/json"


let url = NSURL.URLWithString(urlStr)

let data = NSData(contentsOfURL: url)

var error : NSError?


// 반환 타입이 AnyObject!

let result : AnyObject = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &error)



let entries = result["feed"]!["entry"]! as [AnyObject]


for one in entries {

    let name = one["im:name"]!["label"]! as String

    let artist = one["im:artist"]!["label"]! as String

    

    println("Song - name : " + name + " artist : " + artist)

}


반응형

iOS7과 iOS7.1의 변경 사항 - Aspect Ratio Constraint


다음은 iOS7용 인터페이스 빌더의 모습이다. 간격(Spacing)이나 크기(Width, Height) 등을 설정할 수 있다.



다음 그림은 iOS7.1b 용 Xcode(5.1)의 인터페이스 빌더의 모습이다. Aspect Ratio를 설정할 수 있다.



다음 그림은 비율 제약조건의 인스펙터의 모습이다.




코드로 해야 하는 부분이 인터페이스 빌더로도 가능해졌다. 그런데 문제는 스토리보드 파일 호환이 안된다.





반응형

+ Recent posts