IT_Programming/Objective-C · Swift · iOS

Swift 프로젝트에서 Objective-C 코드를 함께 사용하기 / Objective-C 프로젝트에서 Swift 코드 사용하기

JJun ™ 2016. 8. 8. 12:39



 [출처]

 : http://seorenn.blogspot.kr/2014/07/swift-objective-c.html

 : http://seorenn.blogspot.kr/2014/08/objective-c-swift.html

 : http://funnyrella.blogspot.kr/2015/12/109-objective-c-swift.html




Swift 프로젝트에서 Objective-C 코드를 함께 사용하기

여러가지 이유가 있겠지만 두 가지 이상의 언어로 프로젝트를 진행하는 것도 종종 있다.
예를 들어 특정 언어로 작성된 써드파티 라이브러리를 가져다 쓴다거나 혹은 특수한 퍼포먼스 로직이
필요할 경우 등등이다. Xcode 에서는 스위프트(Swift) 프로젝트에서 C나 Objective-C 언어로 코딩된
파일을 함께 빌드하는 것이 가능하다.

이 글은 스위프트(Swift)를 주 언어로 설정한 프로젝트에서 Objective-C 코드를 함께 사용하는 방법을
기술한다. 반대로 Objective-C를 주 언어로 사용하는 프로젝트의 경우는 방법이 다르므로 관련글을
참고하자.

동일한 내용의 스크린캐스트

일단 특정 스위프트 프로젝트가 있다고 가정하자. Xcode 6 부터는 아래 스크린샷 처럼 프로젝트를
만들 때 사용하려는 주 언어를 설정하도록 되어 있으니 무슨 의미인지는 알 것이다.

복소수(complex) 예제가 아닌데 이름을 잘못 지었다! ;ㅁ;


이 프로젝트를 작업하는 도중 Objective-C 코드를 써야 할 일이 있다면?
그냥 평소에 하던대로 New File 메뉴를 이용해 Objective-C 클래스를 추가한다.




이렇게 Objective-C 클래스를 처음 추가하면 아래와 같이 브릿지 헤더를 만들겠냐고 물어온다.


이 브릿지 헤더는 스위프트와 Objective-C 코드를 같이 쓰기 위해서 꼭 필요하다.
따라서 당연히 YES를 선택 해야 한다. 그러면 프로젝트이름-Bridging-Header.h 라는 이름의 파일이
프로젝트에 추가된다.

참고: 위의 브릿지 헤더 추가 다이얼로그가 자동으로 안뜨는 경우가 있다. 버그라던가, 혹은 수동으로 파일을 프로젝트 디렉토리에 복사해 넣고 Add files를 하는 경우 등등의 경우는 뜨지 않았다. 이럴때는 그냥 수동으로 New File 메뉴를 이용해 C 헤더 파일을 동일한 방식의 이름을 지어서 프로젝트에 추가하자.



이름이 길어서 슬픈 파일이여... ;ㅁ;


애플에서는 이 브릿지 파일 이름을 'Projectname-Bridging-Header.h' 와 같은 식으로 짓는 것을 권장한다. 하지만 꼭 이렇게 지어야 할 필요는 없는 것 같다. 왜냐하면 빌드 세팅에서 이 브릿지 정의 헤더 경로와 파일 이름을 설정해 주어야 하기 때문이다.

위의 방식으로 브릿지 헤더가 자동으로 추가된 경우에는 아래와 같이 빌드 세팅(Build Settings)의
Objective-C Bridging Header 항목에 자동으로 해당 파일의 경로가 추가된다.


만약 브릿지 파일을 수동으로 프로젝트에 추가했다면 동일하게 빌드 세팅(Build Settings)에서
Objective-C Bridging Header 섹션에 브릿지 파일 경로를 추가해주자.
보통(?)이라면$(SRCROOT)/ProjectName/ProjectName-Bridging-Header.h 와 같은 식으로 입력하면 된다.

이제 브릿지 파일을 편집하자. 자동으든 수동이든 추가한 브릿지 파일(ProjectName-Bridging-Header.h)을 열고 필요한 Objective-C 코드의 헤더 파일을 #import를 이용해 임포트시키는 코드를 작성한다.


사용하려는 Objective-C코드들의 헤더 파일을 임포트 하도록 브릿지 파일 편집이 끝났다면
이제 준비 작업이 끝난 것이다. 의외로 간단해서 참 싱겁다. 어쨌든, 이제 이 Objective-C 클래스나
함수들을 이용하면 된다.

참고: iOS나 OS X용의 일반적인 난이도(?)의 Objective-C 코드라면 여기까지만으로 매핑이 완료된다. 코드를 고치지 않고도 말이다. 물론 좀 고난이도(?)의 C함수 등등을 쓰는 코드가 있다면
Foundation에서 사용되는 타입을 사용하다록 코드를 수정해야 하는 수고가 필요할 수도 있다.

이번 글에서 사용하는 예제는 공백(Scratch)에서 시작한 예제라 Objective-C 코드가 비어있다.
일단 대충 아주 간단하게 Objective-C 파일을 코딩해봤다.


위 스크린샷은 그냥 예제이다.
보다시피 생성자 하나와 메소드 하나가 있고 하는 일은 그냥 로그만 찍는 역활이다.

이제 이 코드를 스위프트 언어로 작성된 AppDelegate.swift 코드에서 실행시켜보자.

위의 예제처럼 그냥 스위프트로 만들어진 코드라고 생각하고 멋대로 코딩하니 의도한대로 잘 동작한다. 코코아 프로젝트라서 맥 데스크탑용 앱이 떴는데 iOS 프로젝트라도 별로 걱정할 것 없이 잘 될 것이다.


마무리

위의 예제는 너무 단순한 예제라서 완벽한 설명은 되지 못 한다.
예를 들어 함수나 메소드 호출 시 파라미터 규격을 어떻게 써야 하는가 등등 말이다.

하지만 걱정할 필요는 없다. 이미 Xcode 6 가 세 번째 베타를 맞이하면서 파운데이션(Foundation) 쪽의 데이터타입 클래스들은 이미 스위프트와 자동으로 호환되게 매핑이 되어있다. 예를 들어 NSArray 라던가 NSDictionary는 Objective-C 에서는 그대로 쓰면 되고 스위프트에서는 Array와 Dictionary 처럼 사용하던대로 쓰면 된다. 이미 CInt 같이 호환성을 위한 타입도 자동으로 어울리는 Int 타입에 매핑되는 듯 점점 스위프트와 Objective-C 혹은 C 파운데이션과의 호환성이 높아져 가고 있다.

다만 완전히 매핑되지 않은 프레임워크, 예를 들어 C API위주로 구성된 Carbon 등의 프레임워크는 완벽한 매핑이 되어있지 않아서 스위프트에서 호출하는게 여간 귀찮은 일이 아니다. 이럴 때는 오히려 위의 방법처럼 Objective-C 코드로 한번 더 감싸서(Wrapping) 쓰는게 더 편할 정도다. :-)







Objective-C 프로젝트에서 Swift 코드 사용하기

앞서 Swift 프로젝트에서 Objective-C 코드를 사용하기에 관한 글을 적으면서 '과연 스위프트 모듈을 Objective-C 프로젝트에서 쓸 일이 있을까' 라고 적긴 했었는데, 실상은 왠지 쓸 수도 있을 것 같다는 느낌이었다. 그래서 관련 내용을 글로 정리해본다.
동일한 내용의 스크린캐스트

Objective-C 프로젝트에서 Swift 코드를 사용하기는 반대의 경우보다는 좀 더 간단하다. 하지만 공식 영문 문서의 내용 자체가 좀 난해한 편이었다.

우선 예제를 보자. 아래는 사용하려는 Swift 코드이다. 
class SwiftModule {
    func run() {
        println("This is SwiftModule's run() method")
    }
}
코드 내용은 설명하지 않아도 될 정도로 단순하다.

자 이제 위 코드를 사용하려는 Objective-C 코드 예제를 보자. 
@interface OBjCModule : NSObject
- (void)test;
@end
@implementation OBjCModule
- (void)test {
    SwiftModule *obj = [[SwiftModule alloc] init];
    [obj run];
}
@end
앞서 스위프트 코드로 만든 SwiftModule 이라는 클래스의 오브젝트를 만들어서 run() 메소드를
실행시키는 단순한 예제이다.

여기까지 코딩하고 나서 빌드를 해 보자. 100% 오류가 발생한다. -_-;;;

@objc

스위프트에는 @objc 라는 키워드가 있다. 이 키워드는 심볼을 Objective-C 네임스페이스에 알려주는
역활을 한다. 즉, 기본적으로 Swift 모듈은 Objective-C 쪽 코드에서는 찾을 수 없게 되어있는데
이를 가능하게 해 주는 것이 바로 이 @objc 키워드이다.

앞서 코딩한 스위프트 모듈의 예제를 약간 고쳐보자. 
@objc class SwiftModule {
    func run() {
        println("This is SwiftModule's run() method")
    }
}
이제 빌드를 해 보자. 또 에러가 날 것이다. -_-;;;;


헤더 추가 및 프로젝트 설정

Objective-C로 코딩을 해 왔다면 헤더파일(.h) 없이는 해당 클래스를 사용 할 수 없다는 것은 잘 알 것이다. 클래스 등의 심볼의 정의가 없으면 해당 클래스나 기타 함수들을 찾을 수가 없기 때문이다.
그렇다면 Swift 코드의 헤더를 임포트 해야 하는데... 스위프트에는 헤더가 없다.

하지만 걱정할 건 없다.
우선 Objective-C 쪽 코드 상단에 아래와 같은 식으로 헤더 파일을 임포트 시키자. 
#import “ProjectName-Swift.h”

ProjectName-Swift.h 라는 파일이름은 말 그대로 프로젝트 이름 뒤에 '-Swfit.h'를 붙여서 만드는 이름이다. 예를 들어 프로젝트 이름이 MyProject 라면 MyProject-Swift.h 파일을 임포트 시켜야 된다. 만약 하이픈(빼기 -) 등의 이름으로 쓰기엔 부적합한 특수 키워드가 있다면 언더스코프 (underscope, '_', 언더라인 등)으로 바꿔서 적어주면 된다.

실제 프로젝트에 이 헤더파일을 추가 할 필요는 없다. 이 파일은 Swift 코드가 빌드되면서 자동으로 생성된다. 물론 이를 위해서 Xcode상에서 프로젝트(혹은 타겟)의 설정에 약간의 수정이 필요하다.

프로젝트나 타겟의 Build Settings 에서 Packaging 을 찾는다. 이 항목에서 Defines Modules 항목을 YES로 설정한다. 그리고 Product Module Name 항목은 현재 프로젝트 이름을 넣어주자. Product Module Name 항목은 기본적으로 프로젝트 이름 뒤에 특수 아이디가 붙는 형태로 기본적으로 설정되어 있는데 이러면 제대로 못 찾기 때문에 그냥 프로젝트 이름을 또박또박 적어주면 된다.

이제 빌드를 해 보자. 상황에 따라 될 수도 있겠지만, 위 예제 대로라면 아마 빌드 오류가 날 것이다. 

init이 없다?

위의 스위프트 코드 예제의 경우에는 init 이라는 생성자를 만들어 두지 않았다. 따라서 Objective-C 측의 코드에서 init을 못 찾는다는 오류가 발생한다.

그렇다면 해결법은 init을 만들어 주는 건데 귀찮다. 내 경우 아래와 같은 식으로 스위프트측 코드를 수정했다. 

@objc class SwiftModule: NSObject {
    func run() {
        println("This is SwiftModule's run() method")
    }
}
뭐가 바꼈는지 찾았고 이게 무엇인지 안다면 이해는 될 것이다. 바로 NSObject를 상속받은 건데, 이건 Objective-C 의 최상위 클래스이고 그래서 기본 init도 이미 만들어져 있다. 그래서 상속받는 것 만으로 문제가 해결된다.

혹시나 이걸 상속받는게 꺼림칙하다면 init을 만들어주면 아마도 될 것이다.

여기까지의 내용으로 일단 빌드가 되어야 한다. 아마도 될 것이다. -_-; 

@objc 추가 정보

앞서 이야기 한 대로, @objc의 역활은 Objective-C 측에서 Swift 코드의 심볼을 파악 할 수 있게 해 주는 용도이다. 추가로, @objc 는 별칭을 붙이는 기능이 있다. 
@objc(SuperGoodClass)
public class 짱좋은클래스 {
    // ...
}
스위프트의 심볼은 영문자 외에도 유니코드 대부분의 문자를 쓸 수 있다. 하지만 이런 유니코드는 Objective-C 측에서 인식하지 못 하는 심볼이다. 그래서 @objc 커맨드를 통해 Objective-C 쪽에서 이해가 가능한 이름을 붙여줄 수 있다.

그런데 이게 실제로 잘 작동하는지는 의문이다. -_-;;; 해 본 적이 없어서...





109. Objective-C 프로젝트안에서 Swift 코드 사용하고 싶을 때


: Swift에서 Objective-C 코는 브릿지 헤더파일에서 import만 하고 쓰면 되는 듯 하고, 
: 아직은 Objective-C 프로젝트를 생성하고 있는데, 간혹 샘플은 Swift로 된 것들이 있어서 일일이 Objective-c로 찾아 쓰기
  귀찮아서… 걍 Swift코드도 필요한 경우 같이 쓸 때 편함



<파일만들기>

(1) .swift 파일 만듦  ☞  .swift는 별도의 헤더파일이 없다
   자동으로 BookPlayer-Bridging-Header.h 만들까요? 물으면 yes! (이건 .swift파일에서 Objective-C헤더 import시키는 것임)
   Project > Build Setting > Swift Compiler - Code Generation > Objective-C Bridging Header에 자동으로
   BookPlayer/BookPlayer-Bridging-Header.h 들어감.
  (수동으로 하고 싶으면 여기 셋팅, 브릿지 헤더파일은 물리적으로는 소스파일루트에 존재)

(2) Project > Build Setting > Packaging > Defines Module을 YES로 만든다




<Swift파일 작성>

(1) @objc (UtilModule) 
     ☞ 옆에 이름은 안써도 된다. 이 키워드는 상속받은 경우 넣음. 인스턴스 클래스는 init을 반드시 정의해야 하니…
         귀찮으면 걍 NSObject 상속 받아서 필요한 메소드만 추가

(2) 함수정의 시 'class func'쓰면 클래스함수(Class Method)

(3) 함수정의 시 'func'쓰면 인스턴스함수(Intance Method)


@objc (UtilWModule)
class UtilWModule : NSObject {
    class func determineDuration(url: NSURL) -> NSTimeInterval { // Class Method : "class" 키워드 붙임
         ……
    }
    func hi(name : NSString) { //Instance Method
         ……
    }
}



<Objective-C에서 사용>

(1) #import “TestSample-Swift.h"
☞  #import "프로젝스명-Swift.h", xcode에서 알아서 헤더파일을 정의를 해줌, 이 형태로 가져다가 쓰면 됨

(2) 클래스 함수 사용
float duration = [UtilWModule determineDuration:self.audioUrl];

(3) 인스턴스 함수 사용
UtilWModule *utilW = [[UtilWModule alloc]init];
[utilW hi:@“funnyrella”]; //output ==> Hi!  funnyrella



[참고]

* Swift파일에서 Objective-C파일 import시키기 : Xcode Swift Project with Objective-C Codes

* Objective-C파일에서 Swift파일 import시키기 : Xcode Objective-C Project with Swift Codes

  2) http://seorenn.blogspot.kr/2014/08/objective-c-swift.html