I implementing custom subclass of the NSURLProtocol. I need to store my NSURLProtocol instance inside NSMutableURLRequest. Since [NSURLProtocol setProperty:forKey:inRequest:] raises warning Invalid protocol-property list if you try to store non-plist-serializable object, I do it like this:
- (void)startLoading {
...
// when I need to store an NSURLProtocol subclass
[NSURLProtocol setProperty:[NSNumber numberWithLongLong:(long long)self] forKey:#"WebProxyURLProtocol" inRequest:mutableRequest];
...
}
and
// when I need to get an NSURLProtocol subclass back in NSURLSessionDelegate
- (NSURLProtocol *)protocolForTask:(NSURLSessionTask *)task {
NSNumber *number = [NSURLProtocol propertyForKey:#"WebProxyURLProtocol" inRequest:task.originalRequest];
return (__bridge NSURLProtocol *)(void *)number.longLongValue;
}
This works quite well. But is it safe and correct way to solve my problem, or I sometimes can get already deallocated object? Thanks!
Related
I'm trying to call a method from a class that uses a singleton pattern on a different class. I'm using a tutorial that doesn't use a singleton that works great, but I can't seem to implement what I need. I keep getting an error that says Expected identifier. I'm assuming this is probably due to syntax, but I can't seem to resolve it. The POI is an NSManagedObject subclass from core data.
This is my version using a singleton. The error points at the bracket before DataSource.
NSArray *itemGroups = [POI [DataSource sharedInstance] fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];
Here is the equivalent (from the tutorial) without a singleton
NSArray *itemGroups = [POI fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];
If you want be able to use POI class in this way:
NSArray *itemGroups = [POI fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];
then I will suggest to add such code:
// POI.h
+ (NSArray *)fetchDistinctItemGroupsInManagedObjectContext:(NSManagedObjectContext *)context;
// POI.m
+ (NSArray *)fetchDistinctItemGroupsInManagedObjectContext:(NSManagedObjectContext *)context {
return [[DataSource sharedInstance] fetchDistinctItemGroupsInManagedObjectContext: context];
}
You should have somethig like: [[POI sharedInstance] fetchDistinctItemGroupsInManagedObjectContext:managedObjectContext];
I have a question about passing data in iOS/Objective-C. I just started creating an app that connects and fetches data from a website, and now I'm having a problem.
My default and root view controller is called ViewController. It has the basic login UI views: 2 textfields and a button.
When the button has been clicked/touched, the ViewController calls a method from another class called LoginService. Now LoginService handles the connection to the website. I have no problem connecting to the website and fetching data from it, but I have a problem returning the fetched data, now processed as an NSDictionary, to the ViewController.
The first thing I tried was to create a setter method in the ViewController that sets the instance variable userProfile to the NSDictionary passed into it. It failed, however. I tried using it in the NSURLConnectionDataDelegate method connectionDidFinishLoading from the LoginService.
This might be a silly question, but I have no idea how can I pass the fetched NSDictionary from LoginService to the ViewController after the button is clicked. Do I need blocks, queue, or something else? I mean, for example, I need to set a label below my login button to the name of the user who logged in. How can I perform this?
Hope someone can help me. I'd greatly appreciate it.
As danh has explained blocks pattern for doing this, I will try to explain the delegating pattern. The steps for making this work:
In LoginService.h
Create a protocol definition in your LoginService like this:
#protocol LoginServiceDelegate
-(void)applicationLoggedIn:(NSMutableDictionary*) responseData;
#end
Now add a member pointer holding this delegate and add a property for this
#interface LoginService {
id<LoginServiceDelegate>delegate;
}
#property (nonatomic, assign) id <LoginServiceDelegate> delegate;
In LoginService.m
Once you got the response for login in connectionDidFinishLoading, just invoke the delegate method like below:
if ([delegate respondsToSelector:#selector(applicationLoggedIn:)]) {
[delegate applicationLoggedIn:responseDict];
}
In LoginViewController.h
Now to use this in your LoginViewController, you need to implement this protocol
#import "LoginService.h"
#interface LoginViewController<LoginServiceDelegate>
In LoginViewController.m
Assign the delegate of LoginService to LoginViewController
LoginService* loginService = [[LoginService alloc]init];
loginService.delegate = self;
Implement the protocol method as:
-(void)applicationLoggedIn:(NSDictionary*)response{
}
Hope this helps.
Two patterns to consider: delegate and block. Block is quicker to code, and I usually prefer it to delegate for network ops. To use a block, write the login service this way:
// LoginService.h
- (void)login:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion;
It sounds like you're using NSURLConnection delegate pattern here, so I will assume that. Please realize that NSURLConnection also provides a nice one-shot block method to do the request.
// LoginService.m
#property (copy, nonatomic) void (^completion)(NSDictionary *, NSError *);
- (void)login:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion {
// copy the block when the request begins
self.completion = completion;
// start your request, as you have it now
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSDictionary *dictionary = // parse the data you collected into a dictionary
// invoke the block with the result
self.completion(dictionary, nil);
self.completion = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
self.completion(nil, error);
self.completion = nil;
}
It's polite to dispose of the block (set it to nil) after you invoke it, so it doesn't retain any part of the calling context.
Basically you need ViewController to have a public method which LoginService can call when it's done its job, and NSDictionary will be a parameter to this method. LoginService will need a reference back to ViewController in order to invoke this method, so define a public property on LoginService which will hold a ViewController reference - and set this right after instantiating LoginService.
Of course, if you want LoginService to be more reusable, and not tied to ViewController specifically, delegates are the way to go. LoginService would define the LoginServiceDelegate protocol with the method to be called on completion. ViewController would then implement the LoginServiceDelegate protocol. The public property on LoginService becomes a LoginServiceDelegate reference, and so LoginService no longer needs to import ViewController. This way, ViewController is dependent on LoginService, but LoginService is not dependent on ViewController.
I need to transfer a single object across device. Right now I am converting my NSManagedObject to a dictionary , archiving it and sending as NSData. Upon receiving I am unarchiving it. But I would really like to transfer the NSManagedObject itself by archiving and unarchiving instead of creating an intermediate data object.
#interface Test : NSManagedObject<NSCoding>
#property (nonatomic, retain) NSString * title;
#end
#implementation Test
#dynamic title;
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
self.title = [coder decodeObjectForKey:#"title"]; //<CRASH
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.title forKey:#"title"];
}
#end
NSData *archivedObjects = [NSKeyedArchiver archivedDataWithRootObject:testObj];
NSData *objectsData = archivedObjects;
if ([objectsData length] > 0) {
NSArray *objects = [NSKeyedUnarchiver unarchiveObjectWithData:objectsData];
}
The problem with the above code is. It crashes at self.title in initWithCoder saying unrecognized selector sent to instance.
Why is title not being recognized as a selector.
Should unarchive use a nil managed object context somehow before creating the object in initWithCoder?
Do i need to override copyWithZone?
This snippet below should do the trick. The main difference is to call super initWithEntity:insertIntoManagedObjectContext:
- (id)initWithCoder:(NSCoder *)aDecoder {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Test" inManagedObjectContext:<YourContext>];
self = [super initWithEntity:entity insertIntoManagedObjectContext:nil];
NSArray * attributeNameArray = [[NSArray alloc] initWithArray:self.entity.attributesByName.allKeys];
for (NSString * attributeName in attributeNameArray) {
[self setValue:[aDecoder decodeObjectForKey:attributeName] forKey:attributeName];
}
return self;
}
Above snippet will handle only the attributes, no relationships. Dealing with relationships as NSManagedObjectID using NSCoding is horrible. If you do need to bring relationships across consider introducing an extra attribute to match the two (or many) entities when decoding.
how to obtain <YourContext>
(based on a now unavailable post by Sam Soffes, code taken from https://gist.github.com/soffes/317794#file-ssmanagedobject-m)
+ (NSManagedObjectContext *)mainContext {
AppDelegate *appDelegate = [AppDelegate sharedAppDelegate];
return [appDelegate managedObjectContext];
}
Note: replace <YourContext> in the first snippet with mainContext
Obviously NSManagedObject does not conform to NSCoding. You could try to make a custom managed object subclass conform, but it would be a dicey proposition at best. An NSManagedObject must have a related NSManagedObjectID. And, you don't get to assign the object ID-- that happens automatically when the object is created. Even if you made your subclass conform to NSCoding, you'd have to find a way to unarchive the object while also allowing the local managed object context to assign an object ID.
And even that ignores the question of how you'd handle relationships on your managed objects.
Converting to/from an NSDictionary is really a much better approach. But you can't just unarchive the data and be finished. On the receiving end, you need to create a new managed object instance and set its attribute values from the dictionary. It might be possible to get your approach to work, but by the time you're done it will be more work and more code than if you just used an NSDictionary.
Seriously: NSCoding, initWithCoder:, copyWithZone:, etc, are a really bad idea for the problem you're trying to solve. NSCoding is nice for many situations but it's not appropriate here.
The problem is obviously the unarchiver. In the end there is no way to use both initWithEntity: and initWithCoder: in the same object. However, I suspect that with some trickery you may be able to make this work. For instance, implement initWithCoder: as you have done, and in that create another managed object with initWithEntity: (this means you will need unmanaged ivars that can hold such a reference. Implement forwardingTargetForSelector:, and if the object is the one being created using initWithCoder:, forward it to the shadow object you created with initWithEntity: (otherwise, forward that selector to super). When the object is decoded fully, then ask it for the real managed object, and you're done.
NOTE: I have not done this but have had great success with forwardingTargetForSelector:.
I am accessing a dispatched notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleUnpresent:) name:UNPRESENT_VIEW object:nil];
...
-(void)handleUnpresent:(NSNotification *)note;
{
NSLog(#"%#", note.object.footer);
//property 'footer' not found on object of type 'id'
}
Some of the incoming note.object objects have a "footer" and some don't. However, I don't want to go through to trouble of making a class that only has a property called footer just to make this work. I even tried ((NSObject *)note.object).footer) which works in some languages, but apparently not obj-c. What can I do?
Checking the isKindOfClass is certainly the more robust option. However, if you have multiple unrelated classes that return the property you need, there is another way: respondsToSelector. Just ask if the object has a footer method, and you can safely call it.
-(void)handleUnpresent:(NSNotification *)note;
{
id noteObject = [note object];
if ([note respondsToSelector:#selector(footer)])
{
NSLog(#"Footer = %#", [noteObject footer]);
}
}
That respondsToSelector method is powerful and handy in the right places, but don't go wild with it. Also, it can't tell you anything about the return type, so the footer you get may not be of the class you were expecting.
The syntax for noteObject.footer and [noteObject footer] are easy to treat as equivalent. However, when the class of noteObject is unknown, the compiler will accept the latter but not the former. If noteObject has a defined class that doesn't usually respond to footer, it will give a warning, but still compile and run. In these cases, it is your responsibility to guarantee that the method will indeed exist when needed, and therefore that the method call won't crash at run time.
If the object passed in the notification may be one of a number of classes and you don't want to cast the object to a specific class you can use performSelector: to call the footer method on the object. If you wrap this call with a respondsToSelector: you'll avoid an exception if the object turns out not to have a footer method.
-(void)handleUnpresent:(NSNotification *)note;
{
if ([[note object] respondsToSelector:#selector(footer)]) {
NSString *footer = [[note object] performSelector:#selector(footer)];
NSLog(#"%#", footer);
}
}
Using performSelector will stop the compiler complaining that the method "'footer' not found on object of type 'id'."
NSObject doesn't have any property named footer, which is why the compiler is complaining. Casting an id back to an NSObject doesn't help. If you know the object is always going to be some custom object you've created, you can cast back to that and then call footer and the compiler won't complain. It's best to actually check tho. See the example below (for the example, I named the class that has the footer property ViewWithFooter, so rename appropriately):
- (void)handleUnpresent:(NSNotification*)note
{
ViewWithFooter view = (ViewWithFooter*)[note object];
NSParameterAssert([view isKindOfClass:[ViewWithFooter class]]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}
If you have a bunch of unrelated classes (i.e., not in the same class hierarchy) that all present a footer property, you'd be best served creating a protocol with the required footer property and casting the object to the protocol in the code example above and asserting it responds to the -footer selector.
Here's an example using the protocol:
#protocol ViewWithFooter <NSObject>
- (UIView*)footer; // this could also be a readonly property, or whatever
#end
- (void)handleUnpresent:(NSNotification*)note
{
id<ViewWithFooter> view = (id<ViewWithFooter>)[note object];
NSParameterAssert([view respondsToSelector:#selector(footer)]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}
I have an noob problem and I would like you yo point me in the right direction. Basicly I have a custom class which implements the copying protocol. However when I save the class during execution the custom class i released and I get a bad access. I can see in instruments that the retain count is -2. I save the custom class with the following method:
-(void)storeDataInFile:(NSString*)dataFileName DataArray:(NSArray*)dataToStore
{
//Get the path
NSString *path = [self pathToDocumentsForDataFile:dataFileName];
//Archive the file
[NSKeyedArchiver archiveRootObject:dataToStore toFile:path];
}
Is I use the method sor saving a array with strings it works flawless. What should I look deeper into regarding my custom class?
Regards
I soved this issue, however I only provided the solution in a comment which apprantly has been deleted. So I just wanted to post the answer which indicates it was a noob mistake.
From an eralier test implementation I had the following method in the class
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
//retain is counted up
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX;
}
These methods ruined my retain count :)