I have a piece of ARC code written in Objective-C that is creating a block and will want to provide it to some Objective-C MRC code as a callback, to be called when an operation finishes. What is the safest way to do that? My hunch is to just provide the MRC code with a copy.
Is there a guide somewhere that explains the memory management issues that arise when mixing ARC and MRC code?
Provided you handle the request in-place, you don't need to do anything special under MRR, since blocks themselves make const copies of the referenced values. Thus in this example the callback parameter is retained automagically (and doesn't get released until after dispatch_after's block finishes):
- (void)calculateResult:(void (^)(NSInteger))callback {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
callback(_value);
});
}
If you want to retain the callback and use it later on, then you should explicitly copy the block passed OR still copy it via synthesised properties with copy semantic:
NS_ASSUME_NONNULL_BEGIN
#interface TDWMrrObject ()
#property (copy, nonatomic, nullable) void(^callback)(NSInteger);
#property (assign, nonatomic, nullable) NSTimer *timer;
#property (assign, nonatomic) NSInteger value;
- (void)asyncCalculate;
#end
NS_ASSUME_NONNULL_END
#implementation TDWMrrObject
#pragma mark Lifecycle
- (instancetype)init {
if (self = [super init]) {
_value = 4;
}
return self;
}
- (void)dealloc {
[_callback release];
[_timer invalidate];
[super dealloc];
}
#pragma mark Actions
- (void)calculateResult:(void (^)(NSInteger))callback {
self.callback = callback;
[self asyncCalculate];
}
#pragma mark Private
- (void)asyncCalculate {
if (_timer) {
[_timer invalidate];
}
self.timer = [NSTimer scheduledTimerWithTimeInterval:4 repeats:NO block:^(NSTimer * _Nonnull timer) {
_callback(_value);
self.callback = nil;
self.timer = nil;
}];
}
#end
Related
I have one model.
I want to update one attribute each 3 seconds after init, and I need to change the attribute in one other view controller later.
the code is:
MODEL
#interface Ap : NSObject
#property (nonatomic, retain) NSString *address;
#property (nonatomic, retain) NSString *sessionId;
+ (id)sharedInstance;
#end
#implementation Ap
#synthesize sessionId, address;
-(id) init {
if (self = [super init]) {
self.address = nil;
self.sessionId = nil;
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
return self;
}
-(void) updateSession{
sessionId = #"some string";
}
- (void) update{
self.sessionId = #"some value from network";
}
+ (Ap *)sharedInstance {
if (!sharedInstance) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
#end
CONTROLLER
- (void) viewDidLoad {
[super viewDidLoad];
[[Ap sharedInstance] updateSession];
}
The error is: (lldb) bad access when model update
And I change the updateSession method to
-(void) updateSession{
self.sessionId = #"some string";
}
The error is gone, can anyone tell me why?
You should use self.ap = [[Ap alloc] init]; in your app delegate. Your model object might be getting released when you try to call update on that which might cause a crash.
Your code will look like this,
#interface SomeAppDelegate : UIResponder{
#property(nonatomic, retain) Ap *ap; //declare property here
#end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if(!self.ap){
self.ap = [[Ap alloc] init]; //use property here as self.ap which will retain ap. Otherwise it will not retain it.
}
}
Update:
Looks like you have removed the previous question and added a completely new question to the previous one. Anyways I will try to answer this one as well.
The error is gone, can anyone tell me why?
The reason is same as what I mentioned above. If you use self.sessionId = #"some string";, you are retaining the object. Because it is a property and a property will internally retain since you have declared that property as retain. But if you use sessionId = #"some string";, its scope is only inside that method since you are not using the property. You are directly setting the value without calling property and it will autoreleased after that method. And hence you will get a bad access.
I would suggest you to go through the apple documentation to understand more about properties and its working.
I recently ran into reentrancy issues with KVO. To visualize the problem, I would like to show a minimal example. Consider the interface of an AppDelegate class
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic) int x;
#end
as well as its implementation
#implementation AppDelegate
- (BOOL) application:(__unused UIApplication *)application
didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions
{
__unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self];
self.x = 42;
NSLog(#"%d", self.x);
return YES;
}
#end
Unexpectedly, this program prints 43 to the console.
Here's why:
#interface BigBugSource : NSObject {
AppDelegate *appDelegate;
}
#end
#implementation BigBugSource
- (id)initWithAppDelegate:(AppDelegate *)anAppDelegate
{
self = [super init];
if (self) {
appDelegate = anAppDelegate;
[anAppDelegate addObserver:self
forKeyPath:#"x"
options:NSKeyValueObservingOptionNew
context:nil];
}
return self;
}
- (void)dealloc
{
[appDelegate removeObserver:self forKeyPath:#"x"];
}
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(__unused void *)context
{
if (appDelegate.x == 42) {
appDelegate.x++;
}
}
#end
As you see, some different class (that may be in third-party code you do not have access to) may register an invisible observer to a property. This observer is then called synchronously, whenever the property's value has changed.
Because the call happens during the execution of another function, this introduces all sort of concurrency / multithreading bugs although the program runs on a single thread. Worse, the change happens without an explicit notice in the client-code (OK, you could expect that concurrency issues arise whenever you set a property...).
What is the best practice to solve this problem in Objective-C?
Is there some common solution to regain run-to-completion semantics automatically, meaning that KVO-Observation messages go through an event-queue, AFTER the current method finishes executing and invariants / postconditions are restored?
Not exposing any properties?
Guarding every critical function of an object with a boolean variable to ensure that reentrancy is not possible?
For example: assert(!opInProgress); opInProgress = YES; at the beginning of the methods, and opInProgress = NO; at the end of the methods. This would at least reveal those kind of bugs directly during runtime.
Or is it possible to opt out of KVO somehow?
Update
Based on the answer by CRD, here is the updated code:
BigBugSource
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(__unused void *)context
{
if (appDelegate.x == 42) {
[appDelegate willChangeValueForKey:#"x"]; // << Easily forgotten
appDelegate.x++; // Also requires knowledge of
[appDelegate didChangeValueForKey:#"x"]; // whether or not appDelegate
} // has automatic notifications
}
AppDelegate
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"x"]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
- (BOOL) application:(__unused UIApplication *)application
didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions
{
__unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self];
[self willChangeValueForKey:#"x"];
self.x = 42;
NSLog(#"%d", self.x); // now prints 42 correctly
[self didChangeValueForKey:#"x"];
NSLog(#"%d", self.x); // prints 43, that's ok because one can assume that
// state changes after a "didChangeValueForKey"
return YES;
}
What you are asking for is manual change notification and is supported by KVO. It is a three stage process:
Your class overrides + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey returning NO for any property you wish to defer notifications for and deferring to super otherwise;
Before changing a property you call [self willChangeValueForKey:key]; and
When you are ready for the notification to occur you call [self didChangeValueForKey:key]
You can build on this protocol quite easily, e.g. it is easy to keep a record of keys you have changed and trigger them all before you exit.
You can also use willChangeValueForKey: and didChangeValueForKey with automatic notifications turned on if you directly alter the backing variable of a property and need to trigger KVO.
The process along with an examples is described in Apple's documentation.
I've developed a Category that gives the NSOperation the ability to be executed in the background at timed intervals. I would really appreciate getting some feedback on this, especially any potential problems with this approach that I'm not thinking of.
Thank you!
Here's the code:
NSOperation+Repeat.h
#import <Foundation/Foundation.h>
#interface NSOperation (repeat)
#property (readonly, nonatomic) NSTimeInterval repeatInterval;
#property (readonly, nonatomic) NSOperationQueue *repeatOperationQueue;
- (void)performUsingOperationQueue:(NSOperationQueue *)operationQueue;
- (void)performAtRepeatingInterval:(NSTimeInterval)interval usingOperationQueue:(NSOperationQueue *)operationQueue;
#end
NSOperation+Repeat.m
#import "NSOperation+repeat.h"
#import <objc/runtime.h>
static char const * const RepeatPropertiesKey = "RepeatProperties";
#implementation NSOperation (repeat)
#dynamic repeatInterval;
#dynamic repeatOperationQueue;
static NSString * RepeatIntervalKey = #"interval";
static NSString * RepeatOperationQueueKey = #"operationQueue";
static NSString * RepeatTimerKey = #"timer";
- (NSMutableDictionary *)repeatProperties {
NSMutableDictionary * properties = objc_getAssociatedObject(self, RepeatPropertiesKey);
if (properties == nil) {
properties = [NSMutableDictionary new];
objc_setAssociatedObject(self, RepeatPropertiesKey, properties, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return properties;
}
- (NSTimeInterval)interval {
NSNumber * interval = [[self repeatProperties] objectForKey:RepeatIntervalKey];
return [interval doubleValue];
}
- (NSOperationQueue *)repeatOperationQueue {
NSOperationQueue * operationQueue = [[self repeatProperties] objectForKey:RepeatOperationQueueKey];
return operationQueue;
}
- (void)performUsingOperationQueue:(NSOperationQueue *)operationQueue {
[operationQueue addOperation:[self copy]];
}
- (void)performAtInterval:(NSTimer *)timer {
[self performUsingOperationQueue:self.repeatOperationQueue];
}
- (void)performAtRepeatingInterval:(NSTimeInterval)interval usingOperationQueue:(NSOperationQueue *)operationQueue {
// Save interval and operationQueue in repeatProperties
[self.repeatProperties setValue:[NSNumber numberWithDouble:interval] forKey:RepeatIntervalKey];
[self.repeatProperties setValue:operationQueue forKey:RepeatOperationQueueKey];
// Create timer to call performAtInterval on self
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:(interval*60)
target:self
selector:#selector(performAtInterval:)
userInfo:nil
repeats:YES];
// Save the timer in repeatProperties
[self.repeatProperties setValue:timer forKey:RepeatTimerKey];
[self performUsingOperationQueue:operationQueue];
}
#end
Here's an example of a NSOperation subclass that can repeat:
MSScheduleImportOperation.h
#import <Foundation/Foundation.h>
#import "NSOperation+Repeat.h"
#interface MSScheduleImportOperation : NSOperation <NSCopying>
#property (readonly, strong, nonatomic) NSString* employeeId;
- (id)initWithEmployeeId:(NSString *)employeeId;
#end
MSScheduleImportOperation.m
#import "MSScheduleImportOperation.h"
#implementation MSScheduleImportOperation
#synthesize employeeId = __employeeId;
- (id)initWithEmployeeId:(NSString *)employeeId {
self = [super init];
__employeeId = [employeeId copy];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
MSScheduleImportOperation* copy = [[MSScheduleImportOperation alloc] initWithEmployeeId:self.employeeId];
return copy;
}
- (void)main
{
...
}
#end
The Apple documentation says:
An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again.
So the first problem is that there might be internals that stop it from working. Although, I see you try to get around the problem by making a copy.
This leads us to the other problem is that NSOperation is not advertised to conform to NSCopying.
[operationQueue addOperation:[self copy]];
This line should throw an exception.
Instead of a category on NSOperation where the object copies itself and adds the copy to an NSOperationQueue - it would be simpler to manage this at a higher level. For example:
+[RepeatingOperation operationBlock:(InitOperationBlock)operationBlock
queue:(NSOperationQueue*)queue
interval:(NSTimeInterval)interval];
where InitOperationBlock would be a block where the operation was created and configured.
The main benefit is the API would be harder to mess up. For example in category in the original post, performUsingOperationQueue: will fail silently if you forget to set repeatOperationQueue.
Is it safe to say that if a class member does not need getter or setter functions then there's no point in making them properties and synthesizing them?
Well, yes, but often properties can be helpful in the implementation itself even if the properties won't be set outside of the implementation.
For example, suppose you had
#interface SomeObject : NSObject {
NSThing *thing;
}
#end
#implementation SomeObject
- (id)init {
if((self = [super init]))
thing = [[NSThing someThing] retain];
return self;
}
- (void)someMethod {
if(thing)
[thing release];
thing = [[NSThing someOtherThing] retain];
}
// etc etc
#end
Why would you want to bother having to check if thing had been allocated, release thing, set it to something else, and then retain it again, when you could simply do:
- (id)init {
if((self = [super init]))
[self setThing:[NSThing someThing]];
return self;
}
- (void)someMethod {
[self setThing:[NSThing someOtherThing]];
}
If you don't want to make these properties accessible outside of your class, you can use a category
#interface SomeObject ()
#property (retain) NSThing *thing;
#end
in your .m file.
If I have a custom class called Tires:
#import <Foundation/Foundation.h>
#interface Tires : NSObject {
#private
NSString *brand;
int size;
}
#property (nonatomic,copy) NSString *brand;
#property int size;
- (id)init;
- (void)dealloc;
#end
=============================================
#import "Tires.h"
#implementation Tires
#synthesize brand, size;
- (id)init {
if (self = [super init]) {
[self setBrand:[[NSString alloc] initWithString:#""]];
[self setSize:0];
}
return self;
}
- (void)dealloc {
[super dealloc];
[brand release];
}
#end
And I synthesize a setter and getter in my View Controller:
#import <UIKit/UIKit.h>
#import "Tires.h"
#interface testViewController : UIViewController {
Tires *frontLeft, *frontRight, *backleft, *backRight;
}
#property (nonatomic,copy) Tires *frontLeft, *frontRight, *backleft, *backRight;
#end
====================================
#import "testViewController.h"
#implementation testViewController
#synthesize frontLeft, frontRight, backleft, backRight;
- (void)viewDidLoad {
[super viewDidLoad];
[self setFrontLeft:[[Tires alloc] init]];
}
- (void)dealloc {
[super dealloc];
}
#end
It dies after [self setFrontLeft:[[Tires alloc] init]] comes back. It compiles just fine and when I run the debugger it actually gets all the way through the init method on Tires, but once it comes back it just dies and the view never appears. However if I change the viewDidLoad method to:
- (void)viewDidLoad {
[super viewDidLoad];
frontLeft = [[Tires alloc] init];
}
It works just fine. I could just ditch the setter and access the frontLeft variable directly, but I was under the impression I should use setters and getters as much as possible and logically it seems like the setFrontLeft method should work.
This brings up an additional question that my coworkers keep asking in these regards (we are all new to Objective-C); why use a setter and getter at all if you are in the same class as those setters and getters.
You have declared frontLeft as a 'copy' property:
#property (nonatomic,copy) Tires *frontLeft, *frontRight, *backleft, *backRight;
When you assign to this property, a copy is made by invoking the object's copy method. This only works for objects which support the NSCopying protocol (i.e., which implement a copyWithZone: method). Since your Tires class does not implement this method, you get an exception.
You probably want to change this to be a 'retain' property:
#property (nonatomic,retain) Tires *frontLeft, *frontRight, *backleft, *backRight;
See the Objective C documentation on declared properties for more on property declarations.
One problem that i see is here:
- (void)viewDidLoad {
[super viewDidLoad];
[self setFrontLeft:[[Tires alloc] init]];
}
When you call [Tires alloc] you get back an object with a retain count of 1. You then use a set method which you have synthesized, which bumps the retain count to 2. When your object is done with the Tire object, it will reduce the retain count back to 1, but the tire will never get deallocated. I think you should use:
[self setFrontLeft:[[[Tires alloc] init] autorelease]];