How do you weak link frameworks when developing for the iPhone?
Once you've weak-linked a framework, how do you use classes and methods that may not be present on all OS versions?
What do you want to use weak Linking for?
A common use is to use a class defined only in a newer version of the SDK. The framework exists on older systems, but a class defined in the framework doesn't.
If this is the case then use NSClassFromString(). If it returns nil, the class doesn't exist, otherwise it will return the class object which can be used.
This is the recommended way according to Apple in this document:
Related
A few months ago I started to work on a MacOS Application which required CoreData implementation. Today I am beginning to work on a related iOS application that is based on the same Api, and though relies on the same model. I added my iOS target on my project and I mutualised some classes (by adding them to both targets), including the CoreData Stack:
I added my app.xcdatamodeld on both targets
I added my Object+CoreDataClass.swift & Object+CoreDataProperties.swift on both targets
I modified my ManagedObjectsController to support both iOS and MacOS implementation
by defining the appDelegate for both iOS and OSX, I can access it the same way to get my context let context = appDelegate.persistentContainer.viewContext
It works fine but I was wondering if I am doing this right. Is this the correct way to mutualise access to appDelegate instances between two targets?
Should I use some kind of Protocole & Generic Typing?
Or should I simply build a ManagedObjectController for each target?
Thanks
Declaring a protocol helps if you have multiple classes which you want to both support common functions. But in this case, UIApplication and NSApplication already support the common functions you need! The problem is that you need access two different global symbols.
One alternative worth considering is: Instead of declaring two classes IosAppDelegate and MacAppDelegate, declare a single class AppDelegate, and move that dirty #if code out of your ManagedObjectsController class and into AppDelegate. Then, this AppDelegate could be used wherever you need a reference to the shared app delegate. This is more than a few places in most projects.
But if you want to get your product out the door asap, and this ManagedObjectsController is the only place you need the shared app delegate, your code is fine.
I just created a new target for the Lite version of my app. The Lite app only uses part of a base class that I have in the main app, ie it won't need to use an option that requires it to import 4 or 5 files.
My question is, from a design perspective, what is the best way to handle this so that my Lite version can only use the part of the class that it needs? Obviously, one solution is I just import those 4 unnecessary files into Lite build phase, and just use the whole class (even the parts it doesn't need). This seems inefficient though. I know I can do an ifndef to block those files from being imported if the Lite version is running, but how do I block out the code in the class from also not being picked up by the compiler?
Would a better way just be to have my Lite version create a subclass of the Base class that only uses the options it needs? But then I believe, would I still need to import those unnecessary files?
Just a bit confused about this, first time I've ever created another target that utilizes code from the main target. Any help appreciate thanks.
Put the common/lite functionality in a super class. Heavy functionality in the sub-class.
As another answer points out, you can handle this by putting the lite functionality in a subclass and the full functionality in a superclass.
Another option is to use a single class, and add the full functionality in an Objective-C category. Essentially, you can define methods in the category to supplement – or replace – methods in the base implementation.
Unlike a subclass, however, methods defined in a category can't invoke super to get the base class's functionality. super still refers to the base class's superclass, whether that's NSObject, UIDocument, or what have you – not the implementation without the category.
The advantage is that you only have one class name, so the code which instantiates your class (or classes) doesn't need to use something like #ifdef to switch classes and #includes depending on whether you're building the lite or full version.
[edited] I edited the question to isolate the problem and help other people better.
I'm using NSMutableAttributedString class in my app, which is available in iOS 3.2 and later. I'm also targeting 3.1.2-version devices though; for the backward compatibility, I used the following code:
CFAttributedStringRef attributedString;
if (NSClassFromString(#"NSMutableAttributedString")) {
attributedString = (CFAttributedStringRef)[[[NSMutableAttributedString alloc]
/* init... to initialize an object */ ] autorelease];
} else {
attributedString = CFAttributedStringCreate(kCFAllocatorDefault,
(CFStringRef)NSLocalizedString(#"MessageInEllipse",
#"Message to show in an ellipse"),
(CFDictionaryRef)attributes);
}
}
In line 3, I directly use the class name NSMutableAttributedString, but I expected this to be weakly linked by the linker, so it merely means nil here and the app would work without problems.
However, my app crashes on 3.1.2 devices when it launches, complaining that it can't find symbol NSMutableAttributedString. It seems like this class symbol is strongly linked. Why would this happen?
You need to change the framework linking configuration to "weak" link to the framework you are testing in the code.
Weak linking to a specific class is not available in all cases. In order to weakly link a class symbol,
The base SDK must be iOS 4.2 or newer.
The deployment target must be iOS 3.1 or newer.
The compiler must be the LLVM-GCC 4.2 or newer, or LLVM-Clang 1.5 or newer.
The class to which you want to weakly link must be declared using NS_CLASS_AVAILABLE macro.
The framework that the class belongs to must exist in the version for deployment, and if otherwise the framework itself must be weakly linked.
The third condition was my problem because I wrongfully thought I was using LLVM (I only found this with a help in the Apple's forum). GCC is the Xcode 3 default, so you must be careful.
If these condition doesn't hold, you cannot use weak linking. In this case, instead of using [NSMutableAttributedString alloc], for example, I should do like [NSClassFromString(#"NSMutableAttributedString") alloc].
There's one thing left to mention. As in #sza's answer, if I weakly link against the framework itself (Foundation in this case), I can use weak linking to the missing class even with GCC 4.2. Although it can solve the problem right away, in my opinion, it seems like a practice that should be avoided. I'm cautious in this because I'm not sure how weak linking to a framework does work in runtime, but wouldn't it impose more performance overhead than strongly linking to a framework, because all the information about the framework need to be acquired in runtime? Therefore, if I weakly link against a framework that is frequently used (sure does Foundation), I guess I could have a performance problem. At least, the references are very specific to say weakly link against a framework if that framework is not available for some of your deployment targets.
Therefore, I think the best practice here is:
always strongly link against frameworks that are available in my deployment target
and if I'm using a class of the framework that becomes available after the deployment target,
use weak linking if I can meet the requirements, OR
always use NSClassFromString() to refer to the class, no matter it would be executed or not in the older versions of iOS.
Is it possible to build a static library, called, say, libA that:
Contains code that calls upon classes from libB (not created by me, so I can't modify or access it);
Will compile when put into an app that doesn't link to libB (naturally, it will crash if the code using libB's classes is called without libB, but I can get around that. I just need the app that links with libA but not libB to compile.)?
Short answer, no.
Static references need to be resolved at link time - that's what static linking means.
What you could do is to build a dynamic library that statically links libB, and then dynamically link it from your application. If you prefer static linking, you could build a static library that does the dynamic linking to your dynamic-linked library.
There is a way, but it's not portable because it relies on features that are not common to all object code formats. It may also rely on knowing the name mangling rules of your compiler. Consider your other options before deciding to do this.
You can include in your library an undefined weak symbol for every libB symbol you reference. If your code is not linked against libB in the final linking step, every undefined weak symbol will be NULL. Otherwise, the undefined weak symbols will be superseded by the symbols from libB. If you're using GCC, you can use __attribute__((weak)) on symbol declarations.
How do I write a program for iPhone (Objective C++) that runs on OS 2.0 but takes advantage of 3.0 features if they're available?
Example: copy&paste (class UIPasteboard). Nice feature to have, but I don't want to kill backward compatibility. Do I compile against SDK v. 3 or v. 2? If the latter, how do I create an instance of UIPasteboard, considering it's not declared in the headers? If the former, won't some C-linkage functions cause "unresolved reference" upon loading under OS 2.0?
Edit your Target's build settings like this:
Set the Base SDK to the version whose APIs you want to use (e.g. 3.0 or 3.1).
Set the Deployment Target to the lowest OS version you want to support (e.g. 2.2.1).
When building, you compile against the Base SDK. For all symbols that defined in your Base SDK but not available in your Deployment Target, you need to add runtime checks to your code to check for their availability. Examples:
Check if UIPasteboard is available:
Class PasteboardClass = NSClassFromString(#"UIPasteboard");
if (PasteboardClass != nil) {
// UIPasteboard is available
}
Check if a specific method is available:
if ([UITableViewCell respondsToSelector:#selector(initWithStyle:reuseIdentifier:)]) {
// ...
}
I didn't try this but i would recommend building against the most recent (3.x) SDK release. So you get any class and method definitions that might be available on the target device.
And in your application you have to check the OS release your application runs on. Based on the target OS you have to decide which Class and Method you should use. After all it is a big mess of conditional code, probably with a lot of additional code to provide missing functionality (i.e. direct access to SQLite instead of using Core-Data). In my experience that should not lead to problems, because most type information is erased at runtime.