NSOperation with repeat option - objective-c

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.

Related

How to share a block between MRC and ARC code?

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

App crashing while working with class and not going to catch

In my AppDelegate.m, I am doing something like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#try {
// initalizing Meeting config
MeetingConfig *config = [[MeetingConfig alloc] init];
NSLog(#"Initalized Meeting Config: %#", config);
[config setRoomName:#"test123"];
NSLog(#"SetRoom name for Meeting config: %#", config.roomName);
NSString *clientId = #"";
NSLog(#"Unused Client id is: %#", clientId);
//Call UIView from here
}#catch (NSException *exception) {
NSLog(#"exception: %#", exception);
}
return YES;
}
Where my MeetingConfig.m file looks like this
#implementation MeetingConfig
- (id) init
{
if (self = [super init]) {
self.apiBase = #"https://api.in";
self.showSetupScreen = false;
self.autoTune = true;
}
return self;
}
- (void) setAuthToken:(NSString *)authToken
{
self.authToken = authToken;
}
- (void) setApiBase:(NSString *)apiBase
{
self.apiBase = apiBase;
}
// more code
and MeetingConfig looks like this
#import <Foundation/Foundation.h>
#interface MeetingConfig : NSObject
#property (nonatomic, assign) NSString* roomName;
#property (nonatomic, assign) NSString* authToken;
#property (nonatomic, assign)Boolean autoTune;
#property (nonatomic, assign)NSString* apiBase;
#property (nonatomic, assign)Boolean showSetupScreen;
- (void) setRoomName:(NSString *)roomName;
- (void) setAuthToken:(NSString *)authToken;
- (void) setShowSetupScreen:(Boolean)showSetupScreen;
- (void) setAutoTuneEnabled:(Boolean)autoTune;
- (id) init;
#end
Can someone help me in determining what I could be doing wrong here? and why doesn't it log exception in NSLog? Also, I am super new to objective C (i have been asked to stick with Objective c) and if anyone have any suggestion in regards to the code then please let me know.
Error
You're using assign for reference/pointer types: #property retain, assign, copy, nonatomic in Objective-C
They should probably be declared copy, because this is a kind of value object, I think.
No exceptions were caught because no exceptions were thrown. Throwing/catching exceptions for control flow is not common in Objective-C
You don't need to write explicit setter functions for #properties
You should prefer to use BOOL type instead of Boolean, with values of YES/NO instead of true/false.
You should return instancetype not id from init, at least in reasonably modern Objective C
Consider making an initialiser that takes all the properties (initWithRoomName:clientID:) and make them read only once set
You don't need to declare -(id) init in your header since it gets that from NSObject

How to properly use a protocol-defined property in Realm predicates?

I am trying to implement a cache system using Realm.
Depending on classes, the cache should have different lengths. I've therefore defined a StalenessChecking protocol:
#protocol StalenessChecking <NSObject>
#required
// nonatomic needed when using a getter
#property (readonly, nonatomic, getter=isStale) bool stale;
#optional
- (void) setStaleness: (NSTimeInterval) duration;
#end
and an object:
Interface file (DziObject.h)
#import "Realm.h"
#import "StalenessChecking.h"
#interface DziObject : RLMObject <StalenessChecking>
#property (readonly) NSDate* refresh;
#end
Implementation file (DiObject.m)
#import 'DziObject.h'
#implementation DziObject
{
NSTimeInterval stalenessInterval;
}
#synthesize stale = _stale;
- (instancetype)init
{
self = [super init];
if (self) {
_refresh = [NSDate date];
stalenessInterval = 120.0;
}
return self;
}
- (bool) isStale {
return [[NSDate dateWithTimeInterval:stalenessInterval sinceDate:_refresh] timeIntervalSinceReferenceDate]< [[NSDate date] timeIntervalSinceReferenceDate];
}
- (void)setStaleness:(NSTimeInterval) duration
{
stalenessInterval = duration;
}
#end
I then call them from a Facade:
Interface:
#import <Foundation/Foundation.h>
#interface SDK_Facade : NSObject
+ (void) createDziO;
#end
Implementation:
#import "SDK_Facade.h"
#import "DziObject.h"
#implementation SDK_Facade
+ (void) createDziO
{
DziObject *dziO = [[DziObject alloc] init];
// both work fine
if (dziO.isStale) {
NSLog(#"Is stale");
}
if (dziO.stale) {
NSLog(#"Is really stale");
}
// Query Realm for all results less than 2 minutes old
// TODO: -- currently crashes
RLMResults< DziObject *> * dzios = [DziObject objectsWhere:#"stale == %#", #YES];
NSLog(#"DziOs: %lu", (unsigned long)dzios.count);
// Persist your data easily
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:dziO];
}];
// Queries should update in realtime
NSLog(#"DO: %lu", (unsigned long)dzios.count);
}
#end
I get an ugly crash:
Terminating app due to uncaught exception 'Invalid property name', reason: 'Property 'stale' not found in object of type 'DziObject'
You can't define a Realm model property in a protocol that the model class conforms to. It must be done in the class definition itself.
#interface DziObject : RLMObject <StalenessChecking>
#property NSDate* refresh;
#property (getter=isStale) bool stale;
#end

Incomplete Implementation Example Help!

I am working on an example from a book that I got and it doesnt seem to be working I am getting the warning Incomplete implementation. When I run the program I get an error singal "EXC_BAD_ACCESS". The warning is in the .m file at the line return [NSString stringWithFormat:#"Name:... Does anyone know what I am doing wrong?
my .m file
#import "RadioStation.h"
#implementation RadioStation
+ (double)minAMFrequency {
return 520.0;
}
+ (double)maxAMFrequency {
return 1610.0;
}
+ (double)minFMFrequency {
return 88.3;
}
+ (double)maxFMFrequency {
return 107.9;
}
- (id)initWithName:(NSString *)newName atFrequency:(double)newFreq atBand:(char)newBand {
self = [super init];
if (self != nil) {
name = [newName retain];
frequency = newFreq;
band = newBand;
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:#"Name: %#, Frequency: %.1f Band: %#", name, frequency, band];
}
- (void)dealloc {
[name release];
[super dealloc];
}
#end
My .h file
#import <Cocoa/Cocoa.h>
#interface RadioStation : NSObject {
NSString *name;
double frequency;
char band;
}
+ (double)minAMFrequency;
+ (double)maxAMFrequency;
+ (double)minFMFrequency;
+ (double)maxFMFrequency;
-(id)initWithName:(NSString*)name
atFrequency:(double)freq
atBand:(char)ban;
-(NSString *)name;
-(void)setName:(NSString *)newName;
-(double)frequency;
-(void)setFrequency:(double)newFrequency;
-(char)band;
-(void)setBand:(char)newBand;
#end
radiosimulation.m file:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSMutableDictionary* stations = [[NSMutableDictionary alloc] init];
RadioStation* newStation;
newStation = [[RadioStation alloc] initWithName:#"Star 94"
atFrequency:94.1
atBand:'F'];
[stations setObject:newStation forKey:#"WSTR"];
[newStation release];
NSLog(#"%#", [stations objectForKey:#"WSTR"]);
newStation = [[RadioStation alloc] initWithName:#"Rocky 99"
atFrequency:94.1
atBand:'F'];
[stations setObject:newStation forKey:#"WKFR"];
[newStation release];
NSLog(#"%#", [stations objectForKey:#"WKFR"]);
[stations release];
[pool drain];
return 0;
You are declaring the following property accessor/mutators (getter/setters) but are not implementing them in your .m file.
-(NSString *)name;
-(void)setName:(NSString *)newName;
-(double)frequency;
-(void)setFrequency:(double)newFrequency;
-(char)band;
-(void)setBand:(char)newBand;
You need to implement all 6 of these methods in the .m file if you want to remove the warning about incomplete implementation.
You are effectively saying in the .h file that this is what your object is going to do, then not doing it in the .m. It won't generate an error, as objective-c messaging means that the message will be handed up to NSObject to deal with, which will also not have any matching implementation, and the messages will just be silently ignored. I don't like the way that this is only shown as a warning - but there you go.
That said, I wouldn't create the properties like this (there are neater ways of doing this in objective-c using #property), I would remove those method declarations in the .h and replace them with:
#property (nonatomic, retain) NSString *name;
#property (nonatomic, assign) double frequency;
#property (nonatomic, assign) char band;
These property declarations go in the same place as method declarations.
and then add the following to the .m file:
#synthesize name;
#synthesize frequency;
#synthesize band;
This will avoid having to write all that boilerplate accessor/mutator code that you are currently missing. Again, these go in the same region of the code as method implementations. Effectively the compiler is going to create name and setName methods automatically.
This code is untested - but should point you in the right direction for tidying up the incomplete implementation. It may fix your access error too - but that may require more detailed look at a stack trace.
Another point I'm not sure the code as written even needs to use get/set methods or properties. You might try removing the method declarations from the .h and see if it works. It seems that all the accesses to name, frequency and band are all from within the object.

Help with a method that returns a value by running another object's method

I have a Class that runs the following method (a getter):
// the interface
#interface MyClass : NSObject{
NSNumber *myFloatValue;
}
- (double)myFloatValue;
- (void)setMyFloatValue:(float)floatInput;
#end
// the implementation
#implementation
- (MyClass *)init{
if (self = [super init]){
myFloatValue = [[NSNumber alloc] initWithFloat:3.14];
}
return self;
}
// I understand that NSNumbers are non-mutable objects and can't be
// used like variables.
// Hence I decided to make make the getter's implementation like this
- (double)myFloatValue{
return [myFloatValue floatValue];
}
- (void)setMyFloatValue:(float)floatInput{
if ([self myFloatValue] != floatInput){
[myFloatValue release];
myFloatValue = [[NSNumber alloc] initWithFloat:floatInput;
}
#end
When I mouse over the myFloatValue object during debugging, it does not contain a value. Instead it says: "out of scope".
I would like to be able to make this work without using #property, using something other than NSNumbers, or other major changes since I just want to understand the concepts first. Most importantly, I would like to know what mistake I've apparently made.
I can see a couple of mistakes:
The line #implementation should read #implementation MyClass
The function setMyFloatValue is missing a closing ] and } —it should read:
- (void)setMyFloatValue:(float)floatInput{
if ([self myFloatValue] != floatInput){
[myFloatValue release];
myFloatValue = [[NSNumber alloc] initWithFloat:floatInput];
}
}
I've just tested it in Xcode and it works for me with these changes.
Why not just set property in interface and synthesize accessors in implementation?
#interface MyClass : NSObject {
float *myFloat
}
#property (assign) float myFloat;
#end
#implementation MyClass
#synthesize myFloat;
#end