Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
iOS Developer/Objective-C

8. Objective-C Blocks 블록

by Toughie 2024. 5. 7.

KAKAO-Choonsik

블록이란?

Objective-C의 클래스는 여러 데이터와 관련된 여러 메서드로 이루어져 있다. (프로퍼티와 메서드)

하지만 작업을 하다 보면 여러 메서드의 조합 보다는 하나의 작업만 수행해도 되는 경우가 있다. 

즉 클래스까지 따로 만들 필요는 없는 경우를 말한다.

 

Block은 값인 것처럼 함수에 전달될 수 있는, 코드 덩어리를 하나의 단위로 캡슐화 하는데 사용되는 객체이다.

익명함수,람다와 비슷하고 Swift에서 클로저라고 생각하면 되겠다.(클로저는 Swift에서 1급 객체이다.)

 

블록 또한 1급 객체로,

변수나 상수에 할당이 가능하고, 다른 함수의 파라미터로 전달 가능하고,

함수의 반환 값으로 사용 가능하며 배열과 같은 자료구조에 저장할 수 있다.


블록 활용처

- GCD를 통한 비동기 작업

- 콜백함수의 인자

- 컬렉션의 이터레이션(NSArray, NSDictionary) 등


블럭 구현

returntype (^blockName) (argumentType) = ^{
};

#import <Foundation/Foundation.h>

//블럭 정의
void (^sampleBlock)(void) = ^{
    NSLog(@"BLOCK");
};

int main(void) {
//블럭 호출
    sampleBlock();
    return 0;
}

 

값을 받아 값을 반환하는 블럭 예시

#import <Foundation/Foundation.h>

double (^multiply)(double, double) =
    ^(double first, double second) {
        return first * second;
    };

int main(void) {
    double result = multiply(3,9);
    NSLog(@"%f",result);
    return 0;
}

 

typedef를 활용해 블록을 사용할 때마다 반환타입과 매개변수 다시 정의하지 않는 예시

#import <Foundation/Foundation.h>

//반환형과 매개변수를 가지지 않는 블록
typedef void (^CompletionBlock)(void);

@interface MyClass: NSObject
-(void)performActionWithCompletion: (CompletionBlock)completionBlock;
@end

@implementation MyClass
-(void)performActionWithCompletion:(CompletionBlock)completionBlock {
    NSLog(@"Action Perform");
    completionBlock();
}
@end

int main(void) {
    MyClass *myClass = [[MyClass alloc] init];
    [myClass performActionWithCompletion:^{
        NSLog(@"Perform After Action Perform printed");
    }];
    
    return 0;
}

블록의 변수 캡쳐

 

블록의 변수 캡쳐는 해당 블록 내에서 외부 스코프에 있는 변수를 참조하는 기능을 말한다.

블록이 생성될 때 외부 스코프에 있는 변수의 값을 복사하고, 이후 블록 내에서 해당 변수에 접근할 수 있게 된다.

 

값 캡쳐

변수의 값을 복사해서 블록 내에서 사용한다. 

블록이 생성될 떄 외부 변수의 당시 값을 기억하기 때문에, 외부 변수의 값이 이후 변경되어도 블록 내에서 사용되는 값은 변하지 않는다.

 

참조 캡쳐

변수 선언 시 __block 키워드를 사용한다.

외부 변수에 대한 참조(포인터)를 블록 내에서 사용한다.

외부 변수의 변경이 블록 내에서 반영이 되고, 외부 변수와 블록 내에서 사용하는 값은 동기화 된다.

즉 서로의 변화가 영향을 끼칠 수 있다는 말이다.

 

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
    int outsideValue = 5;
    void (^valueCaptureBlock)(void) = ^{
        NSLog(@"value capture: %d",outsideValue);
    };
    outsideValue = 10;
    valueCaptureBlock(); //생성 시점에서 outsideValue가 5였기 때문에 5가 출력된다.
    
    __block int outsideValue2 = 99;
    void (^referenceCaptureBlock)(void) = ^{
        NSLog(@"reference capture: %d", outsideValue2);
    };
    outsideValue2 = 100;
    referenceCaptureBlock(); //outsideValue2의 값이 아닌, 주소를 참조하기 때문에 100이 출력된다.
}

약한 참조 __weak

Swift에서 weak self 키워드로 강한 순환참조로 인한 메모리 누수를 방지하는 것과 거의 동일한 개념이다.

즉 참조를 하더라도 retain을 통해 참조카운트를 늘리지 않는다는 말이다.

 

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@property int myValue;
@end

@implementation MyClass
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *myClass = [[MyClass alloc] init];
        myClass.myValue = 7;
        __weak MyClass *weakObject = myClass;

        void (^block)(void) = ^{
            if (weakObject) {
                NSLog(@"Weak Object's Value: %d", weakObject.myValue);
            } else {
                NSLog(@"Weak Object is nil.");
            }
        };
        
        myClass = nil;

        block();
    }
    return 0;
}

 

일단.. 대략 개념과 사용법은 위와 같은데 무슨 이유인지 xcode에서 block을 호출하면 값이 제대로 출력되고 있다.(왜?)

객체가 nil이 되면 약한 참조도 nil이 될 거라 생각했는데 흠..이따 다시 알아보고 수정할 예정

 

(원인 파악)

*xcode의 프로젝트에서 ARC 환경으로 다시 설정하니,위 로직이 잘 동작했다.

객체 - 객체 약한 참조 관계에서 객체를 nil로 참조를 끊어주면 약한 참조 또한 자동으로 nil이 된다.

하지만 이는 ARC를 사용하는 환경에서의 얘기였다. 

 

즉 ARC를 사용하지 않는, 수동 메모리 관리를 해주는 프로젝트라면 week를 사용하는 것이 아니라

retain, release 등을 직접 해줘야 한다.