Passing blocks in Objective-C

前端 未结 4 1789
青春惊慌失措
青春惊慌失措 2020-12-24 15:20

When writing a method that accepts a block as an argument, do I need to do anything special such as copying the block to the heap before executing it? For example, if I had

4条回答
  •  一个人的身影
    2020-12-24 16:07

    As the blocks programming topics guide says under 'Copying Blocks':

    Typically, you shouldn’t need to copy (or retain) a block. You only need to make a copy when you expect the block to be used after destruction of the scope within which it was declared.

    In the case you are describing, you can basically think about the block as simply being a parameter to your method, just like as if it were an int or other primitive type. When the method gets called, space on the stack will be allocated for the method parameters and so the block will live on the stack during the entire execution of your method (just like all the other parameters). When the stack frame gets popped off the top of the stack at the return of the method, the stack memory allocated to the block will be deallocated. Thus, during the execution of your method, the block is guaranteed to be alive, so there's no memory management to deal with here (in both ARC and non-ARC cases). In other words, your code is fine. You can simply call the block inside the method.

    As the quoted text suggests, the only time you need to explicitly copy a block is when you want it to be accessible from outside of the scope where it was created (in your case, beyond the lifetime of the stack frame of your method). As an example, suppose you want a method that fetches some data from the web, and runs a block of code when the fetch is complete. Your method signature might look like:

    - (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(void))completionHandler;

    Since the data fetch happens asynchronously, you would want to keep the block around (likely in a property of your class) and then run the block once the data has been fully fetched. In this case, your implementation might look like:

    @interface MyClass
    
    @property (nonatomic, copy) void(^dataCompletion)(NSData *);
    
    @end
    
    
    
    @implementation MyClass
    @synthesize dataCompletion = _dataCompletion;
    
    - (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler {
        self.dataCompletion = completionHandler;
        [self fetchDataFromURL:url]; 
    }
    
    - (void)fetchDataFromURL:(NSURL *)url {
        // Data fetch starts here 
    }
    
    - (void)finishedFetchingData:(NSData *)fetchedData {
        // Called when the data is done being fetched
        self.dataCompletion(fetchedData)
        self.dataCompletion = nil; 
    }
    

    In this example, using a property with a copy semantic will perform a Block_copy() on the block and copy it to the heap. This happens in the line self.dataCompletion = completionHandler. Thus, the block gets moved from the stack frame of the -getDataFromURL:completionHandler: method to the heap which allows it to be called later in the finishedFetchingData: method. In the latter method, the line self.dataCompletion = nil nullifies the property and sends a Block_release() to the stored block, thus deallocating it.

    Using a property in this way is nice since it will essentially handle all of the block memory management for you (just make sure it's a copy (or strong) property and not simply a retain) and will work in both non-ARC and ARC cases. If instead you wanted to use a raw instance variable to store your block and were working in a non-ARC environment, you would have to call Block_copy(), Block_retain(), and Block_release() yourself in all the proper places if you wanted to keep the block around any longer than the lifetime of the method in which it's passed as a parameter. The same code above written using an ivar instead of a property would look like this:

    @interface MyClass {
        void(^dataCompletion)(NSData *);
    }
    
    @end
    
    
    
    @implementation MyClass
    
    - (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler {
        dataCompletion = Block_copy(completionHandler);
        [self fetchDataFromURL:url]; 
    }
    
    - (void)fetchDataFromURL:(NSURL *)url {
        // Data fetch starts here 
    }
    
    - (void)finishedFetchingData:(NSData *)fetchedData {
        // Called when the data is done being fetched
        dataCompletion(fetchedData)
        Block_release(dataCompletion);
        dataCompletion = nil;
    }
    

提交回复
热议问题