weak vs strong pointers behaving slightly differently in xcode 5 - ios

i have some code that was working happily in xcode 4.6. since i upgraded to xcode 5 one section was not working. it's not erroring. very oddly the exact same code with no modifications DOES work in the simulators, but does NOT work on devices. if i compile the same coe in xcode 4.6 it DOES work on devices as well as simulators.
it uses tony millions reachability class.
i have tracked down the issue but becasue itworks on simulators in xcode 5, i dont understand.
basically on a button click i check the reachability.
i have a weak pointer to a reachability object, which i setup on the button click - snippet below
#interface settingsViewController ()
#property (weak,nonatomic) Reachability *reachable;
#end
....
//called on click
/ Checks if we have an internet connection or not
- (void)testInternetConnection
{
self.reachable= [Reachability reachabilityWithHostname:#"www.google.com"];
if (self.reachable)
{
NSLog(#"reachability created");
}
else
{
NSLog(#"NO OBJECT");
}
//do more stuff.....
}
basically without changing ANYTHING in the simulators the debug console prints "reachability created" but on ANY device (iPhone/ipad, IOS6/IOS7) the debug console prints "NO OBJECT"
tony milions code to create the object using reachabilityWithHostname is below
#pragma mark - class constructor methods
+(Reachability*)reachabilityWithHostname:(NSString*)hostname
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
NSLog(#"init1");
if (ref)
{
NSLog(#"init2");
id reachability = [[self alloc] initWithReachabilityRef:ref];
#if __has_feature(objc_arc)
NSLog(#"init with arc");
return reachability;
#else
NSLog(#"init no arc");
return [reachability autorelease];
#endif
}
NSLog(#"cannot init");
return nil;
}
and on both the simulator and device the debug console prints "init with arc" as expected.
so basically it creates the object OK, but as soon as i test it on the simulator it is valid, but on the device it is null. that bit i dont understand.
BUT if i change the reachability object to be a strong pointer, the simulator continues to work AND the device works as well - in that when i test the reachability object, it is set and the debug console prints "reachability created"
i dont understand how the object can be null as soon as it's created ONLY on the device with a weak pointer... surely
a) the simulator and device should behave the same
b) i have only just created the object the line before - how has it become null on the device if i'm using weak pointer?/
thanks in advance!

The code you posted should never work.
The fact that it does work on simulators is an artifact of the imperfect nature of the simulator, not a reflection that it SHOULD work.
The compiler should be giving you a warning that creating an object and saving it to a weak pointer, it will be released immediately. That's how ARC works.
Use a local strong variable. As soon as the strong variable goes out of scope, it will be up to the caller to decide if it should retain a strong reference to the object.

Related

Creating an app with deployment target of iOS 7.1 and optional iOS 8-only features

I'm creating an app that works with CloudKit framework (iOS 8-only) but still want to keep compatibility with iOS 7.1, with no CloudKit functionality, of course. Apple documentation recommends checking for optional classes like this:
if ([CKRecordID class]) {
// iOS 8 code
} else {
// iOS 7 code
}
This works. On the other hand, if I write
#interface Foo : NSObject
#property (nonatomic) CKRecordID *recordID;
#end
anywhere in the code, the app will crash on iOS 7 when loading the Foo class. How can I define properties with those optional classes?
You could use the forward declaration
#class CKRecordID;
but you will need runtime checks for the iOS version, such as
[[NSProcessInfo processInfo] operatingSystemVersion]
Other solutions for detecting the iOS version are shown here or here.
But how about two different builds for different iOS versions?
You can make your property recordId of type id or NSObject.
And when you need to access this property (after checking that your runtime is iOS8+), you cast it to CKRecordID class.

How to intercept adding calls to call history in iOS?

I'm developing a tweak for jailbroken iPhones. I'm trying to intercept the process of a call being added to the call history. With a little bit search I found CTCallHistoryStoreAddCall function in CoreTelephony framework found here. When I try to use it I get an error:
Undefined symbols for architecture armv7: "_CTCallHistoryStoreAddCall"
I linked the CoreTelephony framework and the way I used it in my code was:
typedef struct __CTCall * CTCallRef;
extern "C" void CTCallHistoryStoreAddCall(CTCallRef call);
I guess that means this function does not exist anymore or if it does I'm not using it in the correct way.
How can I find the right function that is responsible for adding an incoming phone call to the call history?
Thanks in advanced.
I'm using iOSOpenDev on Xcode 5.
There is no such function. At least in iOS7.
I've posted solution for iOS7 here Hide a phone call completely in iOS (jailbreak device)
Here is the code:
//Private API from CoreTelephony.framework
void CTCallDeleteFromCallHistory(CTCallRef call);
%hook PHRecentCall
-(id)initWithCTCall:(CTCallRef)call
{
if (IsCallShouldBeDeleted(call) == YES)
{
//Delete call from call history
CTCallDeleteFromCallHistory(call);
//Update MobilePhone app UI
id PHRecentsViewController = [[[[[UIApplication sharedApplication] delegate] rootViewController] tabBarViewController] recentsViewController];
if ([PHRecentsViewController isViewLoaded])
{
[PHRecentsViewController resetCachedIndexes];
[PHRecentsViewController _reloadTableViewAndNavigationBar];
}
//Try uncommenting this, may be it will work. Should make the code faster.
//return nil;
}
return %orig;
}
%end
Tweak hooks class inside MobilePhone app so bundle filter is com.apple.mobilephone.
IsCallShouldBeDeleted is pseudo function that determines whether a call should be deleted. You can remove it or implement your own. It's there just to make the code more clear.
On iOS6 class names are different but code is exactly the same - Apple just renamed the classes. I use that solution since iOS4. Also on iOS4 it requires a bit more code as there was no CTCallDeleteFromCallHistory function.
You are encountering this error because the CoreTelephony framework is not being linked to your program. To fix this, add the following to your makefile:
PROJECT_NAME_PRIVATE_FRAMEWORKS = CoreTelephony
Note that you have to replace PROJECT_NAME with your own project's name.

EXC_BAD_ACCESS using ARC only during testing

I have an issue where I'm getting bad access exceptions but only when running a testing build (calling the same methods in a debug build doesn't cause the problem to come up). The project has ARC enabled and I'm running this on the iPad 5.1 simulator using Xcode 4.3:
Here's where the problem crops up:
- (void)testChangeFoodNotification {
Player* p = [[Player alloc] init];
[p addObserver:self forKeyPath:#"food" options:0 context:0]; // <-EXC_BAD_ACCESS (code=2)
p.food += 1;
STAssertTrue(_wasNotifiedOfFoodChange, nil);
}
At the point when the addObserver: method is called it doesn't seem like any of the objects involved should have been released so what could be causing the exception?
EDIT:
Apologies if it wasn't clear but the code above is being executed as part of a test case (using the standard Xcode OCUnit). Also in case it clarifies anything here's the relevant code from the player class (there's other ivars and methods but they don't have any connection to the property or methods being tested):
// Public interface
#interface Player : NSObject
#property (nonatomic, assign) NSInteger food;
#end
// Private interface
#interface Player() {
NSInteger _food;
}
#end
#implementation Player
#synthesize food = _food;
#pragma mark - Getters/Setters
- (void)setFood:(NSInteger)food {
[self willChangeValueForKey:#"food"];
_food = food;
[self didChangeValueForKey:#"food"];
}
If your class is indeed key-value compliant, ensure that the implementation for the class exhibiting the issue is not included in your test product. This means that the Target Membership panel of the Identity inspector for your .m file should only have your app checked (not YourAppTests).
I experienced the same issue in Xcode 4.3.1 when an implementation was included in both products and I registered observers in both production and test code. The following logs tipped me off:
Class YourClass is implemented in both /Users/yourUser/Library/Application Support/iPhone Simulator/5.1/Applications//YourApp.app/YourApp and /Users/yourUser/Library/Developer/Xcode/DerivedData/YourApp-/Build/Products/Debug-iphonesimulator/YourAppTests.octest/YourAppTests. One of the two will be used. Which one is undefined.
As per the Key-Value Observing Programming Guide, is your Player key-value-compliant? You want to make sure you are Ensuring KVC Compliance. I also assume that you have also implemented your observeValueForKeyPath:ofObject:change:context:? If you think you've done all of this and it's still not working, then perhaps you can share your code.
Also, minor thing, but I assume this is a code snippet to highlight the issue. I only mention it because ARC is going to be releasing your p object at the end of your testChangeFoodNotification and I would have thought that you'd want to remove your observer first.

UIViewController reported as responding to addChildViewController: on iOS 4

Has anyone else encountered this? The following code reports "YES" when running on the iOS 4 simulator but according to the Apple docs the method addChildViewController is only available on iOS 5 and later. This doesn't seem like the correct behavior, is this a bug?
if([UIViewController instancesRespondToSelector:#selector(addChildViewController:)]) {
NSLog(#"YES");
} else {
NSLog(#"NO");
}
I think this is a bug. Calling the addChildViewController seems to run without any warning or error too.
I wrote the following viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
MyChildView *aChildViewController = [[MyChildView alloc] initWithNibName:#"MyChildView" bundle:nil];
// Do any additional setup after loading the view, typically from a nib.
SEL mySelector = #selector(addChildViewController:);
if([UIViewController instancesRespondToSelector:mySelector] == YES) {
NSLog(#"YES addChildViewController:");
[self addChildViewController:aChildViewController];
} else {
NSLog(#"NO addChildViewController:");
}
if([UIViewController instancesRespondToSelector:#selector(automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers)] == YES) {
NSLog(#"YES automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers");
} else {
NSLog(#"NO automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers");
}
}
In the iOS 4.3 Simulator I see following output. Both messages are restricted to IOS 5.0 and higher. It appears addChildViewController is responding in the 4.3 simulator incorrectly. I don't have 4.3 device to test on an actual device.
2011-11-18 09:55:12.161 testViewFunctionality[873:b303] YES addChildViewController:
2011-11-18 09:55:12.162 testViewFunctionality[873:b303] NO automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
In the iOS 5.0 Simulator both respond which is correct behavior.
2011-11-18 09:59:31.250 testViewFunctionality[932:f803] YES addChildViewController:
2011-11-18 09:59:31.252 testViewFunctionality[932:f803] YES automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
I'm using XCode 4.2 on Lion. When I look through UIViewController.h on the 4.3 Simulator's framework there is no mention of addChildViewController: or automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers but the only SDK included is 5.0.
I suppose that if you wanted to be cautious you could test the running iOS version on the running device. See How to check iOS version?
Yes, that is a bug and it will never be fixed. As a workaround, instead of checking for the availability of addChildViewController: method, you can check for removeFromParentViewController method. The latter is not available before iOS 5.0.
It's quite possible this method existed in previous version of iOS, but it just wasn't public yet. Apple usually prepends private methods with an underscore but it has been known to do this kind of thing before.

Testing CoreLocation on iPhone Simulator

UPDATE: As of iOS 5 and Xcode 4.1 is is now possible to test location in the simulator and even define routes. See http://developer.apple.com for more details.
Legacy Question
Is there anyway to test CoreLocation on the iPhone Simulator?
All I require is to be able to set the location myself and have CoreLocation return it.
Here is my simple hack that forces the CLLocationMager to return
the geocoords of Powell's Tech Bookstore only on the simulator:
#ifdef TARGET_IPHONE_SIMULATOR
#interface CLLocationManager (Simulator)
#end
#implementation CLLocationManager (Simulator)
-(void)startUpdatingLocation {
CLLocation *powellsTech = [[[CLLocation alloc] initWithLatitude:45.523450 longitude:-122.678897] autorelease];
[self.delegate locationManager:self
didUpdateToLocation:powellsTech
fromLocation:powellsTech];
}
#end
#endif // TARGET_IPHONE_SIMULATOR
Thanks for the great feedback, it has prompted me to find a robust solution.
All the code can be found here:
http://code.google.com/p/dlocation/
It is very messy but as I use it it will be become much better.
The solution was to subclass CLLocationManager and define a new delegate #protocol, called DLocationManagerDelegate.
It is designed to be a simple drop-in replacement for CLLocationManagerDelegate that compiles down to a very thin layer when deployed on an actual device.
When running on the device it will return data as normal using CoreLocation, but in the simulator it will read latitude and longitude from a text file (defined in the DLocationManager.h file).
I hope this helps, the implementation is on the simple side and you have to startUpdatingLocation and stopUpdatingLocation to update the display.
Comments and feedback will be gratefully received.
Use a filtering function to swap in a test instance when running on the simulator. Wherever you previously received the location (delegate call, etc), pass it through this:
+ (CLLocation *) wakkawakka: (CLLocation*) loc {
#ifdef TARGET_IPHONE_SIMULATOR
/* replace with a test instance */
return [[CLLocation alloc] initWithLatitude:10.0 longitude:20.0];
#else
return loc;
#endif
}
Memory management issues aside...
I think there's another (better IMHO) approach here than subclassing CLLocationManager like in
http://code.google.com/p/dlocation/
In ObjectiveC it seems to be possible to replace an existing method from a class without overriding it. This is often called "method swizzling" : you define your own category for an existing class an implement an existing method in it.
From the client perspective, everything is transparent : he has the feeling he's dealing with the real CLLocationManager but actually, you "took the control from it". So that he doesn't need to deal with any special subclass or any special delegate protocol : he keeps on using the same class / protocol as the one from CoreLocation.
Here's an example to took the control over the delegate a client would inject :
#implementation CLLocationManager (simulator)
-(void) setDelegate:(id)delegate {
//your own implementation of the setDelegate...
}
-(id)delegate {
//your own implementation of the delegate....
}
-(void)startUpdatingLocation {
}
-(void)stopUpdatingLocation {
}
//....
//same for the rest of any method available in the standard CLLocationManager
#end
Then in this implementation, you're free to deal with a pre defined set of coordinates (coming from a file of whatever) that will be "pushed" to the delegate using the standard CLLocationManagerDelegate protocol.
Trigger the Core Location callbacks from a test class, if you need to set a location other than the one the simulator gives you.
the locationManager:didUpdateToLocation and locationManager:didFailedWithError overloaded callbacks are never called in the iphone simulator, that's kinda strange, all i get is 0.0000 for lat., and 0.0000 for lon. as the position. In the situation you develop something, that's kinda hard to implement all the possible situations that can occur during the location handling, using only simulator environment.
If you're interested in updating the blue userLocation dot in a MKMapView with the simulated location updates, check out my FTLocationSimulator at http://github.com/futuretap/FTLocationSimulator
It reads a KML file generated by Google Earth to provide continuous location updates.
Testing CoreLocation on iPhone Simulator
1) To test the location in simulator,best way is to use GPX files,just go to Files -> New -> Resource -> GPX File.
2) After Adding the GPX file update the location coordinates as desired.
3) once the GPX file is added to the project,Select the Scheme -> Edit Scheme -> Run -> Allow Location Simulation.tick the location simulation and select the name of the GPX file you just created.
this way simulator will always pick your desired coordinates,that we have added in our GPX File.

Resources