Search code examples
objective-c++nsthread

How to wrap a worker NSThread inside of C++ Class


I need to create an NSThread object with a C++ class object passed. The problem is to "exit" this thread from the class the thread operates with.

I tried:

int threadProc(void* mpClass);
struct my_autopool { /// sterge valoarea obiectelor NS definite in functie/constructor
    my_autopool() { pool = [[NSAutoreleasePool alloc] init]; }
    ~my_autopool() { [pool release]; }
    NSAutoreleasePool *pool;
};

@interface NS_THD : NSObject {
    @public
    void* thobj;
}

@end

@implementation NS_THD

-(void)thread_start {
    hreadProc(thobj);
}

-(void)thread_close {
    printf("Close!\n");
    [NSThread close];
}
@end

class CLS_THD {
public:
    NSThread* mythread;
    NS_THD *trgt;
    void CreateThreadOsx(void* mpClass) {
        my_autopool ap;
        trgt=[[NS_THD alloc] init];
        trgt->thobj=(void*)mpClass;
        mythread = [[NSThread alloc] initWithTarget:trgt selector:@selector(thread_start) object:nil];
        [mythread start];
    }

    void ExitThreadOsx() {
        [mythread close];
        //[trgt thread_close];
    }
};

class CLS_CPP {
    CLS_THD th;
public:
    int var_check;
    CLS_CPP() {
        printf("Hello CLS_CPP!\n");
        var_check=77;
        th.CreateThreadOsx((void*)this);
    }

    void change() {
    var_check++;
    }

    void exit() {
        th.ExitThreadOsx();
    }
};

int threadProc(void* mpClass) {
    CLS_CPP *pClass = reinterpret_cast<CLS_CPP*>(mpClass);

    while([[NSThread currentThread] isCancelled] == NO) {
        printf("Check var %d\n", pClass->var_check);
        usleep(333000);
    }

    return 0;
}

The key issue i'm struggling right now is that it seems there is no way to "close" the NSThread object.

How to solve this situation?


Solution

  • There are two complications in the design you are trying to implement:

    1. There are no that much of means to schedule a task into an NSThread after its creation in Foundation framework when you deal with non-Foundation classes. Essentially the only (robust) way to communicate with an NSThread is by -[NSThread cancel] and -[NSThread start]. All other parts of the class (the runloop, input sources, etc..) are available from inside the thread only (or via NSObject's perform selector family of methods, but this is not really applicable to your scenario). Also the only robust way to finish a(ny) thread is by letting it finish all subroutines it was supposed to execute and i'm not aware of any well-defined implementations which lets you stop a task in the middle of executing it without adjusting the task itself (e.g. via occasional flag check).
    2. NSThread itself was designed to execute tasks wrapped inside of Foundation classes.

    To get round these problems, you apparently need an instance of NSThread (and not its NSRunLoop or sources, because accessing and calling methods of these objects violates contract and can possibly lead to race condition) in your class and get use of the newly introduced constructor of the class -[NSThread initWithBlock:]. Here is how I would implement such a class:

    class CLS_CPP {
        void(^workerBlock)(NSTimer*);
        NSThread *workerThread;
    
    public:
        int var_check;
    
    #pragma mark Special members
    
        CLS_CPP(): workerBlock{ ^(NSTimer *timer){
            if (NSThread.currentThread.isCancelled) {
                [timer invalidate];
                return;
            }
            printf("Check var %d\n", this->var_check);
        } }, var_check{ 77 } {
            start();
        }
    
        CLS_CPP(const CLS_CPP&) = delete;
        CLS_CPP& operator=(const CLS_CPP&) = delete;
    
        ~CLS_CPP() {
            if (workerThread) {
                exit();
            }
        }
    
    #pragma mark Actions
    
        void change() {
            var_check++;
        }
    
        void start() {
            if (workerThread) {
                exit();
            }
    
            workerThread = [[NSThread alloc] initWithBlock:^{
                [NSTimer scheduledTimerWithTimeInterval:0.333 repeats:YES block:workerBlock];
                while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture]) {};
            }];
            [workerThread start];
        }
    
        void exit() {
            [workerThread cancel];
            // Waits until the thread finishes
            while (!workerThread.isFinished) {
                std::this_thread::sleep_for(std::chrono::milliseconds(128));
            }
            workerThread = nil;
        }
    };
    

    As you can see I have most of special functions deleted (copy operations are deleted explicitly, move operations are deleted implicitly), because it requires extra care in your case (and is a little beyond the question asked). The start member function makes a new NSThread object and starts it. exit() cancels the thread, which state then is checked from within the task itself. The effect of usleep(333000) is achieved via NSTimer source, which is more consistent with the Cocoa framework.

    The actual task is wrapped inside of the workerBlock member variable, so you can adjust it accordingly. Provided you compile your code with ARC enabled, you don't need to do anything extra to release the CLS_CPP resources.

    Be advised, that access to var_check is not synchronised in the implementation, so you will have to take care of it as well (in case you are going to change var_check from one thread and read from the other)