본문 바로가기
macOS

objective-c 와 swift 차이점

by vapor3965 2022. 8. 13.

목차

    * 해당 글은 프로그래밍을 swift로 시작하여 익숙해진 다음 objective-c를 공부하면서 어려웠던 경험을 바탕으로 작성한 글입니다. 
    또한 부정확한 개념도 있을 수 있어, 알려주시면 감사하겠습니다!

     

    objectvie-c는 c언어로부터 발전한 언어이기때문에, swift관점에서 objective-c를 이해하려면 다소 어려운 부분이 있습니다. 그래서 swift관점에서 이해하기 보다는, 있는 그대로 받아들이는게 좋은 것 같아요. 

     

     

    objective-c는 헤더파일(.h)이 존재한다.

    • swift는 코드작성을 .swift에서만 작성하면 되는데, objective-c는 .h(헤더파일)와 .m(구현파일) 파일이 존재한다.
    • swift로 개발을 시작한 나에겐 다소 생소했는데, 간단하게 이해하자면, 소스코드의 뼈대라고 생각하면 된다. 헤더파일에 직접적인 구현은 없고, 필요한 메소드, 이니셜라이저, 프로퍼티 등을 정의만 하면된다. 
    • swift에서는 모듈 내부에서 만든 소스코드들은 따로 import하지 않는다. 하지만 objective-c에서는 모듈내부에서 만든 코드라고 할지라도 import해줘야한다. 이때, #import "****.h" 정의하는데, 헤더파일을 import한다. 
    • 구현하다보면 해당 객체의 행위를 외부에 알리고 싶은 부분이 있고, 숨기고 싶은 부분이 있다. swift는 접근지시자를 이용하여 모듈 내,외부 접근가능하도록 할 수 있는데, public 접근지시자를 이용한다면 모듈 외부에서도 접근이 가능해진다. 이처럼 헤더파일은 외부에서 사용하게끔 필요한 행위들을 명시하곤한다. 
    • 그래서 해당 코드가 외부에서 필요하다보면 헤더파일에 작성한다. 
      • 반대로 해당 코드내에서만 필요한 경우에는 구현파일에만 작성하는 경우도 있다. 
    • 개인적인 생각 
      • 헤더파일과 구현파일이 있다는것이 코드를 더 많이 작성해야하는 불편함이 있다고 생각할 수 있는데, 코드를 나중에 보다보면은 헤더파일이 굉장히 유용하다. 해당 객체가 어떤 행위를 하는지를 헤더파일만 봄으로써 대략적으로 유추가 가능해지기 때문. 
      • 물론 swift에서도 .swift파일에서 control + command + 방향키⬆ 를 누르면 (interface)라는 파일이 나타나면서 internal 이상의 지시자가 포함된 정의코드만 보여주기도 한다. 

    objective-c는 객체를 @interface @implementation으로 표현한다. 

    • 객체를 표현하고자 할때, 헤더파일에 @interface 으로 시작하는 코드를 작성한다.  @end 가 마침표.
    • interface는 말그대로 정의만 한다.  위에 헤더파일관련해서 언급했듯이 주로 헤더파일에서 작성하고, 외부에 알리고 싶은 메소드나 이니셜라이저, 프로퍼티등을 정의한다. 
      • 위에서도 언급했지만, 해당 파일내에서만 사용하고 싶은 객체가 있다면, .m파일에서 interface를 정의해도된다. 
    • .m (구현파일)에서 @implementation으로 시작하는 코드를 작성하여, interface에 정의한 메소드들을 실제로 구현하면 된다.  마찬가지로 @end가 마침표.


    objective-c는 포인터가 기본이다.

    간단하게 C언어에서 포인터를 정리하면, 

    • 포인터는 포인터변수라고도 불리는데, 변수의 메모리 주소를 가리킨다( 메모리 주소를 저장한다 ).  변수는 무언가를 담는다.
      • int num = 4; num변수는 int형 4를 담고있고, 
      • int *pointer = #  pointer변수는 num 변수가 저장된 주소를 가리킵니다. 
        • &는 해당 변수의 주소를 반환합니다.
      • 햇갈릴 수 있는데, num, pointer 모두 변수이므로, 당연히 해당 변수들은 각각 다른 메모리 주소를 가집니다.  
      • 즉, num의 메모리주소와, pointer는 같지만, pointer와 pointer의 메모리주소는 다르죠 
      • 포인터의 실제값 ( 메모리 주소에 해당하는 실제 값 )을 참조하기 위해서는 *를 사용합니다.
        • *pointer += 5; 를 하게되면, num의 값은 9가 되어있습니다. 
      • 포인터가 필요한 이유는, 함수에서 진가를 발휘하는데, 함수에서 매개변수는 복사하여 전달하기 때문에, 함수안에서 매개변수의 변경은 반영이 되지 못합니다. 하지만 포인터로 전달한다면, 포인터가 가르키는 주소값을 직접적으로 접근하여 변경이 가능해집니다. 
    • 사실 위의 포인터개념을 모르더라도 objective-c에서는 크게 어렵지 않습니다.  간단하게 생각하면 클래스타입들은 포인터를 사용하고, 클래스타입이 아닌 경우는 포인터를 사용하지 않습니다. 명시할때만 포인터를 사용하지, 일반적으로 프로퍼티 접근이나, 메소드호출은 swift와 비슷하다고 생각합니다. 

    클래스

    • 모든 클래스들은 NSObject를 상속받습니다. 그러므로, NSObject를 상속받는 객체들은 포인터를 사용합니다. 
      • ( 여기서 잠시 언급하자면, 많은 Cocoa framework에는 NS으로 시작하는 네이밍이 많은데요, NextSTEP라이브러리에서 가져왔기 때문입니다.  NextSTEP은 스티브잡스가 일했던 곳이기도 해요. )
    • NSString, NSArray, NSDictionary...들도 NSObject를 상속받으므로, 포인터를 사용합니다.
      • 잠깐 설명하면, NSNumber 와 NSInteger가 있는데, NSNumber는 클래스이고, NSInteger는 아키텍쳐에 맞게 설정되는 애플의 Foundation에 있는 특별한 원시타입이다. 
    • 모든 클래스들은 메모리할당과 초기화를 합니다.  기본적으로 NSObbject에 alloc과 init이 있습니다. 
      • 몇몇 특정 객체는 메모리할당과 초기화를 한번에 할 수 있도록 간편한 지시자 (@)를 제공합니다.

    • 클래스가 아닌 타입은 원시타입이라고 부르기도 하는데, c에서 넘어온 타입들입니다. int, bool....   요런애들은 포인터를 사용하지 않는데, 클래스타입안에서 사용이 불가합니다. 예를 들어, NSArray나, NSDictionary들은 원소들이 모두 NSObject로 이루어져야 합니다. 그리고 주의할점이 NSNumber나 NSString끼리 값을 비교할 때는 == 으로는 할수 없어요. 원시타입들만 가능합니다. 그래서 해당 클래스의 메소드를 이용해서 비교해야합니다.  [NSString isEqualToString: ] 처럼요. 

     

    ( 번외편 - 매개변수에 **가 들어갔을 때 )

    • 포인터개념과 함수의 매개변수 복사개념만 안다면 넘어가셔도 되는 부분입니다. 제가 몰랐던 부분이라 정리합니다
    • [NSData dataWithContentsOfURL:options:error:] 의 경우 error의 매개변수타입이 NSError ** 로 되어있습니다.
      • **는 포인터의 포인터인데, 즉 원래의 포인터의 주소를 저장하고 있습니다.
    • url로부터 NSData로 가져올건데, 그과정에서 에러가 생기는지 확인하기 위해, 에러를 넣어주고, 아래와 같은 코드로 에러를 확인해야합니다.

    • 주로 이렇게 바깥에서 포인터를 선언하고, 포인터를 전달하지않고, 포인터의 주소( &fileError ) 를 전달하고, 에러가 nil인지 판단합니다. 해당 메소드는 내부적으로 에러가 발생한다면 NSError를 생성해서 포인터의 실제값에 전달하겠죠. 그 이유는 함수는 기본적으로 매개변수를 복사하기 때문입니다.
    • 함수안에서 포인터 매개변수는, 함수안에서 포인터를 새로 생성하고, 포인터가 가르키는 주소만 복사합니다. 포인터가 가르키는 주소만 같을 뿐이지, 아예 다른 포인터인거죠. 그래서 포인터 자체에 새로운 포인터를 대입해도 반영되지 않지만,  포인터의 실제값에 대입하게 되면 외부에서도 반영이 되죠.
    • 비슷하게 만들어보면, 아래와 같이 두가지 메소드로 확인해볼 수 있습니다.

    • 아래와 같이 각 메소드별로 넘겨보면, oldString만 변경된 것을 확인할 수 있습니다.

     

     


    objective-c는 타입을 작성해야 타입추론이 가능하다. 

    • swift는 타입을 명시하지 않아도, 타입추론이 가능한데, objective-c는 작성해야합니다.
    • 그래서 반드시 타입을 명시해야합니다. 이부분은 크게 어려운부분은 아니라, swiftLint에서도 타입을 명시하도록 하는 룰이 기본적으로 있기도 하고, 타입을 명시하는것이 자신뿐만 아니라 다른팀원분들에게도 가독성이 좋아요. 

     

    objective-c는 동적인 언어다. 

    • swift는 타입추론이 가능하고, 그로인해 타입이 다른경우 컴파일타임때 바로 알 수 있습니다. 이것으로 보면 굉장히 안정적인 언어라고 볼 수 있죠. 런타임이전에 미리 알 수 있으니까요. 
    • objective-c는 타입을 작성해야하는데, 타입을 모든타입 ( id ) 이라고 명시할 수 있습니다. 
    • id는 모든 클래스의 타입들을 참조할 수 있습니다. 모든 클래스의 메소드들을 가리킬 수 있습니다. 아래와 같이 작성을 해도 컴파일에러가 나질 않습니다. 런타임때 새로운 객체로 할당할 수도 있습니다. 
    • 즉 런타임때 실제로 해당 메소드를 가리키고 있는지 알 수 있습니다. 만약 런타임때 ViewController가 아니라면, 크래쉬가 나겠죠.

    • 이런 부분이 유용한 부분도 있겠지만, 크래쉬가 날 수 있으므로, 아래와 같이 한번더 안정적으로 체크하는 방법이 있습니다
      • swift에서도 자주봤던 selector인데,  SEL 타입입니다. @selector()구문은 컴파일타임때 알 수 있습니다. 
      • NSSelectorFromString은 런타임때 알 수 있습니다. 
        • NSSelectorFromString을 왜 사용하냐 싶기도 하겠지만, 규칙적으로 객체이름이 포함되는 네이밍으로 메서드를 만들었다면 NSString으로 메서드이름을 만들어서 런타임때 호출할 수 있겠죠

     


    objective-c 메소드 문법 -  반환값을 먼저 명시한다.

    • 위의 사진 코드에서도 볼 수 있듯이 swift랑은 다르게 반환값이 먼저 명시되어있다. 그리고 func 라는 지시자를 사용하지 않는다. 
    • void는 swift에서도 똑같듯이 반환값이 없다는 의미로, swift에서는 생략이 가능하지만, void라도 obejctive-c에서는 명시해야한다. 
    • 메소드 작성법은 (반환타입)메소드이름:(매개변수1타입)매개변수1이름  메소드이름:(매개변수2타입)매개변수2이름....; 으로 이루어진다.
      • 반환타입 이전에 인스턴스 메소드인 경우에는 - 를 붙이고, 클래스 메소드인 경우에는 + 를 붙인다. 
      • 매개변수가 2개이상부터는 메소드이름이 추가로 들어갑니다. 왜냐하면 외부에서 보여져야할 이름이 필요하니까요. 
      • 간단하게 메소드이름:(매개변수타입)매개변수이름  이 반복된다고 생각하면 됩니다. 

    • 매개변수이름은 아래와 같이 사용하면 된다. 

    • 위의 내용들을 swift로 바꿔보면 아래와 같겠다. 


    objective-c 메소드 호출방법 

    • 대괄호를 사용하며, 대괄호의 한쌍이 하나의 반환값을 의미한다고 보면 됩니다. ( 왠만하면 Xcode에서 대괄호입력하면 자동으로 한쌍으로 만들어줘요. 그래서 나름 편해요... )
    • 보통 반환값이 있으면서 매개변수가 없는 경우에는 swift처럼, 5번째줄에 나와있듯이 마침표로 이용해도 됩니다.


    클로저 문법

    • ( 아직까지도 클로저 문법은 난해하다고 생각한다.. )
    • 메서드에서 클로저를 사용하는 경우에는 큰틀은 비슷하다. 클로저의 타입을 명시하고 그다음 매개변수이름을 작성한다. 
      • 클로저의 타입은, 클로저의 return타입과, (^) , 클로저에 들어가는 매개변수들을 정의면된다. 

    • 다음과 같이 호출하면 된다. 

    • 인스턴스변수로 사용할 경우에는, 클로저의 return타입을 먼저 명시하고, 해당 변수이름을 ^뒤에 작성해주고,  클로저의 매개변수들을 명시하면된다. 

     


    Enumeration 

    • swift랑은 다르게 기능이 굉장히 제한적이다.  swift의 enum은 struct, class처럼 메소드도 작성할 수 있고, 다양한 기능이 많지만, objective-c에서는 타입만 명시할 수 있다.  swift의 enum처럼 더 많이 활용하려면 따로 객체로 만들어야한다. 

    • 아래와 같이 사용할 수 있다.


    인스턴스 변수와 @property 

    • 인스턴스 변수를 아래와 같이 두가지로 작성할 수 있는데, 간단히 얘기하면 @property를 이용하게 되면 편리한 점이 많다.  자동적으로 해당 변수의 getter, setter를 내부적으로 만들어주고, 접근이 용이해진다.  해당 클래스 내부에서 해당 프로퍼티는 self로 접근하거나, 언더바+이름으로 (_value)접근할 수 있다. 

    • 헤더파일에 프로퍼티를 명시하면 외부에서도 접근이 가능해지는데, 해당 객체 내부에서만 사용하고 싶다면, 구현파일에 아래와 같이 작성하면 된다.
    • interface   () 로 이루어지는데,  () 이거는 Objective-c의 Category 기능의 한 부분 ( extension ) 이라 한다. swift의 extension과는 다른 듯 하다. 주로 private한 프로퍼티를 명시하기 위해 사용한다. 


    Category 

    • Category는 swift의 extension과 비슷한 역할을 한다. 특정 클래스에 기능을 확장하는 면에서 비슷하다.
    • swift관점에서 보자면.. swift의 extension을 독립적으로 사용할 수 있도록 한게 Category라고 생각한다.
    • Category는 아래와 같이 특정클래스+이름 으로 이루어지게 된다.

    • 보면 알 수 있듯이,  특정 소스코드에서 ViewController.h 만 import한다고 해서 +Post, +Network에 작성된 메소드들을 사용할 수 없다.  즉 ViewController+Post.h , ViewController+Network.h 모두 import해야만, 각각 확장된 메소드들을 사용할 수가 있다.
    • 그래서 특정 모듈이나 특정 목적에만 맞도록 Category를 만들어서 사용하는 듯 하다. 

     

     

     

     

    'macOS' 카테고리의 다른 글

    macOS - 샌드박스 및 북마크 개념  (0) 2022.04.10

    댓글