I'm using Objective-C, and I don't know how to create and call a method with out parameters when compiling the code with the ARC compiler.
This is the kind of thing I'm trying to accomplish in non-ARC Objective-C (this is probably wrong anyway).
//
// Dummy.m
// OutParamTest
#import "Dummy.h"
#implementation Dummy
- (void) foo {
NSString* a = nil;
[self barOutString:&a];
NSLog(#"%#", a);
}
- (void) barOutString:(NSString **)myString {
NSString* foo = [[NSString alloc] initWithString:#"hello"];
*myString = foo;
}
#end
I've read the documentation here:
https://clang.llvm.org/docs/AutomaticReferenceCounting.html
and here:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html
...but am finding it difficult to get anything that compiles, never mind anything that is correct. Would anybody be able to rewrite the jist of the code above, in a way that is suitable for ARC Objective-C?
You need to use the __autoreleasing attribute on the out parameter:
- (void) barOutString:(NSString * __autoreleasing *)myString {
NSString* foo = [[NSString alloc] initWithString:#"hello"];
*myString = foo;
}
The prerelease documentation (which I'm not allowed to link to due to NDA) puts the __autoreleasing in the middle of the two '*'s, but it might just work as (__autoreleasing NSString **)
You also cannot use an indirect double pointer (b) as in your original code. You must use this form:
- (void) foo {
NSString* a = nil;
[self barOutString:&a];
NSLog(#"%#", a);
}
You are also calling dealloc directly on an object which is completely wrong. I suggest you read the memory management guidelines.
Related
My app currently uses this deprecated function:
id unarchivedObject=[NSKeyedUnarchiver unarchiveObjectWithData:codedData];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns TRUE when reading existing user data.
}
To update, I've converted to this:
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[NSDictionary class] fromData:codedData error:nil];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns FALSE when reading existing user data.
}
The data was originally encoded like this:
-(void)encodeWithCoder:(NSCoder*)encoder{
[encoder encodeObject:text forKey:#"text"];
}
-(instancetype)initWithCoder:(NSCoder*)decoder{
if(self=[super init]){
text=[decoder decodeObjectForKey:#"text"];
}
What could be causing the IF statement to return FALSE using the newer code?
Please note that I am concerned primarily with reading existing data stored prior to deprecating the Archiving functions. Simply changing to the newer functions does not resolve the issue.
Interesting question! I've been supporting iOS 10.0 so I haven't encountered such issue until I saw this. I was tinkering for an hour and I successfully found the issue.
What could be causing the IF statement to return FALSE using the newer
code?
It's because your unarchivedObject object is nil!
If you use the parameter error in the new method, you would see an error like this:
Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only
decode classes that adopt NSSecureCoding. Class 'QTPerson' does not
adopt it." UserInfo={NSDebugDescription=This decoder will only decode
classes that adopt NSSecureCoding. Class 'QTPerson' does not adopt it.
But how do we get the correct value for this unarchivedObject and not nil? It would take a couple of steps.
First off, make your model/class conform to <NSCoding, NSSecureCoding>
Example:
QTPerson.h
#import <Foundation/Foundation.h>
#class QTPerson;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Object interfaces
#interface QTPerson : NSObject <NSCoding, NSSecureCoding>
#property (nonatomic, copy) NSString *text;
#end
NS_ASSUME_NONNULL_END
And then implement the protocol methods:
QTPerson.m
#import "QTPerson.h"
#implementation QTPerson
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_text forKey:#"text"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_text = [coder decodeObjectOfClass:[NSString class] forKey:#"text"];
}
return self;
}
#end
And then when archiving an object, you would want to pass YES to the parameter requiringSecureCoding, like so:
QTPerson *person = [[QTPerson alloc] init];
person.text = #"Glenn";
NSData *codedData1 = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setValue:codedData1 forKey:#"boom"];
Lastly, when unarchiving, just do what you did correctly, like so:
NSData *codedData = [[NSUserDefaults standardUserDefaults] dataForKey:#"boom"];
NSError *er;
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[QTPerson class] fromData:codedData error:&er];
if([unarchivedObject isKindOfClass:[QTPerson class]]){
NSLog(#"TRUE!");
} else {
NSLog(#"FALSE!");
}
Voila! You'll get nonnull object unarchivedObject, hence the TRUE/YES value you're looking for!
Ok, strange thing occurred and I guess answer is quite simple, but I fail to figure out what's going on.
Situation is next:
I have an NSObject class called Constants.
Constants.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <GooglePlus/GooglePlus.h>
#interface Constants : NSObject
+(Constants*)shared;
#property GTLPlusPerson* googlePlusUser;
#property int profileType;
#property NSString *userName, *userLastName, *userEmail, *userGoogleId,*userProfilePicture;
#end
Constants.m
#import "Constants.h"
#implementation Constants
#synthesize profileType, userProfilePicture, userLastName,userName,userGoogleId,userEmail;
static Constants *constants = nil;
+ (Constants*)shared {
if (nil == constants) {
constants = [[Constants alloc] init];
}
return constants;
}
I use this class in order to save some static variables that I will use throughout the app.
Now, If I try and declare one of the variables like
[Constants shared].userName = #"name";
from an NSObject class method (Which I call from a ViewController), I fail to do so.
But If I declare Constant variables directly from ViewController (after viewDidLoad for example) everything works fine.
Here is the Class I try to declare variables from, but I fail (It also has singleton in it, that might be the source of the problem, but im not sure why would it)
#implementation GoogleLogin
static GoogleLogin* gLogin = nil;
+(GoogleLogin*)shared
{
if (nil == gLogin){
gLogin = [[[self class]alloc]init];
}
return gLogin;
}
-(void)getProfile
{
GTLServicePlus* plusService = [[GTLServicePlus alloc] init];
plusService.retryEnabled = YES;
[plusService setAuthorizer:[GPPSignIn sharedInstance].authentication];
GTLQueryPlus *query = [GTLQueryPlus queryForPeopleGetWithUserId:#"me"];
plusService.apiVersion=#"v1";
[plusService executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLPlusPerson *person,
NSError *error) {
if (error){
NSLog(#"Error while fetching user profile: %#", error);
}else{
NSLog(#"User profile information fetched OK");
[Constants shared].googlePlusUser = person;
[Constants shared].profileType = 1;
[Constants shared].userName = person.name.givenName;
[Constants shared].userLastName = person.name.familyName;
[Constants shared].userEmail = [GPPSignIn sharedInstance].authentication.userEmail;
[Constants shared].userGoogleId = person.identifier;
[Constants shared].userProfilePicture = person.image.url;
NSLog(#"%# %# %# %# %# ",person.name.givenName,person.name.familyName,[GPPSignIn sharedInstance].authentication.userEmail,person.identifier,person.image.url);
}
}];
}
and this is how I call those methods, from my ViewController:
- (IBAction)signupWithGoogle:(UIButton *)sender {
//if i call this method here, on button click, it will finish all the steps needed, except setting constant variables
[[GoogleLogin shared] googleLoginFromViewController:self];
//if I uncomment next line, username will be declared and I will be able to access it later
//[Constants shared].userName = #"Petar";
}
Can anybody figure out why is this happening and what should I do to change that?
When you define a property is strongly suggested to declare the attributes to use with it. I guess the compiler should complain about this with a message like
No 'assign', 'retain', or 'copy' attribute is specified - 'assign' is
assumed
So, use the following instead (copy semantics is fine for mutable classes).
#property (nonatomic, copy) NSString *myString;
You should also specify if the property should be accessed in a atomic or nonatomic way. If you don't specify it, the former will be applied.
Then, you are using a singleton pattern. The suggested way is to use GCD like so.
+ (ConstantsManager*)sharedManager {
static ConstantsManager *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[[self class] alloc] init];
});
return sharedManager;
}
Well you did not set your property attributes on the singleton class.
For example,
#property (nonatomic, strong, readonly) ...
Have you tried moving the property assignments out of the completionHandler? It may be that your properties are being assigned on a background thread and your view controller is not catching the assignment. An easy way to check is to override the setters and getters and put breakpoints in them to see what order they are being accessed.
1) Remove the #synthesize because it's not needed (properties will be synthesized as _property automatically)
2) Override setter & getter
-(void)setProfileType:(NSInteger)profileType {
_profileType = profileType;
}
-(NSInteger)profileType {
return _profileType;
}
3) Place breakpoints within these methods and see if the getter is being called before the setter. Alternatively, if simply moving the assignments out of the completionHandler fixes it you know you have some concurrency issues.
I suggest reading up on atomic/nonatomic properties, #synthesize and Objective-C concurrency.
I have a framework A which is ARC. This consumes some API's from non ARC framework B.
Framework B sample code (non ARC):
#interface Settings : NSObject {
NSDictionary* settings;
}
- (NSString*)stringForKey:(NSString*)key;
#end
#implementation Settings
- (NSString*)stringForKey:(NSString*)key {
return [settings objectForKey: key];
}
#end
Framework A sample code (ARC):
{
// ...
SEL stringForKeySelector = #selector(stringForKey:);
NSMethodSignature *stringForKeySignature = [[Settings class] instanceMethodSignatureForSelector:stringForKeySelector];
NSInvocation *stringForKeyInvocation = [NSInvocation invocationWithMethodSignature:stringForKeySignature];
[stringForKeyInvocation setSelector:stringForKeySelector];
[stringForKeyInvocation setTarget:SettingsObject];
NSString *Key = #"KEY";
[stringForKeyInvocation setArgument:& Key atIndex:2];
[stringForKeyInvocation invoke];
NSString *value = nil;
[stringForKeyInvocation getReturnValue:& value];
// ...
}
Object from settings dictionary gets released after executing above code in framework A.
Any help is appreciated in advance.
Answer:
Thanks Chuck for pointing out problems with NSInvocation and ARC.
I got around this problem by returning basic data types.
This has nothing to do with memory management between ARC and non-ARC code. Instead, this is because NSInvocation does not work very cleanly with ARC. It's usually preferable to avoid NSInvocation anyway, and doubly so with ARC. There is usually a better choice for anything you'd use NSInvocation for. If you must use NSInvocation, you'll need to remember that it just deals with raw byte blobs and doesn't handle object ownership at all, so passing an ARC-managed pointer is not kosher. You'll want to have the return-value variable be a void pointer, and then use a bridging cast to assign it to a normal object-type variable.
I am trying to use BNHtmlPdfKit to save some HTML as a PDF. Just to see if it works, I'm trying to take a web page and write it to a PDF. I'm unable to get it to work (at all). Below is my code.
First, I include the delegate reference:
#interface PPToolsTableViewController () <BNHtmlPdfKitDelegate>
Then I do the following:
NSString *exportsPath = [[PPHelpers documentsPath] stringByAppendingPathComponent:[NSString stringWithFormat:#"exports/Exported.pdf"]];
BNHtmlPdfKit *htmlPdfKit = [[BNHtmlPdfKit alloc] init];
htmlPdfKit.delegate = self;
[htmlPdfKit saveUrlAsPdf:[NSURL URLWithString:#"http://google.com"] toFile:exportsPath];
Nothing happens. No errors, and none of the delegate methods fire:
- (void) createPdf:(id)sender {
NSLog(#"Create PDF");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didSavePdfData:(NSData *)data {
NSLog(#"PDF Save Data");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didSavePdfFile:(NSString *)file {
NSLog(#"PDF Save File");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didFailWithError:(NSError *)error {
NSLog(#"PDF Error");
}
Is anyone familiar with this library able to provide me with a working example? Or perhaps spot what's wrong with what I'm doing here? Thanks in advance.
I finally found this little snippet in the documentation which led me to a solution:
Be sure to retain a reference to the BNHtmlPdfKit object outside the
scope of the calling method. Otherwise, no delegate methods will be
called...
So making the BNHtmlPdfKit object a #property made all the difference:
#property (strong, nonatomic) BNHtmlPdfKit *htmlPdfKit;
...then this worked:
NSString *exportsPath = [[PPHelpers documentsPath] stringByAppendingPathComponent:[NSString stringWithFormat:#"exports/Logbook.pdf"]];
self.htmlPdfKit = [[BNHtmlPdfKit alloc] init];
self.htmlPdfKit.delegate = self;
[self.htmlPdfKit saveUrlAsPdf:[NSURL URLWithString:#"http://google.com"] toFile:exportsPath];
All better now. :)
I would like to do the following:
In a class (shared instance) I will have a method that takes as parameters a data object (nsstring) and a delegate. This will create a new background thread and dispatch some calculations on that thread. The thing is that the method may be called hundreds of times with different data and possibly different delegates passed in . I would like the results to go to the correct delegate (I will need to keep the delegates in an array right? or can I just pass them to the background thread as they come and when that thread finishes it will send the result only to that delegate?).
One more thing... all this methods will use a very large data structure (an array with 10000 nsstring objects,they only need to read from it). How do I make sure this is not duplicated on each thread? And is only allocated when needed and deallocated when no thread uses it?
Here is the code I decided to use:
if (!self.dictPasswords) {
// read everything from text
NSString* fileContents = [NSString stringWithContentsOfFile:fileRoot
encoding:NSUTF8StringEncoding error:nil];
// separate by new line
self.dictPasswords = [fileContents componentsSeparatedByCharactersInSet:
[NSCharacterSet newlineCharacterSet]];
}
__weak id<PSPasswordDictionaryVerificationDelegate> wdelegate = delegate;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[wdelegate willBeginPasswordVerificationForPassword:password];
for (NSString *posiblePass in self.dictPasswords) {
if ([password isEqualToString:posiblePass]) {
dispatch_async(dispatch_get_main_queue(), ^{
[wdelegate password:password isInDictionary:YES];
});
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[wdelegate password:password isInDictionary:NO];
});
});
However... after this runs I get a permanent 24MB added to the used memory. I would like to detect when no threads are using the self.DIctPasswords array and deallocate it. It will be read from the file again later if somebody calls this method again...
Thanks for the help guys.
Just let the block capture the delegate.
No need to hold it otherwise
Class
#import <Foundation/Foundation.h>
#protocol ProcessorDelegate;
#interface Processor
- (void)process:(id)data forDelegate:(id<ProcessorDelegate>)delegate;
+ (Processor*)sharedInstance;
#end
#protocol ProcessorDelegate
- (void)processor:(Processor*)processor didProcess:(id)data withResult:(id)result;
#end
#implementation Processor
- (void)process:(id)data forDelegate:(id<ProcessorDelegate>)delegate {
__weak id<ProcessorDelegate> wdelegate = delegate; //capture weak to counter potential cycles
__weak id wself = self;
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSLog(#"WORK");
id result = data; //TODO
[wdelegate processor:wself didProcess:data withResult:result];
});
}
+ (Processor*)sharedInstance {
static Processor *p = nil;
if(!p) {
p = [[Processor alloc] init];
}
return p;
}
#end
DEMO
#interface Demo : NSObject <ProcessorDelegate>
- (void)doIt;
#end
#implementation Demo
- (void)doIt {
[Processor sharedInstance] process:#"TEST" forDelegate:self];
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
Demo *d1 = [[Demo alloc] init];
Demo *d2 = [[Demo alloc] init];
Demo *d3 = [[Demo alloc] init];
Demo *d4 = [[Demo alloc] init];
[d1 doIt];
[d2 doIt];
[d3 doIt];
[d4 doIt];
[[NSRunLoop currentRunLoop] run];
}
}
It seems more appropriate to encapsulate the calculations plus data and delegate in a class of its own. Then you can have an array of those objects in your singleton. You may want to consider using NSOperation here.
OMT: Simply pass this large array as a pointer (to each calculation object) and use regular strong properties (not copy) if you're using any properties at all, saving a reference to it using an ivar is fine too. One concern is that this data-structure must be read-only; otherwise (when you'd modify it in each thread), you'd need some data locking.
I have done it with blocks : a singleton that have all the functions you needs (like an API) and a delegate class
// singleton.h
typedef void (^request_handler_t)(NSData* data);
- (void) foo:(NSString *)str withBlock:(request_handler_t)callback;
// singleton.m
- (void) foo:(NSString *)str withBlock:(request_handler_t)callback;{
MyDelegate *delegate = [MyDelegate delegateWithBlock:callback];
[yourMethodThatNeedDelegate:delegate];
}
// MyDelegate.h
+ (MyDelegate*) delegateWithBlock:(api_request_handler_t)block;
- (void)delegateMethod1;
//Delegate.m
+ (MyDelegate*) requestWithBlock:(api_request_handler_t)block;{
//... alloc init
_callback = block;
}
- (void)delegateMethod1;{
// delegate finished the job
block(myResultingData);
}
// Usage :
[MySingleton singleton] foo:(NSString *)str withBlock:^(NSData *data){
//do something with the async data
}];