^(코딩캣)^ = @"코딩"하는 고양이;
썸네일 이미지
[번역글] View Controller 사이에 데이터를 교환하는 방법(2) - segue
본 게시글은 How To: Pass Data Between View Controllers in Swift를 바탕으로 작성하였습니다. 여러분의 앱이 여러 개의 사용자 인터페이스(UI)를 가지고 있다면 여러분은 하나의 UI에서 다른 UI로 데이터를 전달해야 하는 경우도 생길 것입니다. Swift에서는 View Controller 사이에 어떤 방법으로 데이터를 전달할 수 있을까요? 뷰 컨트롤러(View Controller) 사이에 데이터를 주고 받는 것은 iOS 개발의 중요한 일부입니다. 여러분은 몇 가지 방법으로 이를 해낼 수 있고 각기 다른 이점과 약점을 가지고 있습니다. 뷰 컨트롤러 사이에 쉽게 데이터를 교환하는 방법을 선택하는 것은 여러분이 앱 구조를 어떻게 할 것인지에 달려 있습니다. 앱의 구조(Ap..
Language/Objective-C & Swift
2021. 9. 14. 11:33

[번역글] View Controller 사이에 데이터를 교환하는 방법(2) - segue

Language/Objective-C & Swift
2021. 9. 14. 11:33

본 게시글은 How To: Pass Data Between View Controllers in Swift를 바탕으로 작성하였습니다.

 

여러분의 앱이 여러 개의 사용자 인터페이스(UI)를 가지고 있다면 여러분은 하나의 UI에서 다른 UI로 데이터를 전달해야 하는 경우도 생길 것입니다. Swift에서는 View Controller 사이에 어떤 방법으로 데이터를 전달할 수 있을까요?

뷰 컨트롤러(View Controller) 사이에 데이터를 주고 받는 것은 iOS 개발의 중요한 일부입니다. 여러분은 몇 가지 방법으로 이를 해낼 수 있고 각기 다른 이점과 약점을 가지고 있습니다.

뷰 컨트롤러 사이에 쉽게 데이터를 교환하는 방법을 선택하는 것은 여러분이 앱 구조를 어떻게 할 것인지에 달려 있습니다. 앱의 구조(App architecture)는 여러분이 뷰 컨트롤러 사이에 어떻게 작동이 이루어 질 것인지에 영향을 주고, 반대로 여러분이 뷰 컨트롤러 사이에 데이터 교환을 어떻게 할 것인지에 따라 앱의 구조가 달라집니다.

 

Swift에서 여러분은 뷰 컨트롤러 사이에 다음의 6가지 방법으로써 데이터를 주고 받을 수 있습니다.

  1. 인스턴스 프로퍼티(property)를 사용하는 방법(A → B 방향)
  2. 스토리보드(Storyboard)와 세그웨(segue)를 사용하는 방법
  3. 인스턴스 프로퍼티와 함수를 사용하는 방법(A ← B 방향)
  4. 델리게이션(delegation) 패턴을 사용하는 방법
  5. 클로저(closure) 또는 핸들러(completion handler)를 사용하는 방법
  6. NotificationCenter 또는 Observer 패턴을 사용하는 방법

 

이 게시글에서 여러분은 뷰 컨트롤러 사이에 데이터를 주고 받는 6가지의 각기 다른 방법에 대해 익히게 될 것입니다. 이 방법에는 프로퍼티(property)를 사용하는 방법, 세그웨(segue)를 사용하는 방법 및 NSNotificationCenter를 사용하는 방법도 들어 있습니다. 비교적 간단한 이들 방법을 통해 좀 더 복잡한 응용까지 나아갈 수 있습니다. 준비가 되었다면 이제 시작하겠습니다.

 

스토리보드(Storyboard)와 세그웨(segue)를 사용하는 방법(A → B 방향)

스토리보드를 사용하고 있다면 여러분은 세그웨(segue)prepare(for:sender:) 메소드를 사용하여 뷰 컨트롤러 사이에 데이터를 전달할 수 있습니다.

스토리보드와 세그웨를 사용하여 뷰 컨트롤러 사이에 데이터를 전달하는 것은 앞서 XIB를 사용한 방법과 크게 다르지 않습니다. 스토리보드와 세그웨의 의미를 간단히 짚고 넘어가자면, 스토리보드는 여러분의 앱이 갖는 사용자 인터페이스들의 집합체입니다. Xcode의 Interface Builder를 가지고 생성할 수 있고 코드 작성을 최소화면서 뷰 컨트롤러 사이의 전환을 만들 수 있습니다.

세그웨(segue)는 부드러운 화면 전환(smooth transition)을 뜻합니다. 여러분이 예를 들어 내비게이션 컨트롤러를 가지고 어떤 뷰 컨트롤러에서 다른 뷰 컨트롤러로 전환시킬 때, 여러분은 이미 세그웨를 만든 것입니다. 여러분의 뷰 컨트롤러에서 이 세그웨에 후킹(hooking)하여 그 작동을 수정할 수도 있습니다. 뷰 컨트롤러 사이에서 발생하는 데이터 교환도 세그웨 과정 도중에 발생합니다.

이번 예제에서 여러분은 MainViewController에서 TertiaryViewController로 전환되는 세그웨를 보게 될 것입니다. 이를 위해 어떤 액션을 한 버튼에서 다른 뷰 컨트롤러로 이어준 다음 "Show" 유형을 선택합니다. 그러면 MainViewController에서 TertiaryViewController로 이어지는 화살표가 나타날 것입니다. 그 다음 뷰 컨트롤러의 "Identity Inspector"에서 커스텀 클래스를 TertiaryViewController로 지정합니다.

TertiaryViewController의 코드는 다음과 같습니다.

// Swift
class TertiaryViewController: UIViewController {
    var username: String = ""
    
    @IBOutlet weak var usernameLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        usernameLabel.text = username
    }
}
// Objective-C
@interface TertiaryViewController: UIViewController {
    @property (atomic, strong) NSString * username;
    @property (nonatomic, weak) IBOutlet UILabel * usernameLabel;
@end
@implement TertiaryViewController
    @synthesize username;
    @synthesize usernameLabel;
    
    -(void) viewDidLoad {
        [super viewDidLoad];
        [[self usernameLabel] setText: [self username]];
    }
}

 

특별할 것도 없이 이 예제는 앞서 살펴보았던 것과 크게 다르지 않습니다. 여러분은 이 예제를 통해 usernameLabel이라는 라벨의 텍스트를 username이라는 프로퍼티가 현재 갖고 있는 문자열로 설정한 것일 뿐입니다.

이제 MainViewController에서 TertiaryViewController로 데이터를 전달해 보겠습니다. 이 때 prepare(for:sender:)라 불리는 특별한 메소드를 사용해야 하는데 이 메소드는 세그웨를 하기 전에 호출되는 메소드이므로 여러분은 이를 사용자화할 수 있습니다. 세그웨 직전 수행하게 될 코드는 다음과 같습니다.

// Swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if (segue.destination is TertiaryViewController) {
        let tertiaryViewController = segue.destination as? TertiaryViewController
        tertiaryViewController.username = "Arthur Dent"
    }
}
// Objective-C
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([[segue destination] isKindOfClass: [TertiaryViewController class]]) {
        TertiaryViewController * tertiaryViewController = (TertiaryViewController *)[segue destination];
        [tertiaryViewController setUsername: @"Arthur Dent"];
    }
}

 

위 코드에서 어떤 작업들이 수행되는지 살펴보겠습니다.

먼저 if문과 is 키워드는 세그웨의 목적지가 TertiaryViewController 클래스형인지를 검사하게 해 줍니다. 모든 세그웨는 이 prepare(for:sender:) 메소드를 거쳐가기 때문에 이 세그웨가 여러분이 사용자화하고 싶은 그 세그웨인지 아닌지를 검사할 필요가 있습니다.

그 다음 username 프로퍼티를 사용하기 위하여 segue.destinationTertiaryViewController 클래스형으로 형변환합니다. segue 매개변수를 통해 전달되는 destination 프로퍼티는 UIViewController 형식으로 되어 있기 때문에 TertiaryViewController에서 선언한 username 프로퍼티를 사용하려면 이처럼 다운캐스트해야 합니다.

마지막으로 username 프로퍼티를 설정합니다. 이는 앞서 살펴 본 예제와 동일합니다.

prepare(for:sender:) 메소드에 대한 재미있는 사실은 여러분이 이 메소드에서 특정 세그웨 이외의 다른 것에 대해서는 작업할 필요가 없다는 것입니다. 이 메소드는 단순히 세그웨에 훅(hook)되기 때문에 화면 전환을 위해 이어서 나머지 작업을 수행하도록 어떤 코드들을 작성할 필요가 없다는 것입니다. 여러분은 여러분이 새로 구성한 뷰 컨트롤러를 반환할 필요도 없습니다.

또한 여러분은 위 Swift 코드를 다음과 같이 축약할 수도 있습니다.

// Swift
if let tertiaryViewController = segue.destination as? TertiaryViewController {
    tertiaryViewController.username = "Ford Perfect"
}

 

destination의 타입을 검사하기 위해 is를 사용하고 캐스트하는 대신 옵셔널 캐스팅(optional casting)을 사용하여 한 문장으로 처리할 수도 있습니다. segue.destinationTertiaryViewController 타입이 아니라면 as? 식은 nil을 반환할 것이고 조건문에 딸린 문장은 실행되지 않을 것입니다.

만일 형변환(type casting)을 사용하고 싶지 않다면 segue.identifier 프로퍼티로 특정 세그웨를 식별할 수도 있습니다. 예를 들어 스토리보드에서 이 프로퍼티에 "tertiaryVC"라는 문자열을 부여한 다음 다음과 같이 코드를 작성할 수도 있습니다.

// Swift
if segue.identifier == "tertiaryVC" {
    // 수행할 작업
}
// Objective-C
if ([[segue identifier] isEqualToString:@"tertiaryVC"]) {
    // 수행할 작업
}

 

세그웨와 스토리보드를 사용하여 두 개의 뷰 컨트롤러 사이에 데이터를 전달하는 방법은 여기까지입니다.

대다수의 앱에서 스토리보드는 여러분이 사용할 수 있는 뷰 컨트롤러 사이의 전환을 제한합니다. 스토리보드는 종종 Xcode에서 사용자 인터페이스 만드는 것을 과도하게 복잡하게 만들기도 하는데 이에 비해 얻는 이점은 다소 적습니다. 무엇보다도 여러분이 복잡한 구조로 스토리보드나 XIB를 작성한다면 Interface Builder는 느려지고 버벅거립니다.

스토리보드로 작성할 수 있는 모든 것은 사실 여러분이 직접 손으로 코딩하여 구현할 수도 있습니다. 이 때는 개발자가 약간의 노력만 한다면 보다 상세한 제어가 가능합니다. 그렇다고 해서 필자는 여러분에게 손으로 일일이 다 코딩하라고 말하는 것은 아닙니다! 위의 예제처럼 하나의 뷰 컨트롤러당 하나의 XIB를 작성하고, UITableViewCell과 같은 서브 클래스 뷰(sub class view) 하나당 하나의 XIB를 작성합니다.

코더로서 궁극적으로 여러분은 탭 문자로 들여쓸 것인지 스페이스로 들여쓸 것인지, 스토리보드를 사용할 것인지 XIB를 사용할 것인지 아니면 Core Data를 쓸 것인지 Realm을 쓸 것인지 등등의 갈림길에서 여러분만의 최선의 선택을 찾고 싶어질 것입니다. 그것은 전적으로 여러분에게 달려 있습니다.

흥미로운 사실 모든 개발자들은 “segue”라는 단어를 자신만의 방법대로 발음하고 있습니다. 몇몇은 “se-” 부분을 “say”나 “set”의 “-e-”처럼 “-에-”로 발음하고, “-gue” 부분을 “gue_rilla”처럼 “-그웨”와 같이 발음합니다. 다른 이들은 “segway”처럼 “세그웨이”로 발음하기도 합니다. (segway: 여행자들을 위한 개인형 이동수단의 일종으로서 한 쌍의 바퀴로만 되어 있고 스스로 균형을 잡는 기구)

카테고리 “Language/Objective-C & Swift”
more...
썸네일 이미지
[번역글] View Controller 사이에 데이터를 교환하는 방법(1) - property
본 게시글은 How To: Pass Data Between View Controllers in Swift를 바탕으로 작성하였습니다. 여러분의 앱이 여러 개의 사용자 인터페이스(UI)를 가지고 있다면 여러분은 하나의 UI에서 다른 UI로 데이터를 전달해야 하는 경우도 생길 것입니다. Swift에서는 뷰 컨트롤러(View Controller) 사이에 어떤 방법으로 데이터를 전달할 수 있을까요? 뷰 컨트롤러 사이에 데이터를 주고 받는 것은 iOS 개발의 중요한 일부입니다. 여러분은 몇 가지 방법으로 이를 해낼 수 있고 각기 다른 이점과 약점을 가지고 있습니다. 뷰 컨트롤러 사이에 쉽게 데이터를 교환하는 방법을 선택하는 것은 여러분이 앱 구조를 어떻게 할 것인지에 달려 있습니다. 앱의 구조(App archite..
Language/Objective-C & Swift
2021. 9. 13. 20:51

[번역글] View Controller 사이에 데이터를 교환하는 방법(1) - property

Language/Objective-C & Swift
2021. 9. 13. 20:51

본 게시글은 How To: Pass Data Between View Controllers in Swift를 바탕으로 작성하였습니다.

 

여러분의 앱이 여러 개의 사용자 인터페이스(UI)를 가지고 있다면 여러분은 하나의 UI에서 다른 UI로 데이터를 전달해야 하는 경우도 생길 것입니다. Swift에서는 뷰 컨트롤러(View Controller) 사이에 어떤 방법으로 데이터를 전달할 수 있을까요?

뷰 컨트롤러 사이에 데이터를 주고 받는 것은 iOS 개발의 중요한 일부입니다. 여러분은 몇 가지 방법으로 이를 해낼 수 있고 각기 다른 이점과 약점을 가지고 있습니다.

뷰 컨트롤러 사이에 쉽게 데이터를 교환하는 방법을 선택하는 것은 여러분이 앱 구조를 어떻게 할 것인지에 달려 있습니다. 앱의 구조(App architecture)는 여러분이 뷰 컨트롤러 사이에 어떻게 작동이 이루어 질 것인지에 영향을 주고, 반대로 여러분이 뷰 컨트롤러 사이에 데이터 교환을 어떻게 할 것인지에 따라 앱의 구조가 달라집니다.

 

Swift에서 여러분은 뷰 컨트롤러 사이에 다음의 6가지 방법으로써 데이터를 주고 받을 수 있습니다.

  1. 인스턴스 프로퍼티(property)를 사용하는 방법(A → B 방향)
  2. 스토리보드(Storyboard)와 세그웨(segue)를 사용하는 방법
  3. 인스턴스 프로퍼티와 함수를 사용하는 방법(A ← B 방향)
  4. 델리게이션(delegation) 패턴을 사용하는 방법
  5. 클로저(closure) 또는 핸들러(completion handler)를 사용하는 방법
  6. NotificationCenter 또는 Observer 패턴을 사용하는 방법

 

이 게시글에서 여러분은 뷰 컨트롤러 사이에 데이터를 주고 받는 6가지의 각기 다른 방법에 대해 익히게 될 것입니다. 이 방법에는 프로퍼티(property)를 사용하는 방법, 세그웨(segue)를 사용하는 방법 및 NSNotificationCenter를 사용하는 방법도 들어 있습니다. 비교적 간단한 이들 방법을 통해 좀 더 복잡한 응용까지 나아갈 수 있습니다. 준비가 되었다면 이제 시작하겠습니다.

 

프로퍼티를 사용하여 뷰 컨트롤러 사이에 데이터를 주고 받기(A → B 방향)

이미 언급했듯이 일부 접근법들은 데이터가 한 뷰 컨트롤러에서 다른 뷰 컨트롤러로 전달되는 단방향성을 갖습니다. 말하자면 그런 일부의 방법은 대칭(bilateral)적이지 않다는 것입니다. 하지만 염려할 것은 없습니다. 대부분의 경우 여러분은 단방향 통신을 필요로 할 것이기 때문입니다.

뷰 컨트롤러 A(현재의 화면)에서 뷰 컨트롤러 B(다음으로 넘어갈 화면)로 데이터를 넘기는 가장 쉬운 방법은 프로퍼티(property)를 사용하는 방법입니다. 프로퍼티는 그 클래스의 일부인 변수입니다. 그러한 클래스로부터 만들어진 모든 인스턴스는 따로따로 프로퍼티를 가지고 있고 여러분은 여기에 값을 배정할 수 있습니다. 다른 클래스와 마찬가지로 UIViewController의 파생형인 뷰 컨트롤러들은 프로퍼티를 가질 수 있습니다.

프로퍼티를 사용하여 뷰 컨트롤러 사이에 데이터를 전달하기.

다음과 같이 text라는 이름을 가진 프로퍼티가 있는 뷰 컨트롤러인 MainViewController가 있습니다.

// Swift
class MainViewController: UIViewController {
    var text: String = ""
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
// Objective-C
@interface MainViewController: UIViewController {
    @property (atomic, strong) NSString * text;
}

@implementation MainViewController {
    @synthesize text
    
    -(void) viewDidLoad {
        [super viewDidLoad];
    }
}

 

여러분이 MainViewController 클래스 인스턴스를 만들 때마다, 여러분은 다음과 같이 text 프로퍼티에 값을 배정할 수 있습니다.

// Swift
let mainViewController = MainViewController()
mainViewController.text = "Lorem Ipsum"
// Objective-C
MainViewController * mainViewController = [[MainViewController alloc] init];
[mainViewController setText: @"Lorem Ipsum"];

 

참 쉽죠? 이제 이 뷰 컨트롤러가 내비게이션 컨트롤러(Navigation Controller)의 일부라고 가정하고, 이 뷰 컨트롤러에서 다음에 등장할 뷰 컨트롤러로 어떤 값을 전달할 것입니다.

먼저 여러분이 스토리보드(Storyboard)를 사용하고 있다면 이 MainViewController에다가 내비게이션 컨트롤러를 삽입하고 싶을 것입니다. 스토리보드를 사용하고 있지 않다면 여러분은 내비게이션 컨트롤러를 새로 생성한 다음 MainViewController를 그것의 root view controller로 지정할 수 있습니다. 이미 내비게이션 컨트롤러가 적용되어 있다면 완벽합니다!

그 다음 여러분은 새로운 뷰 컨트롤러를 생성하기 위하여 .xib 파일도 생성하고, 서브 클래스도 생성합니다. 이것은 [File] 메뉴에서 [New File...] 항목을 선택 후 "Cocoa Touch Class"를 선택하여 SecondaryViewController라는 이름을 적음으로써 할 수 있습니다. .xib 파일이 생성될 때 "Also create XIB file" 체크박스에 체크하는 것을 절대 잊지 말기를 바랍니다.

뷰 컨트롤러에 대한 소스 코드는 다음과 같습니다.

class SecondaryViewController: UIViewController {
    var text: String = ""
    @IBOutlet weak var textLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        textLabel.text = text
    }
}
@interface SecondaryViewController: UIViewController {
    @property (nonatomic, weak) NSString * text;
}
@implementation SecondaryViewController {
    @synthesize text
    
    -(void)viewDidLoad {
        [super viewDidLoad];
        [self textLabel.setText: [self text]];
    }
}

 

.xib 파일에서 뷰(view)에 UILabel 요소를 추가한 다음 그 요소를 textLabel에 연결합니다. 그러면 여러분은 뷰 컨트롤러에 textLabel라는 이름의 프로퍼티도 가지고 있고, 그 프로퍼티에 연결된 Label 요소도 갖게 됩니다.

위 소스 코드에서도 보다시피 viewDidLoad() 메소드에서 textLabel에 있는 text 프로퍼티는 SecondViewController에 있는 text로부터 할당된 값입니다.

다음 코드에서는 실제로 데이터를 전해주고 있습니다. MainViewController에 다음과 같은 메소드를 추가합니다.

// Swift
@IBAction func onButtonTap() {
    let secondaryViewController(nibName: "SecondaryViewController", bundle: nil)
    secondaryViewController.text = "Lorem Ipsum"
    navigationController?.pushViewController(secondaryViewController, animated: true)
}
// Objective-C
-(IBAction)onButtonTap {
    SecondaryViewController * secondaryViewController = [[SecondaryViewCollector alloc] initWithNibName: "SecondaryViewController" 
                                                                                                 bundle: nil];
    [secondaryViewController setText: @"Lorem Ipsum"]
    [[self navigationController] pushViewController: secondaryViewController
                                           animated: TRUE];
}

MainViewController의 인터페이스에서 버튼을 추가하고 위의 액션에 이를 연결시킨다면, 버튼을 클릭했을 때 코드가 실행될 것입니다. 위 코드를 통해 어떤 일이 벌어지는지를 살펴보겠습니다.

secondaryViewController라는 이름의 변수를 선언하고 여기에 SecondaryViewController 클래스형 인스턴스를 대입합니다. 이 때 여러분은 뷰 컨트롤러가 올바른 XIB 파일을 사용하고 있음을 보증하기 위하여 생성자를 호출할 때 XIB의 정확한 이름을 명시하게 됩니다.

그 다음 secondaryViewControllertext 프로퍼티에 문자열을 대입합니다. 이 때가 뷰 컨트롤러 사이에 실질적으로 데이터를 교환하는 순간입니다!

마지막으로 pushViewController(_:animated:)를 사용하여 내비게이션 스택에 새로 만들어진 뷰 컨트롤러를 푸쉬(push)합니다. 이 때 발생하는 화면 변화에는 애니메이션이 지정되어 있으므로 여러분은 새로 만들어진 뷰 컨트롤러가 단말기 화면의 우측에서 미끄러져 들어오는(slide in) 효과를 보게 될 것입니다.

프로퍼티를 사용하여 뷰 컨트롤러 A에서 뷰 컨트롤러 B로 데이터를 전달하는 방법에 대해 다시 한 번 정리해보겠습니다.

  • 제1단계. 뷰 컨트롤러 B(데이터를 받아서 나중에 나타나는 화면)에서 데이터를 받기 위한 프로퍼티를 선언합니다. 위 예제에서 그 프로퍼티는 text라는 이름을 가졌습니다.
  • 제2단계. 뷰 컨트롤러 B에서 데이터가 변경될 때 수행할 작업을 기술합니다. 위 예제에서 여러분은 text를 통해 받은 문자열을 label로 출력시켰습니다.
  • 제3단계. 뷰 컨트롤러 A(먼저 나타나 있고 데이터를 넘겨 줄 화면)에서 뷰 컨트롤러 B에 대한 새 인스턴스를 만든 다음 프로퍼티에 소정의 값을 설정합니다. 그리고 내비게이션 스택으로 푸쉬(push)합니다.

여기까지가 프로퍼티를 사용한 방법이었습니다. 이어서 스토리보드에서 세그웨(segue)를 사용하여 동일한 기능을 구현해보겠습니다.

카테고리 “Language/Objective-C & Swift”
more...
Cocoa (Touch)에서 애니메이션 구현하기
Cocoa Touch에서 애니메이션 구현하는 방법을 정리해 본다. UIView.animate를 사용하여 애니메이션을 지정하기 UIView.animate에는 몇 가지 오버로드들이 있지만 하나의 애니메이션에 대해 섬세하게 지정할 수 있는 메소드를 기준으로 다음과 같이 사용할 수 있다. // Swift UIView.animate(withDuration: /* 총 소요시간 */, delay: /* 애니메이션 시작 전 뜸 들이는 시간 */, options: /* 애니메이션이 실행되는 동안 속도 변화 및 각종 옵션 */, animations: { /* 애니메이션 끝에 가서 나타날 효과를 기술한다. */ /* 그러면 애니메이션을 통해 서서히 여기서 기술한대로 각종 요소들이 변할 것이다. */ }, completion..
Language/Objective-C & Swift
2021. 9. 13. 20:21

Cocoa (Touch)에서 애니메이션 구현하기

Language/Objective-C & Swift
2021. 9. 13. 20:21

Cocoa Touch에서 애니메이션 구현하는 방법을 정리해 본다.

 

UIView.animate를 사용하여 애니메이션을 지정하기

UIView.animate에는 몇 가지 오버로드들이 있지만 하나의 애니메이션에 대해 섬세하게 지정할 수 있는 메소드를 기준으로 다음과 같이 사용할 수 있다.

 // Swift
UIView.animate(withDuration: /* 총 소요시간 */,
                      delay: /* 애니메이션 시작 전 뜸 들이는 시간 */,
                    options: /* 애니메이션이 실행되는 동안 속도 변화 및 각종 옵션 */,
                 animations: {
                                 /* 애니메이션 끝에 가서 나타날 효과를 기술한다. */
                                 /* 그러면 애니메이션을 통해 서서히 여기서 기술한대로 각종 요소들이 변할 것이다. */
                             },
                 completion: { finished in
                                 /* 애니메이션이 끝난 뒤 수행할 작업을 기술한다. */
                                 /* 레퍼런스에 따르면 이 클로저는 Bool형 변수를 하나 받게 되는데, */
                                 /* 진짜로 애니메이션이 다 끝나서 호출되는 거라면 true가 전달된다. */
                                 /* 그냥 한 번 호출되었다면 false이다. */
                             })

 

 // Objective-C
[UIView animateWithDuration: /* 총 소요시간 */
                      delay: /* 애니메이션 시작 전 뜸 들이는 시간 */
                    options: /* 애니메이션이 실행되는 동안 속도 변화 및 각종 옵션 */
                 animations:^{
                                /* 애니메이션 끝에 가서 나타날 효과를 기술한다. */
                                /* 그러면 애니메이션을 통해 서서히 여기서 기술한대로 각종 요소들이 변할 것이다. */
                            }
                 completion:^(bool finished) {
                                /* 애니메이션이 끝난 뒤 수행할 작업을 기술한다. */
                                /* 레퍼런스에 따르면 이 클로저는 Bool형 변수를 하나 받게 되는데, */
                                /* 진짜로 애니메이션이 다 끝나서 호출되는 거라면 true가 전달된다. */
                                /* 그냥 한 번 호출되었다면 false이다. */
                            }];

 

UIViewPropertyAnimator를 사용하여 애니메이션을 지정하기

UIViewPropertyAnimator는 iOS 10 이후부터 도입된 클래스이다.

var animator: UIViewPropertyAnimator!
// animator 인스턴스를 초기화할 수 있는 메소드 안에서 다음과 같은 내용을 작성한다.
// 생성자 한 줄에서 많은 요소들을 한 번에 초기화 할 수도 있지만, 
// 몇 줄에 걸쳐서 하나씩 지정해 보기 위해 가장 간단한 생성자를 사용한다.
// duration은 생성자에서만 지정 가능하며 이후 프로퍼티에서는 읽기 전용이므로 변경 불가하다.
// curve도 생성자에서만 지정 가능하다.
// animations는 나중에 추가할 수 있으므로 생성자에서는 nil을 지정한다.
animator = UIViewPropertyAnimator(duration: /* 총 소요시간 */,
                                     curve: /* 애니메이션이 실행되는 동안 속도 변화 및 각종 옵션 */,
                                animations:nil)
animator.startAnimation(afterDelay: /* 애니메이션 시작 전 뜸 들이는 시간 */)
animator.addAnimations({
    /* 애니메이션 끝에 가서 나타날 효과를 기술한다. */
    /* 그러면 애니메이션을 통해 서서히 여기서 기술한대로 각종 요소들이 변할 것이다. */
})
animator.addCompletion({ position in
    /* 애니메이션이 끝난 뒤 수행할 작업을 기술한다. */
    /* 레퍼런스에 따르면 이 클로저는 UIViewAnimatingPosition형 변수를 하나 받게 되는데, */
    /* 이름 그대로 애니메이션의 주기에 따라 .start, .current, .end 중 하나이다. */
})

 

카테고리 “Language/Objective-C & Swift”
more...
Swift와 Kotlin, 각 언어에서 비동기 실행하기(2)
본 게시물은 Ivan Fytsyk님의 게시글 Asynchronous execution in Kotlin and Swift: Concurrency problems and how to solve them을 바탕으로 작성되었습니다. Swift와 Kotlin, 각 언어에서 비동기 실행하기 본 게시글에서는 모바일 앱 개발의 양대 언어인 Swift와 Kotlin에서 비동기 실행을 하는 방법에 대해 정리합니다. 이전 글에서 우리는 비동기 실행의 기본적인 예를 살펴보았습니다. 여러분은 아마도 변경 가능한 공유 상태(shared mutable state)와 교착상태(dead-lock)라는 두 개의 일반적인 문제에 대해 이미 알고 있을 것입니다. 이러한 문제는 양대 언어에서도 공통된 문제이기에 서로 비슷한 해결책을 가지고..
Language/Objective-C & Swift
2021. 9. 11. 22:09

Swift와 Kotlin, 각 언어에서 비동기 실행하기(2)

Language/Objective-C & Swift
2021. 9. 11. 22:09

본 게시물은 Ivan Fytsyk님의 게시글 Asynchronous execution in Kotlin and Swift: Concurrency problems and how to solve them을 바탕으로 작성되었습니다.

 

Swift와 Kotlin, 각 언어에서 비동기 실행하기

본 게시글에서는 모바일 앱 개발의 양대 언어인 Swift와 Kotlin에서 비동기 실행을 하는 방법에 대해 정리합니다.

 

이전 글에서 우리는 비동기 실행의 기본적인 예를 살펴보았습니다. 여러분은 아마도 변경 가능한 공유 상태(shared mutable state)와 교착상태(dead-lock)라는 두 개의 일반적인 문제에 대해 이미 알고 있을 것입니다. 이러한 문제는 양대 언어에서도 공통된 문제이기에 서로 비슷한 해결책을 가지고 있습니다. 지금부터 살펴보겠습니다.

 

Shared Mutable State

Shared mutable state부터 시작해 보겠습니다. 아래 예에서 우리는 어떤 정수 값을 비동기식으로 1,000회 증가시켜보겠습니다.

import kotlinx.coroutines.*
import org.junit.Test

class ConcurrencyProblemTest {
    private var someValue = 0
    
    fun runCodeWithConcurrencyProblem() = runBlocking {
        for (i in 0 .. 999) {
            GlobalScope.launch() {
                someValue = someValue + 1
            }
        }
        delay(2000)
        print(someValue)
    }
}

 

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

var someValue = 0

for _ in 0 ... 999 {
    DispatchQueue.global().async {
        someValue = someValue + 1
    }
}

sleep(2)
print(someValue)

 

예상했다시피 이 값은 1,000이 되지 못하고 때때로 달라집니다. 이러한 Shared Mutable State의 해결책은 간단합니다. 변경 가능한 변수의 읽기와 쓰기를 동기화시키면 됩니다. 즉 읽기와 쓰기 작업은 주로 실행되는 쓰레드에서 직렬로(serially) 실행되어야 합니다.

import kotlinx.coroutines.*
import org.junit.Test

class ReadingWritingSerialTest {
    private var someValue = 0
    val singleThreadContext = newSingleThreadContext("mySerialContext")
    
    private fun incrementValue() {
        GlobalScope.launch(singleThreadContext) {
            someValue = someValue + 1
        }
    }
    
    fun runCodeWithConcurrencyProblem() = runBlocking {
        for (i in 0 .. 999) {
            GlobalScope.launch() {
                incrementValue()
            }
        }
        delay(2000)
        print(someValue) // result is always 1,000
    }
}

 

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

var someValue = 0
let serialQueue = DispatchQueue(label: "mySerialQueue")

func incrementValue() {
    serialQueue.async {
        someValue = someValue + 1
    }
}

for _ in 0 ... 999 {
    DispatchQueue.global.sync {
        incrementValue()
    }
}

sleep(2)
print(someValue) // always prints 1,000

 

여기서 여러분은 우리가 작성한 비동기 코드를 동기 코드로 변환하였고, 이로 인해 멀티쓰레드 실행의 이점을 얻을 수 없게 됨을 알았을 것입니다. 맞습니다. 왜냐하면 mutable 변수의 값을 변경하는 것 외에 아무것도 하지 않았기 때문입니다. 하지만 실전 코드에서는, 백그라운드에서 실행될 필요가 있는 연산 및 그 결과는 동기 방식으로 보관하는 연산으로 이루어진 부분들이 많을 수 있습니다.

 

Dead-lock

두 번째 문제는 교착 상태(dead-lock)입니다. 일반적으로 어떤 코드 블록이 내부의 코드 블록에 기술된 내용이 종료될 때까지 기다리나, 내부의 코드 블록 또한 상위의 코드가 종료될 때까지 기다리고 있을 때 쿄착 상태는 발생합니다.

import kotlinx.coroutines.*
import org.junit.Test

class DeadLockTest {
    val singleThreadContext = newSingleThreadContext("mySerialContext")
    
    @Test
    fun runCodeWithConcurrencyProblem() = runBlocking {
        runDeadLock()
        delay(2000)
    }
    
    fun runDeadLock() {
        GlobalScope.launch(singleThreadContext) {
            println("Blocking coroutine start")
            runBlocking(singleThreadContext) {
                // 바깥 블록은 안쪽 블록이 완료될 때까지 기다리겠으나,
                // 안쪽 블록도 마찬가지로 바깥 블록이 완료될 때까지 기다립니다.
                println("Unreachable statement")
            }
            println("Unreachable statement")
        }
    }
}

 

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let serialQueue = DispatchQueue(label: "mySerialQueue")

serialQueue.sync {
    print("Blocking block starts")
    serialQueue.sync {
        print("Unreavhable statement")
    }
}

 

이들 예제에서는 하나의 싱글 쓰레드 큐와 두 블록의 코드가 사용되었습니다. 첫 번째 블록에서 우리는 동기식으로 두 번째 블록을 시작하였고 새로운 블록의 실행이 끝날 때까지 첫 번째 블록은 일시중지됩니다. 하지만 두 번째 블록도 실행되지 못할 것입니다. 왜냐하면 두 번째 블록은 작업 큐의 맨 마지막에 추가되었기 때문에 큐에서 앞쪽 순서에 놓인 블록이 종료될 때까지는 두 번째 블록도 큐에서 순서가 옮겨지지 못하기 때문입니다. 그러한 종류의 교착 상태를 피하기 위한 필자의 조언은 다음과 같습니다.

중첩된 비동기 코드 블록을 실행할 때 동일한 큐를 사용하지 마세요.

import kotlinx.coroutines.*
import org.junit.Test

class DeadLockResolutionTest {
    val singleThreadContext1 = newSingleThreadContext("mySerialContext1")
    val singleThreadContext2 = newSingleThreadContext("mySerialContext2")
    
    @Test
    fun runCodeWithConcurrencyProblem() = runBlocking {
        runDeadlock()
        delay(2000)
    }
    
    fun runDeadLock() {
        GlobalScope.launch(singleThreadContext1) {
            println("Blocking coroutine start")
            runBlocking(singleThreadContext2) {
                // 바깥 블록은 별도의 쓰레드에서 실행되고 있기에,
                // 다음 문장은 접근이 가능합니다.
                println("Unreachable statement")
            }
            println("Unreachable statement")
        }
    }
}

 

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let serialQueue1 = DispatchQueue(label: "mySerialQueue1")
let serialQueue2 = DispatchQueue(label: "mySerialQueue2")

serialQueue1.sync {
    print("Blocking block starts")
    serialQueue2.sync {
        print("Unreachable statement") // 이미 접근 가능하게 되었습니다.
    }
    print("Unreachable statement")
}

 

여기까지가 필자가 접해보았던 일반적으로 흔한 동시성 문제였습니다. 여러분도 보시다시피 동시성 문제와 그에 대한 해법은 양대 언어에서 매우 비슷하게 보입니다. 읽어주셔서 감사합니다.

카테고리 “Language/Objective-C & Swift”
more...
Swift와 Kotlin, 각 언어에서 비동기 실행하기(1)
본 게시물은 Ivan Fytsyk님의 게시글 Asynchronous execution in Kotlin and Swift을 바탕으로 작성되었습니다. Swift와 Kotlin, 각 언어에서 비동기 실행하기 본 게시글에서는 모바일 앱 개발의 양대 언어인 Swift와 Kotlin에서 비동기 실행을 하는 방법에 대해 정리합니다. 이번 시간에 우리는 Kotlin과 Swift에서 동시성 모델(concurrency model)을 비교하며 설명해 볼 것입니다. 이 지식은 Android 및 iOS 프레임워크를 모두 배워보고자 원하는 분들에게도 유익할 것이고, 이미 양대 플랫폼에서 개발을 해 본 분들에게도 유용할 것입니다. 더 많은 정보는 필자의 저서를 통해 확인할 수 있습니다. 기본 용어들 우리가 다루어보고자 하는 기본..
Language/Objective-C & Swift
2021. 9. 11. 21:11

Swift와 Kotlin, 각 언어에서 비동기 실행하기(1)

Language/Objective-C & Swift
2021. 9. 11. 21:11

본 게시물은 Ivan Fytsyk님의 게시글 Asynchronous execution in Kotlin and Swift을 바탕으로 작성되었습니다.

 

Swift와 Kotlin, 각 언어에서 비동기 실행하기

본 게시글에서는 모바일 앱 개발의 양대 언어인 Swift와 Kotlin에서 비동기 실행을 하는 방법에 대해 정리합니다.

 

이번 시간에 우리는 Kotlin과 Swift에서 동시성 모델(concurrency model)을 비교하며 설명해 볼 것입니다. 이 지식은 Android 및 iOS 프레임워크를 모두 배워보고자 원하는 분들에게도 유익할 것이고, 이미 양대 플랫폼에서 개발을 해 본 분들에게도 유용할 것입니다. 더 많은 정보는 필자의 저서를 통해 확인할 수 있습니다.

 

기본 용어들

우리가 다루어보고자 하는 기본 용어들에는 쓰레드(Thread), 오퍼레이션(Operation) 및 큐(Queue)가 있습니다.

 

쓰레드(Thread)

쓰레드(Thread)는 동시에 어떤 연산을 실행할 목적으로 멀티코어 CPU를 사용하는 저수준의 추상화(abstraction)입니다. 싱글코어 CPU에서는 각 연산들 사이를 빠르게 전환함으로써 병렬 실행을 에뮬레이트(emulate)할 수 있습니다. 오늘날 Kotlin이나 Swift에서는 쓰레드가 직접적으로 사용되는 경우가 흔치 않기 때문에 이 글에서 우리는 쓰레드에 대해서는 다루지 않겠습니다. 새로운 쓰레드를 생성하고 이들을 관리하는 것은 많은 시스템 자원을 요구하기 때문에, 현대적인 개발 환경에서 우리는 일반적으로 미리 정의된 몇몇의 쓰레드 풀(Thread Pool)만을 사용하고 (이를 위해) 오퍼레이션(Operation)과 큐(Queue)를 가지고 작업하게 될 것입니다.

 

오퍼레이션(Operation)

오퍼레이션(Operation)은 단순히 한 블록의 코드입니다. 일반적으로 클로저(closure)라든가 다른 객체지향(OOP)적 추상화로써 표현됩니다(예를 들어 Swift에서는 DispatchItem으로 불리고 Kotlin에서는 Task로 불립니다.

 

큐(Queue)

큐(Queue)는 오퍼레이션(Operation)들의 선입선출형 콜렉션(collection)입니다. (이 콜렉션에 담긴) 각각의 오퍼레이션들은 순서대로 선택되어 나온 뒤 각기 다른 쓰레드에서 실행될 수 있습니다.

 

큐에 새로운 오퍼레이션을 추가하는 것은 크게 두 가지 방법으로써 가능합니다. 동기식(sync)과 비동기식(async)이 그것입니다. 동기식은 새로 시작된 오퍼레이션이 끝날 때까지 현재의 오퍼레이션(큐에 오퍼레이션을 넣고자 하는 지금의 작업 흐름)이 일시중지될 것임을 의미합니다. 비동기식은 새로운 오퍼레이션이 시작되더라도 현재의 오퍼레이션은 일시중지되지 않고 나머지 작업을 계속 실행될 것임을 의미합니다.

 

예제 1

설명은 여기까지만 하고, 지금부터는 코드들을 많게 보면서 Kotlin과 Swift에서 위의 개념들이 어떻게 구현되어 있는지를 확인해 보겠습니다. 예제 코드를 실행해보기 위해 Android Studio Kotlin REPL과 Xcode Playground를 사용할 것입니다.

먼저 Kotlin의 코드입니다.

import kotlinx.coroutines.experimental.*

runBlocking {
    GlobalScope.async { // Kotlin sample
        delay(1000L) // 1 second suspending
        println("async call")
    }
    delay(2000L)
}

 

다음은 Swift의 코드입니다.

DispatchQueue.global().async { // Swift sample
    sleep(1) // 1 second suspending
    print("async call")
}

 

여기서 어떤 일들이 벌어지는지 설명해보겠습니다. 먼저 Kotlin 측 코드를 보면 runBlocking { ... }이라는 부분이 보입니다. 이것은 블로킹 함수(blocking function)으로서 REPL의 메인 쓰레드(main threa)가 종료되기 전에 비동기 호출의 결과를 우리가 볼 수 있도록 해 줍니다. 이것은 단지 우리가 코드의 실행 결과를 보는 데 도움을 주기 위해 도입된 구조일 뿐입니다. Xcode Playground는 이와 유사한 구조가 없어도 작동이 됩니다.

 

예제 2

그 다음 우리가 관심을 두어야 할 것은 GlobalScopeDispatchQueue.global()입니다. 이들은 "미리 정의되어 있고 현재 작동 중인 쓰레드" 및 필자가 앞서 언급했던 오퍼레이션 큐(operation queue)를 캡슐화하고 있습니다. 대부분의 경우 이것으로 우리가 소스 코드들을 비동기식으로 실행하는 데 충분합니다. 보다 특수한 목적을 위해 우리는 직접 사용자 정의 큐를 만들 수도 있습니다.

백그라운드에서 실행이 오래 걸리는 작업들이 완료되고 난 뒤, 보통 우리는 UI 쓰레드에게 이 실행 결과를 전달하기도 합니다. Kotlin에서는 이것이 어떻게 이루어지는지 보겠습니다.

import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.android.Main

val job = GlobalScope.async {
    delay(1000L) // 1 second suspending
    println("async call")
    GlobalScope.async(context = Dispatchers.Unconfined) { 
        // 실제 Android 어플리케이션에서는 Dispatchers.Main으로 대체합니다.
        println("called in UI thread")
    }
}

runBlocking {
    job.join()
}

 

Swift에서는 백그라운드 작업 실행 완료를 UI 쓰레드에게 어떻게 통지하는지 보겠습니다.

DispatchQueue.global().async {
    sleep(1) // 1 second suspending
    print("async call")
    DispatchQueue.main.async {
        print("call in UI thread")
    }
}

 

여기서 우리는 비동기 블록 실행이 완료되는 것을 기다리기 위하여 job.join()을 사용하여 UI 쓰레드를 대기시켰습니다. 앞서 적었던 예제에서 delay(2000L)을 호출했던 것을 대체한 것에 지나지 않습니다.

비동기 코드 내에서도 다시 일부 코드 블록을 UI 쓰레드에서 실행하기 위하여 우리는,
Kotlin에서는 GlobalScope.async(context = Dispatchers.Unconfined) { ... }이라는 미리 정의된 메인 큐(main queue)가 동반된 생성자를 사용하였고,
Swift에서는 DispatchQueue.main.async { ... }이라는 호출을 사용하였습니다.

여기까지가 모바일 개발에서 사용되는 동시성 구현의 가장 쉬운 예였습니다. 이런 구조라면 백그라운드에서는 무거운 연산이나 데이터 처리를 수행하고 그 결과만 UI 쓰레드에 전달하면 되겠지요. 다음 장에서 우리는 오퍼레이션과 큐를 가지고 할 수 있는 작업들을 해 보고, 우리가 직접 큐를 만들어 보기도 할 것이며 교착상태(deadlock)나 경쟁(race)과 같은 동시성 문제를 어떻게 해결할 것인지에 대해 살펴보겠습니다.

카테고리 “Language/Objective-C & Swift”
more...
@property의 특성: nonatomic, retain, strong, weak 등
본 게시물은 Saurav Satpathy님의 게시글 Attributes of @property : nonatomic, retain, strong, weak etc..을 바탕으로 작성되었습니다. @property의 특성: nonatomic, retain, strong, weak 등 본 게시글은 프로퍼티 특성(property attribute)에 대한 참고 자료로서 작성되었습니다. 기본적으로 @property는 다음과 같이 생겼습니다. @property (atomic, readonly, strong) NSString * name; 또는 다음과 같이 생겼습니다. @property (atomic, readwrite, assign) NSInteger age; 1. 접근 특성(access attributes) (1..
Language/Objective-C & Swift
2021. 8. 20. 11:35

@property의 특성: nonatomic, retain, strong, weak 등

Language/Objective-C & Swift
2021. 8. 20. 11:35

본 게시물은 Saurav Satpathy님의 게시글 Attributes of @property : nonatomic, retain, strong, weak etc..을 바탕으로 작성되었습니다.

 

@property의 특성: nonatomic, retain, strong, weak 등

본 게시글은 프로퍼티 특성(property attribute)에 대한 참고 자료로서 작성되었습니다.

 

기본적으로 @property는 다음과 같이 생겼습니다.

@property (atomic, readonly, strong) NSString * name;

또는 다음과 같이 생겼습니다.

@property (atomic, readwrite, assign) NSInteger age;

 

1. 접근 특성(access attributes)

(1-1) readonly

name의 선언에서 보이는 바와 같이 그 프로퍼티로 어떤 값이 대입(assign)될 수 없습니다. readonly 특성이 적용된 프로퍼티 내부에는 세터(setter) 메소드가 생략되기 때문입니다.

 

(1-2) readwrite

그 프로퍼티가 갖는 값을 다른 곳으로 가져갈 수 있을 뿐만 아니라, 프로퍼티가 갖는 값이 다른 값으로 대체될 수도 있습니다.

 

2. 쓰레드 특성(threading attributes)

(2-1) atomic

어떤 프로퍼티를 atomic으로 선언하는 것은, 컴파일러로 하여금 그 객체에 대해 여러 쓰레드(thread)에서 동시 접근하는 것을 방지하는 부가적은 코드를 생성하도록 지시합니다.
이 부가적인 코드에서는 세마포어(semaphore)를 잠그고(lock), 그 프로퍼티에 대해 값을 가져오거나 설정한 다음, 다시 세마포어의 잠금을 해제(unlock)합니다. 세마포어를 잠그거나 잠금 해제하는 것은 비용이 들지만(물론 보통은 이 정도의 비용은 경미한 편이지만), 그 프로퍼티의 값을 가져오거나 설정하는 과정이 온전하게 수행됨을 보장합니다.

 

(2-2) nonatomic

프로퍼티 내부의 접근 메소드가 그 값을 단순히 반환하거나 수정하도록 지시합니다. 서로 다른 쓰레드로부터 동시에 이 값에 접근해 올 경우 어떤 일이 벌어질 지에 대해서는 보장이 되지 않습니다. 이런 이유로 인해 atomic으로 선언된 프로퍼티보다 nonatomic으로 선언되는 프로퍼티가 (안전성은 낮지만) 접근 속도가 빠릅니다.

 

3. 메모리 관리 특성(memory management attributes) 및 수명 한정자(lifetime qualifiers)

많은 언어들은 가비지 콜렉터(garbage collector)를 통해 메모리 관리 기능을 지원하지만, Objective-C에서는 객체 소유(object ownership)레퍼런스 카운트(reference counting)이라고 불리는 보다 효율적인 대체 수단을 사용합니다.

 

(3-1) strong과 retain

어떤 프로퍼티를 strong으로 선언한다는 것은 그 프로퍼티가 참조하고자 하는 객체를 “소유(own)”한다는 뜻입니다. 이렇게 선언된 프로퍼티에 여러분이 어떤 데이터를 대입시키면, 그 프로퍼티가 strong 참조로써 다른 데이터를 참조하게 되지 않는 한, 그 데이터는 파괴되지 않을 것입니다.

Objective-C에서 한 객체는 다른 객체들로부터 적어도 한 번은 참조되고 있다면 그 객체는 계속하여 유효한 상태로 남아 있습니다.

non-ARC 코드에서 strong은 단지 retain과 같은 말일 뿐입니다.

어떤 변수가 있을 때 그 변수가 스코프(scope)에서 유효할 동안 또는 그 변수가 다른 객체나 nil을 참조할 때까지 그 변수는 가리키고 있는 객체와 strong 참조를 유지하고 있습니다.

// Example
@property (nonatomic, strong) NSArray * listOfStudents;

 

(3-2) weak

weak은 “누군가가 그 객체를 strong 참조를 하는 한 그 객체를 메모리에 남아있게 하라”는 의미를 갖고 있으며 여러분이 그 객체의 수명에 관여하지 않을 때 지정합니다. 여러분이 weak 참조로 어떤 객체를 참조하면, 그 객체는 다른 객체에 의해 strong 참조되고 있는 한 weak 참조하고 있는 이 쪽에서도 계속 유효하지만, 어떤 객체도 참조하지 않게 될 때 weak 참조로 참조하던 객체는 파괴(destroy)됩니다.

델리게이트 형식의 프로퍼티(delegate property)는 순환적으로 retain되는 것을 방지하기 위해 weak 참조가 됩니다.

IBOutlet이나 서브 뷰(Subview)는 슈퍼 뷰(Superview)에 의해 strong 참조되고 있기 때문에 다른 코드에서는 weak 참조가 됩니다.

// Example
@property (nonatomic, weak) id<SampleDelegate> sDelegate;
@property (nonatomic, weak) IBOutlet UILabel * titleLabel;

 

(3-3) assign

새 값으로서 프로퍼티에 대입됩니다. 이 특성은 대개 int, float, NSInteger 등의 프리미티브 타입(primitive type)의 프로퍼티에 적용됩니다.

 

(3-4) copy

객체가 수정 가능(mutable)할 때 이 특성이 적용됩니다. 프로퍼티에 참조될 객체가 지금 이 순간의 상태로 참조되길 원하며 이후에 다른 요소에 의해 변경되는 것을 여기에서 반영하고 싶지 않을 때 이 특성을 사용하면 됩니다. 다만 이 프로퍼티에 참조되는 객체는 원본에 대한 복사본으로서 현재의 프로퍼티에 의해 retain된 상태이기 때문에, 사용이 끝났다면 여러분이 직접 해제(release)해야 합니다.

이 특성이 적용된 프로퍼티로 객체가 참조될 때 원본과는 다른 메모리 위치에 복사가 이뤄지며 그 복사본의 참조 횟수(retain count)가 증가된 후 해당 프로퍼티에 참조됩니다. 이 참조 횟수는 여러분이 그 객체를 더 이상 소유하고 있지 않을 때 자동으로 감소되겠으나, non-ARC 환경이라면 여러분이 직접 명시적으로 해제해 주어야 합니다.

 

참고 여러분이 mutable 프로퍼티에 대해 copy 특성을 사용하여 선언하였다면, 여러분이 이를 mutable이라 선언했어도 여전히 immutable입니다. 여러분이 여기에 어떤 값이나 객체를 가감하려 한다면 예외가 throw될 것입니다.

 

(3-5) unsafe_unretained

이 특성은 Cocoa 및 Cocoa Touch의 클래스 중 weak 참조를 지원하지 않는 극히 일부 클래스에 대해 사용됩니다. 여러분이 그러한 클래스 형식의 객체에 대해 정보를 얻고자 할 때 weak 참조의 로컬 변수나 weak 참조의 프로퍼티를 사용할 수 없음을 의미합니다. 이 특성은 참조된 객체를 계속해서 유효한 상태로 유지하는 능력이 없으면서 그 객체에 대한 strong 참조가 없어져도 이 특성을 갖는 프로퍼티는 nil 참조로 바뀌지 않는다는 뜻입니다. 때문에 이 특성을 가진 프로퍼티가 가리키는 객체가 할당 해제(deallocated)되었다면, 이 프로퍼티가 갖는 포인터는 오류입니다.

 

4. getter=

직접 작성한 메소드를 프로퍼티에 대한 getter로서 사용할 때 이 특성을 적용합니다.

 

5. setter=

직접 작성한 메소드를 프로퍼티에 대한 setter로서 사용할 때 이 특성을 적용합니다.

 

카테고리 “Language/Objective-C & Swift”
more...
[VB/VBA/VBScript] 정수에 대한 비트 연산자 및 텍스트 포매팅
정수에 대한 비트 연산자 Visual Basic, Visual Basic for Application, VBScript 등에서 사용하는 정수 연산자 및 이를 대상으로 수행되는 비트 연산자에 대해 정리해 본다. 정수형 데이터 타입 Visual Basic에서 미리 정의된 정수형 데이터 타입은 다음과 같다. Byte 부호 없는 8비트 크기를 갖는다. &H00(0x00) 이상 &HFF(0xFF) 이하의 범위를 표현한다. Integer 부호 있는 16비트 크기를 갖는다. 즉 기본 정수형이 32비트 또는 64비트인 C-family 언어에 익숙해져 있다면 VB/VBA/VBScript에서는 Integer가 16비트 크기를 갖고 있음을 항상 유념해 두어야 할 것 같다. Long 부호 있는 32비트 크기를 갖는다. Lon..
Language/Basic (Visual Basic & VB.NET)
2021. 7. 3. 12:48

[VB/VBA/VBScript] 정수에 대한 비트 연산자 및 텍스트 포매팅

Language/Basic (Visual Basic & VB.NET)
2021. 7. 3. 12:48

정수에 대한 비트 연산자

Visual Basic, Visual Basic for Application, VBScript 등에서 사용하는 정수 연산자 및 이를 대상으로 수행되는 비트 연산자에 대해 정리해 본다.

 

정수형 데이터 타입

Visual Basic에서 미리 정의된 정수형 데이터 타입은 다음과 같다.

Byte
부호 없는 8비트 크기를 갖는다. &H00(0x00) 이상 &HFF(0xFF) 이하의 범위를 표현한다.
Integer
부호 있는 16비트 크기를 갖는다. 즉 기본 정수형이 32비트 또는 64비트인 C-family 언어에 익숙해져 있다면 VB/VBA/VBScript에서는 Integer가 16비트 크기를 갖고 있음을 항상 유념해 두어야 할 것 같다.
Long
부호 있는 32비트 크기를 갖는다.
LongLong
부호 있는 64비트 크기를 갖는다.
LongPtr
시스템 의존형 정수 데이터 타입이다. 32비트로 구동되는 환경에서는 Long과 같고, 64비트로 구동되는 환경에서는 LongLong과 같다. MSDN(https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/data-type-summary)에 따르면 이 자체는 독립된 자료형이 아니라 일종의 매크로인듯 하다.

 

비트 연산자

기본적으로 부정(NOT), 비트 곱(AND), 비트 합(OR) 및 배타적 OR(XOR)이 있는데 이건 단어 그대로가 연산자이다.


Dim x As Integer, y As Integer, z As Integer
' 이런 저런 코드들을 생략하고 ...
z = Not z   ' z의 각 비트 반전을 z에 덮어써서 보관한다.
z = x And y ' x와 y의 비트 곱 결과를 z에 보관한다.
z = x Or y  ' x와 y의 비트 합 결과를 z에 보관한다.
z = x Xor y ' x와 y의 배타적 OR 결과를 z에 보관한다.

 

이어서 살펴볼 것은 시프트(Shift) 연산이다. 당황스럽게도 VB 계열의 언어에는 시프트 연산이 없다. 그러나 우회적으로 이와 비슷하게는 작성이 가능하다.

먼저 오버플로우 관련해서 발생할 수 있는 귀찮은 처리들을 방지하고자 현 데이터 타입보다 한 단계 더 큰 정수로 컨버팅을 시전한다.


Dim x As Integer
Dim t As Long
' 이런 저런 코드들을 생략하고 ...
t = CLng(x) ' Integer형 정수를 Long형 정수로 확장한다.

참고로 자매품인 CByte(아무 값이나 Byte로 형변환), CInt(아무 값이나 Integer로 형변환), CLngLng(아무 값이나 LongLong으로 형변환하는데 64비트 프로그램에서만 지원함)도 있으니 적절히 사용하면 될 것이다.

왼쪽으로 이동하는 시프트, 흔히 C-family에서는 <<와 같은 모양으로 표현되는 연산자는 사실 2를 곱하는 것과 동일하다. 그래서 저렇게 한 단계 큰 타입으로 변환된 정수를 2로 곱하면 왼쪽 시프트를 한 번 수행한 것과 같게 된다. 반대로 오른쪽으로 이동하는 시프트, 흔히 C-family에서 >>와 같은 모양으로 표현되는 연산자는 2로 나누는 것과 동일하다. 따라서 2로 나뉘주면 오른쪽 시프트를 한 번 수행한 것과 같게 된다.


Dim x As Integer
Dim t As Long
' 이런 저런 코드들을 생략하고 ...
t = CLng(x) ' Integer형 값을 Long형 값으로 확장하고
t = t * 2   ' t에 대해 왼쪽으로 한 번 시프트
t = t / 2   ' t에 대해 오른쪽으로 한 번 시프트

n번 시프트 하는 것은 2를 n번 곱하는 것과 같다. 이 때는 power 연산자(^)를 쓰면 편리하다.


t = t * (2 ^ 3) ' t를 왼쪽으로 세 번 시프트한다 == t를 왼쪽으로 2의 3제곱만큼 곱한다.
t = t / (2 ^ 3) ' t를 오른쪽으로 세 번 시프트한다 == t를 오른쪽으로 2의 3제곱만큼 나눈다.

그 다음에 원래의 타입 크기에 맞추기 위해 자투리 비트들을 다듬은 다음...


t = t And &HFFFF ' Integer형은 16비트 크기를 갖는다고 했으므로 하위 16비트만 남기고 전부 버림

원래의 변수에 넣어주면 끝이다.


x = t

자바 같은 경우 좌측/우측 로테이션 연산이 있으나 이 역시 VB에는 없다. 물론 만들 수는 있으나 여백이 부족하여 자세한 설명은 생략한다.

 

텍스트 포매팅

C에서 scanf 같은 함수들을 사용하면 텍스트로 적힌 16진수, 10진수를 정수로 파싱이 가능하다. 마찬가지로 Classic VB에도 위에서 적은 CByte 따위의 컨버팅 함수를 쓰면 텍스트에서 정수로 파싱이 가능하다. 문제는 그 반대인 정수에서 텍스트로 서식 적용해서 출력하는 것이다. Classic VB에는 C의 printf 같은 기능이 없다. 따라서 텍스트 함수로써 약간의 편법을 써야 한다.

예를 들어 &H13A(0x13A)라는 어떤 16진수를 32비트 형식에 맞추어 0000013A와 같이 표현하고자 한다. 우선 Hex 함수는 자릿수 맞춤 없이 그냥 16진수로 출력한다.


Dim x as Integer
Dim o as String
x = &H13A
o = Hex(x)

이렇게 하면 o라는 변수에는 13A라는 텍스트가 보관된다. 16진수임을 식별하는 어떤 접두어나 접미어도 없고 자릿수 맞춤도 없다. 이번에는 이렇게 해 보겠다.


o = "00000000" & Hex(x)

Hex의 반환형은 문자열이니까 그 앞에 "00000000"을 붙인다. 그러면 o의 결과는 "0000000013A"처럼 된다. 32비트는 이 문자열에서 오른쪽부터 8문자만을 취하면 된다. 나머지는 어차피 영(0)이므로 버려도 무방하다. 이 때 사용되는 함수가 문자열 함수인 Right이다.


o = "00000000" & Hex(x)
o = Right(o, 8) ' 오른쪽부터 8문자만 취한다.

한 줄로 표현하면,


o = Right("00000000" & Hex(x), 8)

이렇게 해서 정수를 16진수로 출력하되 32비트 자릿수에 맞추어 보기 좋게 출력할 수 있다.

필요하다면 여기에 적절한 접두어나 접미어를 적어주면 완벽하다.


o = "0x" & Right("00000000" & Hex(x), 8) ' C 스타일 0x0000013A
o = "&H" & Right("00000000" & Hex(x), 8) ' VB 스타일 &H0000013A

 

상수 적을 때 Integer 범위를 넘어서는 값을 적으면 오버플로우 오류가 난다.


Dim x As Long
x = &HFFFFFF ' 오류

이 때는 값 끝에 & 기호를 붙여서 Long형 임을 명시해주면 해결된다.


x = &HFFFFFF& ' Long형 값으로서 0x00FFFFFF
x = 50000& ' Long형 값으로서 50000
[PowerShell] OpenFileDialog / SaveFileDialog 사용하기
PowerShell 본 시리즈에서는 PowerShell의 구체적인 사용에 대해 정리한다. 이전 게시글: [PowerShell] C#의 using 키워드 구현하기 OpenFileDialog / SaveFileDialog 사용하기 PowerShell에서는 .NET Framework의 WinForm 구성 요소인 System.Windows.Forms.OpenFileDialog과 System.Windows.Forms.SaveFileDialog를 가져와서 연동할 수 있다. OpenFileDialog를 사용하여 파일 열기 대화상자를 띄우는 코드는 다음과 같다. SaveFileDialog도 이와 다르지 않다. [System.Reflection.Assembly]::LoadWithPartialName("System.Win..
Language/PowerShell
2020. 9. 15. 13:37

[PowerShell] OpenFileDialog / SaveFileDialog 사용하기

Language/PowerShell
2020. 9. 15. 13:37

PowerShell


본 시리즈에서는 PowerShell의 구체적인 사용에 대해 정리한다.

이전 게시글: [PowerShell] C#의 using 키워드 구현하기

 

OpenFileDialog / SaveFileDialog 사용하기


PowerShell에서는 .NET Framework의 WinForm 구성 요소인 System.Windows.Forms.OpenFileDialogSystem.Windows.Forms.SaveFileDialog를 가져와서 연동할 수 있다.

OpenFileDialog를 사용하여 파일 열기 대화상자를 띄우는 코드는 다음과 같다. SaveFileDialog도 이와 다르지 않다.

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$openFileDialog.InitialDirectory = [System.String]::Empty
$openFileDialog.Filter = "All Files (*.*)|*.*"
$openFileDialog.ShowDialog()

 

위 코드를 보다 자세히 분해하여 보겠다.

 

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

이것은 컴퓨터에 설치된 .NET Framework 어셈블리 중에서 System.Windows.Forms라는 이름의 어셈블리를 찾아서 로드하라는 뜻이다. 자세한 내용은 MSDN을 참고하며, 성공하면 현재의 프로그램에 어셈블리가 로드되어 이를 사용할 수 있고 해당 어셈블리에 대해 System.Reflection.Assembly형 객체를 반환한다. 반환값에 대해서 여기에서는 사용하지 않고 그냥 버린다. 만일 실패할 경우 null이 반환될 것이다.

 

$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog

C# 방식으로는 var openFileDialog = new System.Windows.Forms.OpenFileDialog() 정도로 이해할 수 있다. 파일 열기 대화상자 객체를 새로 만든다.

 

$openFileDialog.InitialDirectory = [System.String]::Empty
$openFileDialog.Filter = "All Files (*.*)|*.*"
$openFileDialog.ShowDialog()

나머지는 OpenFileDialog의 reference에 따라 구성된 내용이다. 특히 InitialDirectory 프로퍼티는 대화상자가 맨 처음 열릴 때 보여질 폴더의 경로를 지정하는데, 기본값은 빈 문자열이다. 특정 경로를 보일 것이라면, 그 경로명을 지정하면 된다.

카테고리 “Language/PowerShell”
more...
[PowerShell] C#의 using 키워드 구현하기
PowerShell 본 시리즈에서는 PowerShell의 구체적인 사용에 대해 정리한다. 다음 게시글: OpenFileDialog / SaveFileDialog 사용하기 C#의 using 키워드 구현하기 C#은 System.IDisposable 인터페이스를 구현하는 객체에 대해 using 구문을 사용하여 블록을 벗어날 때 자동으로 관리되지 않는 리소스들을 해제할 수 있다. 예를 들어, using (FileStream fileStream = new FileStream()) { // Do Something } 와 같은 구문을 통해 파일 스트림 작업이 끝나고 블록을 벗어나면 자동으로 fileStream 내부의 리소스들이 해제될 수 있다. .NET Framework의 구성 요소들을 사용할 수 있는 PowerSh..
Language/PowerShell
2020. 9. 15. 12:11

[PowerShell] C#의 using 키워드 구현하기

Language/PowerShell
2020. 9. 15. 12:11

PowerShell


본 시리즈에서는 PowerShell의 구체적인 사용에 대해 정리한다.

다음 게시글: OpenFileDialog / SaveFileDialog 사용하기

 

C#의 using 키워드 구현하기


C#은 System.IDisposable 인터페이스를 구현하는 객체에 대해 using 구문을 사용하여 블록을 벗어날 때 자동으로 관리되지 않는 리소스들을 해제할 수 있다. 예를 들어,

using (FileStream fileStream = new FileStream()) {
    // Do Something
}

와 같은 구문을 통해 파일 스트림 작업이 끝나고 블록을 벗어나면 자동으로 fileStream 내부의 리소스들이 해제될 수 있다.

.NET Framework의 구성 요소들을 사용할 수 있는 PowerShell에는 기본적으로 using statement가 포함되어 있지는 않지만 다음과 같이 간단하게 함수를 하나 선언하여 이를 구현할 수는 있다

 

전체적인 코드는 다음과 같다.

function Using-Disposable {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [AllowEmptyCollection()]
        [AllowNull()]
        [System.IDisposable]
        $disposable,
        
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $scriptBlock
    )
    
    begin {
        
    } process {
        .$scriptBlock
    } end {
        if ($disposable -ne $null) {
            $disposable.Dispose()
        }
    }
}

#usage
Using-Disposable($fileStream = New-Object System.IO.FileStream("hello.txt")) {
    // Do Something
    $fileStream.Flush()
}

 

위 코드를 분해하여 이해해본다.

 

function Using-Disposable {

함수의 이름이다. Using-Disposable이외의 다른 이름을 얼마든지 지정 가능하다.

 

[CmdletBinding()]

함수에 부여할 수 있는 특성(attribute)이다. 이 특성이 부여되면 마치 PowerShell에 기본으로 내장된 명령인 cmdlet처럼 코딩하여 호출 가능하게 된다.

 

param (

이 함수가 받는 매개변수들을 선언한다.

 

[Parameter(Mandatory = $true)]
[AllowEmptyString()]
[AllowEmptyCollection()]
[AllowNull()]
[System.IDisposable]
$disposable,

첫 번째 매개변수는 using statement에 사용될 IDisposable 객체이다. 여기에 부여되는 특성은 이름에서 볼 수 있듯,

생략할 수 없는 필요 매개변수([Parameter(Mandatory = $true)])이고,

"" 또는 [System.String]::Empty 같은 빈 문자열이 전달될 수 있고([AllowEmptyString()]),

포함하고 있는 원소의 개수가 0개인 빈 콜렉션이 전달될 수 있고([AllowEmptyCollection()]),

아예 null 객체가 전달될 수도 있다([AllowNull()]).

다만, 이 매개변수의 데이터타입은 반드시 System.IDisposable이다.

$disposable은 매개변수의 이름이므로 자유롭게 수정 가능하다.

 

[Parameter(Mandatory = $true)]
[scriptblock]
$scriptBlock

Using-Disposable이 블록 { }으로 감싸게 될 많은 실행 구문들이 "매개변수"의 형태로 이 곳에 전달된다. 그러므로 이 매개변수의 데이터 타입은 scriptblock이다. C#의 방식으로는 람다식? 정도로 이해할 수 있다. 스크립트 언어이기에 가능한 기능이다.

이 매개변수는 직관적으로 보아도 당연히 생략될 수 없다([Parameter(Mandatory = $true)]]).

$scriptBlock은 매개변수의 이름이므로 자유롭게 수정 가능하다.

 

PowerShell의 함수는 다음과 같은 구조로 선언할 수 있다.

function 함수이름 {
    param(매개변수, 매개변수, ...)
    
    begin { 준비작업 }
    process { 본문 }
    end { 정리작업 }
    
    반환할 값 또는 객체
}

PowerShell 함수에서는 값을 반환할 때 return과 같은 키워드가 없고 그냥 값 그 자체를 함수 끝에 적어주면 되는 것이 인상적이다. 또한 함수를 작성할 때 준비작업과 본문 및 정리작업을 각각 블록으로 구분하여서 적을 수 있는 것도 인상적인데, C++이나 C#에는 이러한 구문이 없지만 굳이 빗대자면 예외처리를 할 때 try, finally를 사용하는 것 정도로 이해해할 수 있다. 이보다 더 좋은 비유가 있다면 댓글로 남겨주시길...

실행되는 순서는 당연하게도 begin 블록 안의 내용이 실행되고 나서 process 블록 안의 내용이 실행이 될 것이고, 마지막으로 end 블록 안의 내용이 실행될 것이다.

 

begin {
    
} process {
    .$scriptBlock
} end {
    if ($disposable -ne $null) {
        $disposable.Dispose()
    }
}

어쨌든, 이 함수는 사전 작업 할 것이 딱히 없으므로 begin 블록은 비워 두고 process 블록에서 앞서 매개변수로 전달받은 블록인 $scriptBlock을 실행한다.

스크립트 블록의 실행이 끝나면 Dispose()를 호출한다. PowerShell의 조건 연산자는 C#, C++과는 판이하게 다르며 오히려 perl과 가깝다. -ne!= 연산자와 같다. 매개변수로 받은 $disposablenull이 아니라면 Dispose 메소드를 호출한다.

 


Using-Disposable($fileStream = New-Object System.IO.FileStream("hello.txt")) {
    // Do Something
    $fileStream.Flush()
}

이제 실제로 사용해 본다.

첫 번째 매개변수였던 $disposable에는 System.IO.FileStream형 객체가 전달된다. 그리고 블록으로 감싼 코드는 두 번째 매개변수였던 $scriptBlock으로 전달된다.

$fileStream.Flush()까지 실행이 끝나고 블록을 벗어날 때, Using-Disposableend 블록에 적었던 Dispose 메소드가 비로소 호출되고 메모리가 정리된다.

카테고리 “Language/PowerShell”
more...
[단막 C++ 문법] C++ 배열 반복문 사용 방법
C++ 배열 반복문 사용 방법 C++에서 배열 반복문을 사용하는 방법은 다음과 같이 정리할 수 있다. 1. 전통적인 배열 반복문 전통적인 배열 반복문은 역시 인덱스 변수를 선언하고 그 변수를 증감하면서 배열의 원소에 순차적으로 접근하는 방식이다. /* C++ source */ #include // std::array #include // std::cout using namespace std; int main(int argc, char * argv[]) { std::array integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 }; for (size_t i = 0; i < integers.size(); i++) { cout
Language/C & C++
2019. 2. 7. 16:06

[단막 C++ 문법] C++ 배열 반복문 사용 방법

Language/C & C++
2019. 2. 7. 16:06

C++ 배열 반복문 사용 방법


C++에서 배열 반복문을 사용하는 방법은 다음과 같이 정리할 수 있다.

 

1. 전통적인 배열 반복문


전통적인 배열 반복문은 역시 인덱스 변수를 선언하고 그 변수를 증감하면서 배열의 원소에 순차적으로 접근하는 방식이다.

/* C++ source */
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    for (size_t i = 0; i < integers.size(); i++)
    {
        cout<<integers[i]<<'\t';
    }
    return 0;
}

 

2. 반복자 사용하기


STL Container 클래스내의 반복자(iterator)를 자동변수로 선언하여 배열의 원소에 순차적으로 접근할 수도 있다. 여기에서 반복자의 자료형은 std::array<int, 10>::iterator이지만 작성의 편의를 위해 auto 키워드로 대체하여 반복자를 선언하는 것이 보통이다.

/* C++ source */
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    for (std::array<int, 10>::iterator = integers.begin(); integer != integers.end(); integer++)
    {
        cout<<*integers<<'\t';
    }
    
    return 0;
}

 

3. std::for_each 사용하기


std::for_each는 앞서 작성한 반복자 선언을 좀 더 키워드화한 것으로 algorithm 헤더 파일에 다음과 같이 선언되어 있다.

/* C++ source */
template <class InputIterator, class Function>
Function for_each (InputIterator first, InputIterator last, Function fn);

 

여기서 fn 매개변수는 람다식이 될 수도 있고 함수 포인터가 될 수도 있다. 다음은 함수 포인터를 사용한 예이다.

/* C++ source */
#include <algorithm> // std::for_each
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

void myloop(int);

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    std::for_each
    (
        integers.begin(), // InputIterator first
        integers.end(), // InputIterator last
        myloop // Function fn
    );
    
    return 0;
}

void myloop(int integer)
{
    cout<<integer<<'\t';
}

 

다음은 람다식을 사용한 예이다.

/* C++ source */
#include <algorithm> // std::for_each
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    std::for_each
    (
        integers.begin(), // InputIterator first
        integers.end(), // InputIterator last
        [](int integer) { cout<<integer<<'\t'; } // Function fn
    );
    
    return 0;
}

 

4. Range based for 구문 사용하기


Range based for가 바로 파이썬, 베이직 등 타 스크립트 언어에서 지원하는 foreach 구문이다. C++에서 foreach 구문은 다음과 같은 형태를 갖는다.

/* C++ source */
#include <array>     // std::array
#include <iostream>  // std::cout

using namespace std;

int main(int argc, char * argv[])
{
    std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
    
    for (int & integer : integers)
    {
        cout<<integer<<'\t';
    }
    
    return 0;
}

 

카테고리 “Language/C & C++”
more...
[단막 C++ 문법] const 위치에 따른 구문의 의미
const 위치에 따른 구문의 의미 네이버 지식iN에 이런 질문이 올라와서 이에 답변을 달았던 적이 있다. 예제보니까 parameter에 void foo(int* const* bar) 이렇게 있더라고요;; 이게 definition으로서 가능해요? 만일 가능하다면 도대체 이렇게 정의하는 목적이 뭔지;; 그리고 만일 이게 된다면 const* int* bar도 되나요?? 답변 부탁드리겠습니다 ㅠ- [네이버 지식iN] C++ int*const* 변수이름 은 도대체 뭐죠?? 본디 상수 특성을 지정하는 const 키워드는 그 놓인 위치에 따라 약간의 미묘한 의미 차이를 갖는다. 본 포스팅에서는 const 키워드가 들어간 구문의 정확한 의미를 정리해 보고자 한다. const의 선언 위치 const는 선언 문장 내 여..
Language/C & C++
2018. 11. 1. 12:06

[단막 C++ 문법] const 위치에 따른 구문의 의미

Language/C & C++
2018. 11. 1. 12:06

const 위치에 따른 구문의 의미


네이버 지식iN에 이런 질문이 올라와서 이에 답변을 달았던 적이 있다.

예제보니까 parameter에
void foo(int* const* bar)
이렇게 있더라고요;; 이게 definition으로서 가능해요? 만일 가능하다면 도대체 이렇게 정의하는 목적이 뭔지;;
그리고 만일 이게 된다면 const* int* bar도 되나요?? 답변 부탁드리겠습니다 ㅠ

- [네이버 지식iN] C++ int*const* 변수이름 은 도대체 뭐죠??

본디 상수 특성을 지정하는 const 키워드는 그 놓인 위치에 따라 약간의 미묘한 의미 차이를 갖는다. 본 포스팅에서는 const 키워드가 들어간 구문의 정확한 의미를 정리해 보고자 한다.

const의 선언 위치


const는 선언 문장 내 여러 곳에 놓일 수 있다. 그 때문에 의미가 혼동되고 읽기 불편할 수도 있다.

먼저 변수를 선언할 때에는 다음과 같이 const가 올 수 있다.

[const] 자료형 [const] [포인터] [const] 변수명;

 

함수를 선언할 때에는 다음과 같이 const가 올 수 있다.

[const] 자료형 [const] [포인터] 함수명(매개변수…) [const] { 함수본문 }

 

const 키워드가 어느 곳에 붙든 결국 원칙은 이것이다.

const가 걸리는 위치에서는 값 변경이 안 된다.

그렇다면 값 변경이 안 되는 경우(어디서 값 변경이 안 되는가?)를 찾는 것이 관건이다. 질문 원문에서 소개 된 3가지 경우를 예로 들어보면,

  1. const int ** foo;
  2. int * const * foo;
  3. int ** const foo;

이 3가지의 자료형이 있다. const가 붙는 위치가 조금씩 다름을 알 수 있는데 이 차이를 구분할 수 있다면 C의 고수이다.

위의 3가지 형식을 괄호로 쳐 본다면 다음과 같이 묶을 수 있다.

  1. (const int) ** a;
  2. (int * const) * a;
  3. (int **) const a;

 

'const 자료형 **'의 해설


1번은 const int 형에 대한 이중 포인터이다. 즉 int형 상수에 대한 이중 포인터라는 뜻이다. 보다 쉽게 나타낸다면 이런 과정으로 만들어지는 타입이다.

{
const int a = 10;
const int * b = &x; 
const int ** c = &y;
}

 

위와 같이 &b의 자료형이 const int ** 형이라 보면 된다. 알맹이 값인 a = 10이라는 값이 상수처리 되는 것이다.

반복하면,

{
  c : const int ** 형 (값 변경 O)
 *c : const int * 형 (값 변경 O)
**c : const int 형 (값 변경 X)
}

이므로,

int main(int argc, char * argv[]) {
    const int a = 10;
    const int * b = &a;
    const int ** c = &b; // 이게 const int ** 형식임.
    
      c = 0x00000000; // 대입시 에러 없음
     *c = 0x00000000; // 대입시 에러 없음
    **c = 5; // 여기서 에러.
    
    return 0;
}

 

'자료형 * const *'의 해설


2번은 int * 형이 갖는 주소 값을 상수로 다룬다는 뜻이다. 즉 int *형 상수에 대한 포인터라는 뜻이다. 보다 쉽게 나타낸다면 이런 과정을 통해 만들어지는 타입이다.

{
int a = 10;
int * const b = &a;
int * const * c = &b;
}

 

위와 같이 &b의 자료형이 int * const * 형이라 보면 된다. 여기서 알맹이 값인 a = 10은 수정이 자유로우나, 이것이 위치한 주소(a의 주소)를 갖고 있는 b가 상수처리 되는 것이다.

반복하면,

{
  c : int * const * 형 (값 변경 O)
 *c : int * const 형 (값 변경 X)
**c : int 형 (값 변경 O)
}

이므로,

int main(int argc, char * argv[]) {
    int a = 10;
    int * const b = &a; // 여기서 &b가 100% 완벽한 int * const * 형을 갖게 된다.
    int * const * c = &b; // 이게 int * const * 형이다.
    
      c = 0x00000000; // 대입시 에러 없음
     *c = 0x00000000; // 여기서 에러.
    **c = 5; // 대입시 에러 없음
    
    return 0;
}

 

'자료형 ** const'의 해설


3번은 int ** 타입 자체가 상수라는 뜻이다, 즉 이런 과정을 통해 만들어지는 타입이다.

{
int a = 10;
int * b = &a;
int ** const c = &b;
}

 

위와 같이 &b의 자료형을 상수로 처리하는 것이 int ** const 형이라 보면 된다.

{
  c : int ** const 형 (값 변경 X)
 *a : int *  형 (값 변경 O)
**a : int 형 (값 변경 O)
}

이므로,

int main(int argc, char * argv[]) {
    int a = 10;
    int * b = &a;
    int ** const c = &b;
    
      c = 0x00000000;// 여기서 에러.
     *c = 0x00000000; // 대입시 에러 없음
    **c = 5; // 대입시 에러 없음
    
    return 0;
}

 

마무리


지식 iN에도 올렸지만, 다시 한번 표로 정리하면 아래와 같이 요약 가능하다.

선언된 형식const int ** aint * const * aint ** const a
a의 형식const int ** 형 (값 변경 O)int * const * 형 (값 변경 O)int ** const 형 (값 변경 X)
*a의 형식const int * 형 (값 변경 O)int * const 형 (값 변경 X)int * 형 (값 변경 O)
**a의 형식const int 형 (값 변경 X)int 형 (값 변경 O)int 형 (값 변경 O)

 

덧 1. 문자열 상수와 const


strcpy 같은 함수를 보면 두 번째 인수가 const char * 형으로 되어 있을 것이다. 풀이해 보면, 그 문자열의 주소는 경우에 따라 가변적이지만 문자열이 갖는 자체의 내용만큼은 변함이 없다. 이런 뜻이다.

이를 테스트해보자.

void test(const char * str) {
    str = "이건 뭥미?"; // 대입 가능
}

str의 주소가 가리키는 문자열이 매개변수를 통해 넘어온 문자열이 아니라 전혀 새로운 문자열로 대치가 가능하다. 주소 자체에는 상수성이 없기 때문이다.

 

void test(const char * const str) {
    str = "이건 뭥미?"; // 대입 불가
}

문자열의 내용 자체가 불변인 것은 위와 같다. 또한 문자열을 가리키는 주소 또한 상수성을 갖고 있으므로 전혀 다른 문자열로 대치하는 것 또한 불가하다.

 

void test(char * str) {
    str = "ABCD"; // 대입 가능
    str[0] = 'A'; // 대입 가능
}

문자열의 내용과 문자열이 가리키는 주소 모두 상수성이 없다. 자유롭게 대치 및 수정 가능하다.

 

void test(const char * str) {
    str = "ABCD"; // 대입 가능
    str[0] = 'A'; // 대입 불가
}

문자열이 가리키는 주소 자체는 상수성이 없으므로 전혀 다른 문자열로 대치가 가능하다. 그러나 문자열의 내용에 대해서는 상수성이 있으므로 const char *로 선언된 문자열은 일부 내용의 수정이 불가하다.

 

덧 2. 멤버 함수와 const


C 언어에서 사용되는 const는 이 정도에서 끝나지만 C++로 넘어올 경우 하나가 더 있다. 멤버 함수의 scope에 붙이는 const 키워드이다.

class Integer {
    private:
        int value;
    public:
        int getInteger() const;
        void setInteger(int value);
}

int Integer::getInteger() const {
    return this->value;
}

void Integer::setInteger(int value) {
    this->value = value;
}

 

위와 같이 멤버 함수의 scope에도 상수성을 부여할 수 있다. 이것의 의미는...

이 함수는 멤버 변수의 값을 읽을 수는 있어도 수정할 수는 없다.

이다. 만일,

int Integer::getInteger() const {
    this->value = -1; // 오류 발생
    return this->value;
}

위와 같은 코드를 실행한다면 const가 부여된 scope 내에서 멤버 변수의 수정을 시도하므로 오류가 발생할 것이다. 또한,

int Integer::getInteger() const {
    this->setInteger(-1); // 오류 발생
    return this->value;
}

또한 위와 같이 scope에 const 속성이 없는 다른 함수를 호출하는 것도 오류가 발생한다. 상수 함수에서 호출 가능한 멤버함수는 같은 상수 함수 뿐이다. 반대로 scope에 const가 없는 함수는 상수 함수이든 비 상수 함수이든 호출이 가능하다. 이렇게 선언하는 이유는 객체의 상수성 때문이다.

{
    Integer i1;
    const Integer i2;

    i1.setInteger(-1); // 가능
    i1.getInteger(); // 가능
    i2.setInteger(-1); // 불가
    i2.getInteger(); // 가능
}

객체 i1은 상수성이 없고, 객체 i2는 상수성이 있다. 상수성이 있는 객체는 멤버 변수의 수정이 불가능하므로 scope에 const가 명시된 멤버 함수만이 호출 가능하다. 따라서 i2.setInteger(-1);와 같은 호출은 불가능하다.

 

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 11 - strerror) [完]
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 23. 09:08

libc 문자열 조작 함수 정리 (part 11 - strerror) [完]

Language/C & C++
2018. 8. 23. 09:08

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part XI. strerror


이번 포스팅에서는 가장 최근에 발생한 오류 내용을 확인할 때 사용하는 함수인 strerror 함수에 대해 정리한다.

1. strerror


strerror 함수의 원형은 다음과 같이 정의되어 있다.

char * strerror(int errnum);
errnum
오류 번호이다. 이 값에 따라 각기 다른 오류 내용을 볼 수 있다. 가장 최근에 발생한 오류의 고유번호를 알고자 한다면 errno.h 헤더에 정의되어 있는 errno 전역 식별자를 여기에 전달하면 된다.

다음은 strerror 함수의 사용 예이다.

/* strerror.c */
#include <stdio.h>
#include <string.h>
#include <errno.h>                                         // identifier 'errno'

int main(int argc, char * argv[])
{
	FILE * fp = NULL;
	
	if ((fp = fopen("NotExists", "r")) != NULL)
	{
		printf("File Exists.\n");                         // File Exists
		fclose(fp); 
	}
	else
	{
		printf("File NOT Exists.\n");                      // File Error
		printf("\"%s\"\n", strerror(errno));
	}

	return 0;
}
[그림 1] strerror.c 예제 소스 코드
[그림 2] strerror.c 예제 소스 코드의 실행 결과

존재 하지 않는 이름("NotExists")의 파일을 fopen 함수를 통해 열려고 하였다. 당연히 fopen 함수는 NULL을 반환하지만 한편으로는 전역변수 errno에 오류 코드를 설정한다. 이 오류 코드는 정수(int)형인데 구체적으로 사용자가 읽을 수 있는 텍스트로써 어떤 내용인지를 보고자 할 때 strerror 함수를 사용하여 문자열 형태로 출력하고 있음을 확인할 수 있다.

1-1. Wide Character 확장 함수 - _wcserror


현재 버전의 표준 C 라이브러리에는 wchar 버전의 strerror 함수가 정의되어 있지 않다. Visual Studio 사용자는 UTF-16/UTF-32와 같은 Wide Character 문자열 형식에 대해 위하여 다음의 함수를 사용 가능하다.

wchar_t * _wcserror(int errnum);

<Epilogue>


본 포스팅을 통해 문자열 비교 함수에 대해 정리해 보았다. 이것으로 C 표준 라이브러리(libc)에서 제공하는 문자열 조작 함수(strXXX)에 대한 정리를 모두 마친다.

- 끝 -

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 21. 23:41

libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)

Language/C & C++
2018. 8. 21. 23:41

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part X. strxfrm, strcoll


이번 포스팅에서는 시스템 로케일 설정에 따라 문자열을 변환하고 비교할 목적으로 사용되는 함수인 strxfrmstrcoll 함수에 대해 정리한다.

1. strxfrm


strxfrm 함수는 시스템 로케일 설정에 따라 문자열을 변환transform하여 버퍼에 복사하고 변환된 문자열의 길이를 반환하는 함수이다. 여기서 변환이란 시스템 로케일에서 정의한 문자열 변환작업을 의미하는데 구체적으로 무엇을 어떻게 변환하는지에 대해서는 명확하게 정의된 것이 없다. 다만, 변환된 문자열은 일종의 해시hash로서 취급되며 strcmp 함수에 의한 일치 또는 순서가 본래의 문자열과 일치함은 보장한다.

strxfrm 함수의 원형은 다음과 같다.

size_t strxfrm(char * destination, const char * source, size_t num);
destination
변환된 문자열이 복사될 문자열 버퍼이다.
source
변환할 문자열이 보관된 상수 또는 문자열 버퍼이다.
num
destination 버퍼가 보관 가능한 최대 문자 수이다.

함수의 수행 결과 현재 시스템 로케일 조건에서 원본 문자열로부터 변환된 문자열이 destination으로 지정한 문자열 버퍼에 복사되고 이 버퍼에 복사된 문자 수가 반환된다.

다음은 strxfrm 함수의 사용 예이다. macOS(10.13.6 High Sierra 기준) 및 FreeBSD에서는 로케일 기능을 구현하는 부분에서 버그가 존재하기 때문에 아래 소스 코드에 의한 결과가 다를 수 있다. 그 결과는 신뢰할 수 없으므로 가급적 macOSFreeBSD를 제외한 운영체제에서 실행해보기를 권장한다.

/* strxfrm.c */
#include <stdio.h>
#include <string.h>
#include <locale.h>

int main(int argc, char * argv[])
{
    char str1[512] = "hlava";
    char str2[512] = "číšník";
    char xfm1[512] = { '\0', };
    char xfm2[512] = { '\0', };
    char * result = NULL;
    size_t lxfm1 = 0;
    size_t lxfm2 = 0;

    lxfm1 = strxfrm(xfm1, str1, sizeof xfm1);
    lxfm2 = strxfrm(xfm2, str2, sizeof xfm2);
    if ((lxfm1 > 0) && (lxfm2 > 0))
    {
        printf("<Locale Unset>\n");
        printf("setlocale = \"%s\"\n", (result == NULL) ? "NULL" : result);
        printf("str1: \"%s\" --> \"%s\"\n", str1, xfm1);
        printf("str2: \"%s\" --> \"%s\"\n", str2, xfm2);
        printf("strcmp(str1, str2) = %d\n", strcmp(str1, str2));
        printf("strcmp(xfm1, xfm2) = %d\n", strcmp(xfm1, xfm2));
        printf("returns of strxfrm: %zu / %zu\n", lxfm1, lxfm2);
    }

    result = setlocale(LC_ALL, "cs_CZ.UTF-8");
    lxfm1 = strxfrm(xfm1, str1, sizeof xfm1);
    lxfm2 = strxfrm(xfm2, str2, sizeof xfm2);
    if ((lxfm1 > 0) && (lxfm2 > 0))
    {
        printf("<cs-CZ.UTF-8>\n");
        printf("setlocale = \"%s\"\n", result);
        printf("str1: \"%s\" --> \"%s\"\n", str1, xfm1);
        printf("str2: \"%s\" --> \"%s\"\n", str2, xfm2);
        printf("strcmp(str1, str2) = %d\n", strcmp(str1, str2));
        printf("strcmp(xfm1, xfm2) = %d\n", strcmp(xfm1, xfm2));
        printf("returns of strxfrm: %zu / %zu\n", lxfm1, lxfm2);
    }

    return 0;
}
[그림 1] strxfrm.c 예제 소스 코드
[그림 2] strxfrm.c 예제 소스 코드의 실행 결과

위 코드는 체코어 단어인 "hlava""číšník"의 순서를 비교하는 예이다. 두 단어는 각각 str1str2에 UTF-8 인코딩으로 보관되어 있다. (단, unix 일때에 한함.) Microsoft Windows 등의 운영체제를 고려하여 확실하게 UTF-8 인코딩으로 문자열 상수를 보관하기 위해 str1str2의 상수 할당을 다음과 같이 지정해도 좋다.

/* strxfrm.c: alternate */
char str1[] = { 0x68, 0x6C, 0x61, 0x76, 0x61, 0x00 }; // hlava
char str2[] = { 0xC4, 0x8D, 0xC3, 0xAD, 0xC5, 0xA1, 0x6E, 0xC3, 0xAD, x6B, 0x00 }; // číšník

보통의 소문자 'c'의 유니코드는 U+0063이고, 카론caron이 붙은 소문자 'č'의 유니코드는 U+010D이다. 또한 보통의 소문자 'h'의 유니코드는 U+0068이다. 유니코드에 의한 단순 정렬 시,

'c' (U+0063) - 'h' (U+0068) - 'č' (U+010D)

가 되겠지만 체코어 알파벳의 순서대로 문자를 정렬할 경우,

'c' (U+0063) - 'č' (U+010D) - 'h' (U+0068)

의 순서로 정렬된다. strxfrm은 특정 언어로 적힌 문자열을 strcmp로 순서 비교할 때 이러한 문화권(로케일) 차이를 반영하여 해당 언어의 사전 순서대로 정렬할 수 있도록 특별한 패턴의 문자열을 생성하는 역할을 한다. 다시 말하면, strcmp로 순서 비교하고자 할 때 여기에 들어갈 문자열은 "hlava""číšník" 등의 원본 문자열이 아니고, strxfrm을 통해 변환된 문자열(일종의 해시hash)이어야 한다는 것이다.

첫 번째 실험인 <Locale Unset>항목을 본다.

아직 로케일을 명시하지 않은 상태이기 때문에 모든 문자열은 단순히 유니코드에 등재된 순서대로 비교 연산을 수행한다. 그렇기 때문에 strxfrm 함수는 문자열 버퍼에 원본 문자열 그대로를 복사하고 strcmp 함수는 원본 문자열 그대로 비교 연산을 수행한다. 앞서 설명한 대로 유니코드에 의한 단순 정렬 시 보통의 라틴문자인 'h'가 확장 라틴문자인 'č'에 선행하기 때문에(우선하기 때문에) strcmp("hlava", "číšník");의 결과 음수가 반환된다. strcmp의 반환값에 대한 설명은 [libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)]를 참고한다.

두 번째 실험인 <cs_CZ.UTF-8>항목을 본다.

setlocale 함수를 사용하여 로케일이 설정된 상태이므로 체코어 알파벳 순서에 따라 문자열의 비교가 가능하다. 로케일이 설정된 상태에서 strxfrm 함수는 문자열 버퍼에 변환된 문자열을 만든다. 각 문자열은 "hlava""číšník"로부터 얻어진 일종의 해시이기 때문에 읽을 수 있는 문자열은 아니지만 해당 문자열을 대신하여 strcmp 함수에 의한 우선순위 또는 일치 여부를 확인하는데 사용될 수 있다.

위와 같이 얻어진 문자열을 strcmp 함수에 적용해 보자. 로케일을 설정하기 전에는 'h''č'에 선행한다고 보아 음수를 반환하였는데, 로케일을 설정한 후에는 'č''h'에 선행한다고 보아서 양수를 반환하는 것을 볼 수 있다.

좀 더 확인하기 위하여 여러 종류의 로케일에 대해 문자열을 비교해보도록 한다. 위의 코드 중 setlocale에 전달되는 "cz_CS.UTF-8" 부분을 "문자열이 UTF-8로 인코드된 미국 영어 로케일"(en_US.UTF-8)과 "문자열이 UTF-8로 인코드된 한국어 로케일"(ko_KR.UTF-8)로 설정하였을 때 결과는 각각 다음과 같이 나올 것이다. (Ubuntu 기준)

########## terminal ##########
<ko_KR.UTF-8>
setlocale = "ko_KR.UTF-8"
str1: "hlava" --> "hlava"
str2: "číšník" --> "číšník"
strcmp(str1, str2) = -92
strcmp(xfm1, xfm2) = -92
returns of strxfrm: 5 / 10

<en_US.UTF-8>
setlocale = "en_US.UTF-8"
str1: "hlava" --> (garbage)
str2: "číšník" --> (garbage)
strcmp(str1, str2) = -92
strcmp(xfm1, xfm2) = 5
returns of strxfrm: 17 / 20

미국 영어 로케일(en_US)도 체코어와 같은 로마자를 사용하므로 문자 'č''c' 계열의 문자로 간주하여 'h'보다는 앞 순서로 판별하도록 strxfrm에서 문자열 변환이 이루어진다. 그러므로 strcmp(strxfrm("hlava"), strxfrm("číšník"));의 결과 양수가 반환된다.

한국어 로케일(ko_KR) 조건에서는 로마자 문화권이 아닌 상태가 되므로 'č' 문자를 인식하지 않아 단순 유니코드 값대로 순서가 판단되도록 strxfrm에서 문자열 변환이 일어나지 않는다. 그러므로 strcmp(strxfrm("hlava"), strxfrm("číšník"));의 결과 음수가 반환됨을 확인할 수 있다.

참고로 유닉스(리눅스)에서 현재 시스템이 지원 가능한 로케일의 목록을 확인하는 방법은 다음과 같다.

$ locale -a

로케일 관련된 파일들은 대체로 /usr/share/locale 디렉터리에 정의되어 있다. Debian(Ubuntu) 계열의 운영체제에서 특정 로케일을 설치하고자 할 경우 다음과 같이 실행한다.

예를 들어 한국어 로케일(ko-KR)을 생성하고자 할 경우,

$ sudo locale-gen ko_KR

그리고 EUC-KR 인코딩을 지원하는 한국어 로케일을 추가하고자 할 경우,

$ sudo locale-gen ko_KR.EUC-KR

1-1. Wide Character 확장 함수 - wcsxfrm


상기 strxfrm는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

size_t wcsxfrm(wchar_t * destination, const wchar_t * source, size_t num);

2. strcoll


함수의 원형은 다음과 같이 정의되어 있다.

int strcoll(const char * str1, const char * str2);
str1
현지 언어로 적힌 첫번째 문자열이다.
str2
현지 언어로 적힌 두번째 문자열이다.

strcoll 함수는 strxfrm 함수와 strcmp 함수를 합친 함수로서, 현재 설정된 로케일에 의한 문자열 비교 연산을 수행한다. 즉 변환 문자열을 담기 위한 버퍼의 선언과 변환 문자열의 복사는 함수 내부적으로 알아서 수행되므로 strcmp 함수를 사용할 때와 같은 방식으로 원본 문자열만 직접 전달해주면 비교 연산 결과를 반환한다. 문자열 비교 연산의 결과에 대한 정리는 [libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)]를 참고한다.

앞서 예시로 적은 소스 코드를 strcoll 함수를 사용할 경우 다음과 같이 코드 분량을 줄이면서 현지 언어 문자열 비교를 할 수 있다.

/* strcoll.c */
#include <stdio.h>
#include <string.h>
#include <locale.h>

int main(int argc, char * argv[])
{
    char str1[512] = "hlava";
    char str2[512] = "číšník";
    char * result = NULL;

    printf("<Locale Unset>\n");
    printf("setlocale = \"%s\"\n", (result == NULL) ? "NULL" : result);
    printf("strcoll(\"%s\", \"%s\") = %d\n", str1, str2, strcoll(str1, str2));

    printf("\n");

    result = setlocale(LC_ALL, "cs_CZ.UTF-8");
    printf("<cs-CZ.UTF-8>\n");
    printf("setlocale = \"%s\"\n", (result == NULL) ? "NULL" : result);
    printf("strcoll(\"%s\", \"%s\") = %d\n", str1, str2, strcoll(str1, str2));

    return 0;
}
[그림 3] strcoll.c 예제 소스 코드
[그림 4] strcoll.c 예제 소스 코드의 실행 결과

마찬가지로 두 문자열에 대해 로케일을 적용하기 전의 문자열 비교 결과와 로케일 적용 후의 문자열 비교 결과가 서로 다름을 알 수 있다. 로케일을 적용하기 전에는 문자열을 단순 유니코드 순으로 정렬하므로 "hlava""číšník"에 선행한다고 보아 음수를 반환하지만, 로케일을 적용한 후에는 확장 라틴 문자 또한 해당 언어의 알파벳 순서대로 비교하므로 "číšník""hlava"에 선행한다고 보아 strcoll("hlava", "číšník");는 양수를 반환한다.

2-1. Wide Character 확장 함수 - wcscoll


상기 strcoll은 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

int wcscoll(const wchar_t * wcs1, const wchar_t * wcs2);

<Epilogue>


본 포스팅을 통해 문자열 비교 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 11 - strerror)]에서는 가장 마지막에 발생한 오류의 내용을 구할 수 있는 strerror 함수에 대해 정리한다.

카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 09 - strpbrk)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 21. 15:57

libc 문자열 조작 함수 정리 (part 09 - strpbrk)

Language/C & C++
2018. 8. 21. 15:57

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part IX. strpbrk


본 포스팅에서는 문자열에서 미리 열거된 문자들 중 하나를 검색pointer break하는 함수인 strpbrk 함수에 대해 정리한다.

1. strpbrk


C++ 라이브러리에서 제공하는 헤더 파일인 cstring에서는 검색 대상 문자열의 상수성 여부에 따라 2개의 함수가 오버로드overload되어 있다.

const char * strpbrk(const char * str1, const char * str2);
char * strpbrk(char * str1, const char * str2);

C 라이브러리에서 제공하는 헤더 파일인 string.h에서는 하나의 함수만이 정의되어 있다.

char * strpbrk(const char * str1, const char * str2);
str1
검색 대상 본문이 보관된 문자열 버퍼이다.
str2
검색할 문자들을 열거하는 문자열 상수이다.

다음은 문자열로부터 숫자의 개수를 세는 예이다.

/* strpbrk.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str1[512] =
		"L is for the way you look at me. "
		"O is for the only one I see. "
		"V is very, very extraordinary. "
		"E is even more than anyone that you adore.";
	char str2[] = "ELOV";
	char * result = NULL;

	printf("Original String: \n\"%s\"\n", str1);
	printf("****************************************\n");
	
	result = strpbrk(str1, str2);
	while (result != NULL)
	{
		printf("\"%s\"\n", result);
		result = strpbrk(result + 1, str2);
	}

	return 0;
}
[그림 1] strpbrk.c 예제 소스 코드
[그림 2] strpbrk.c 예제 소스 코드의 실행 결과

위 코드를 참조하면, 원본 문자열로부터 'E', 'L', 'O', 'V'로 시작하는 부분 문자열을 구하게 된다. 원본 문자열은 대문자로 시작하는 5개의 문장으로 구성되어 있고 이 문자열에 strpbrk를 적용하여 [그림 2]의 결과를 볼 수 있다. strpbrk의 두 번째 매개변수(str2)에는 'E', 'L', 'O', 'V'의 4개 문자만이 지정되어 있어 각 문자로 시작되는 부분 문자열을 얻을 수 있었지만, 'A'는 지정되지 않았으므로 마지막 문장은 출력되지 않음을 알 수 있다.

1-1. Wide Character 확장 함수 - wcspbrk


상기 strpbrk는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 복사는 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

C++ 헤더인 cwchar에는 상수성 여부에 따라 다음의 두 함수가 오버로드되어 있다.

const wchar_t * wcspbrk(const wchar_t * wcs1, const wchar_t * wcs2);
wchar_t * wcspbrk(wchar_t * wcs1, const wchar_t * wcs2);

C 헤더인 wchar.h에는 하나의 함수만이 정의되어 있다.

wchar_t * wcspbrk(const wchar_t * str2, const wchar_t * str2);
카테고리 “Language/C & C++”
more...
썸네일 이미지
libc 문자열 조작 함수 정리 (part 08 - strlen)
libc 문자열 조작 함수 정리 C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로..
Language/C & C++
2018. 8. 21. 13:39

libc 문자열 조작 함수 정리 (part 08 - strlen)

Language/C & C++
2018. 8. 21. 13:39

libc 문자열 조작 함수 정리


C 언어에서 문자열 처리는 복잡하다. 언어 수준에서 문자열이라는 데이터 형 자체를 지원하지도 않으니, 덧셈 기호(+)나 비교연산자(==)와 같은 기호를 사용하는 직관적인 문자열 연산을 사용할 수 없기 때문이다. C 언어가 문자열 데이터 형을 지원하지 않고, 문자열을 다루는 연산자도 없으니 모든 문자열 연산은 문자열 함수를 통해 이루어진다. C 표준 라이브러리(일명 'libc')에서 str...로 시작하는 함수들이 그것이며, 모두 string.h 헤더(C++은 cstring 헤더)에 정의되어 있으며 본 시리즈를 통해 이들 함수의 사용법을 정리해보고자 한다. 본 시리즈는 cplusplus(http://www.cplusplus.com) 및 MSDN에 나와있는 레퍼런스를 기준으로 하여 작성되었다.

  1. libc 문자열 조작 함수 정리 (part 01 - strcpy, strncpy)
  2. libc 문자열 조작 함수 정리 (part 02 - strcat, strncat)
  3. libc 문자열 조작 함수 정리 (part 03 - strcmp, strncmp)
  4. libc 문자열 조작 함수 정리 (part 04 - strchr, strrchr)
  5. libc 문자열 조작 함수 정리 (part 05 - strstr)
  6. libc 문자열 조작 함수 정리 (part 06 - strtok)
  7. libc 문자열 조작 함수 정리 (part 07 - strspn, strcspn)
  8. libc 문자열 조작 함수 정리 (part 08 - strlen)
  9. libc 문자열 조작 함수 정리 (part 09 - strpbrk)
  10. libc 문자열 조작 함수 정리 (part 10 - strxfrm, strcoll)
  11. libc 문자열 조작 함수 정리 (part 11 - strerror)

Part IIX. strlen


본 포스팅에서는 문자열의 길이length을 구하는 함수인 strlen 함수에 대해 정리한다.

<Prologue>


strlen은 C 스타일 문자열(맨 끝에 NULL ('\0')이 붙는 문자열)의 길이를 구하는 함수이다. 즉 NULL ('\0') 문자 직전의 문자까지만 센다. 예를 들어, 문자열 "Hello" = {'H', 'e', 'l', 'l', 'o', '\0'}의 경우 NULL ('\0') 문자를 제외한 5글자('H', 'e', 'l', 'l', 'o')만을 센다.

1. strlen


strlen의 원형은 다음과 같다.

size_t strlen(const char * str);

NULL문자를 만날때까지 문자를 하나씩 센다. 함수가 종료될 때 이 문자 수를 반환한다. 이 때 문자열 버퍼를 구성하는 NULL 문자는 개수에 포함되지 않는다. 메모리를 할당할 때 이를 고려하여 버퍼의 크기를 1개 문자 더 많게 설정해야 할 것이다.

다음은 strlen으로 문자열 버퍼에서 문자의 수를 세는 예시이다.

/* strlen.c */
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
	char str[64] = "Hello, World!";
	size_t length = 0;

	length = strlen(str);

	printf("original text: \"%s\" (%zu characters).\n", str, length);

	return 0;
}
[그림 1] strlen.c 예제 소스 코드
[그림 2] strlen.c 예제 소스 코드의 실행 결과

2-1. Wide Character 확장 함수 - wcslen


상기 strlen는 ASCII 문자열 또는 UTF-8 인코딩의 Unicode 문자열에 대해 사용 가능하다. UTF-16/UTF-32와 같은 Wide Character 문자열의 경우 아래의 함수를 사용 가능하며, wchar.h, C++에서는 cwchar 헤더를 include한다.

size_t wcslen(const wchar_t * wcs);

<Epilogue>


본 포스팅을 통해 문자열 복사 함수에 대해 정리해 보았다. 다음 포스팅[libc 문자열 조작 함수 정리 (part 09 - strpbrk)]에서는 특정 문자의 위치를 반환하는 함수인 strpbrk에 대해 정리한다.

카테고리 “Language/C & C++”
more...

“Language” (37건)