Writing library code to support both ARC and MRR - objective-c

I'm working on a library and I would like to support both memory management approaches (ARC and MRR) in one codebase.
I don't want to force users to use special flags for my code (-fobjc-arc).
I know about the preprocessor test: #if __has_feature(objc_arc), but what is the best practice to use that to cover the all differences?
Does anyone have any experience with that to make it clean and easy to work with?
The preferable way would be to use some macros for translations between ARC and non-ARC, which I can use in my code.
=========
My problem was solved by the accepted answer, but as a tip for others, I found a blog post by John Blanco giving the best set of examples for how to handle my problem.

Refer the code of MBProgressHUD in github. I think, that's what you want.
#if __has_feature(objc_arc)
#define MB_AUTORELEASE(exp) exp
#define MB_RELEASE(exp) exp
#define MB_RETAIN(exp) exp
#else
#define MB_AUTORELEASE(exp) [exp autorelease]
#define MB_RELEASE(exp) [exp release]
#define MB_RETAIN(exp) [exp retain]
#endif
This is how they are using these macros
self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]);

Either use ARC and instruct people who will use the code to set compilation flags per file (-fobjc-arc), and force them to do so by adding this to the header:
#if !__has_feature(objc_arc)
#error ARC must be enabled!
#endif
Or build as lib/framework with ARC enabled. Wrapping memory management code in preprocessor directives is a terrible idea.

One way to support both ARC and Non-ARC code is to go to the Target, Build Phases, and to the Compile Sources section.
From there you should see all your .m files. You can then add to any file -fno-objc-arc under the Compiler Flags to tell the compiler to ignore ARC.

Yeah, don't do this. You'll end up having to test your code fully twice for every change. And debugging everything twice. It isn't worth the effort.
You really really want to write your code purely ARC or purely non-ARC.
There are very few constructs that can appear in a header file that won't work in one or the other.

Related

Swift conditional compiling didn't work properly when I use macro defined in objective-c

I define a simple macro in objective-c header file, and import this header file into Swift through project bridging header. I was able to use this macro as a constant in Swift, but when I use it to do conditional compiling, it doesn't work properly.
I create a simple project in Xcode 10.2.1 and add some code to reproduce it.
In ViewController.h
#define TEST_FLAG 1
#interface ViewController : UIViewController
#end
In ViewController.m
#import "testMacro-Swift.h"
- (void)viewDidLoad {
[super viewDidLoad];
SwiftClass *s = [[SwiftClass alloc] init];
[s printMSG];
#if TEST_FLAG
NSLog(#"Objc works.");
#endif
}
In testMacro-Bridging-Header.h
#import "ViewController.h"
SwiftFile
#objc class SwiftClass: NSObject {
#objc func printMSG() {
print("Macro \(TEST_FLAG)")
#if TEST_FLAG
print("compiled XXXxXXXXX")
#endif
}
}
Console Output
Macro 1
2019-07-03 14:38:07.370231-0700 testMacro[71724:11911063] Objc works.
I expected compiled XXXxXXXXX to be printed after Macro 1, but it not.
I am curious why this will happen.
My project is mixed with objc and swift. I don't want to declare a same flag in swift.
Based on this Apple article, https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/using_imported_c_macros_in_swift, simple C (and Objective-C) macros are imported into Swift as global constants. This is demonstrated by the output from your line
print("Macro \(TEST_FLAG)")
The snippet
#if TEST_FLAG
print("compiled XXXxXXXXX")
#endif
uses a different TEST_FLAG, which is a Swift preprocessor flag. You could define it under Build Settings -> Active Compilation Conditions as TEST_FLAG or under Build Settings -> Other Swift Flags as -DTEST_FLAG.
The above explains why this happens. I can't think of a simple way to avoid defining the same flag separately for Objective-C and Swift preprocessor in Xcode. If you just want to control whether some Swift code is executed based on the TEST_FLAG, you can do something like this:
if TEST_FLAG != 0 {
print("compiled XXXxXXXXX")
}
However, if you want to control compilation of the code, then you may have to use separate TEST_FLAGs for Objective-C and Swift and make sure they are consistent. To aid in making them consistent, you could set the TEST_FLAG used by Objective-C code in Other C Flags, which allow you to define different flags for different SDKs, Architectures, and build types (Release/Debug). Active Compilation Conditions allow the same flexibility.
Another trick to facilitate consistency between (Objective-)C and Swift compiler flags is to create a new user-defined build setting: click on + to the left of the search box under Build Settings.
Say, call it COMMON_TEST_FLAG and set its value to TEST_FLAG. Then add -D$(COMMON_TEST_FLAG) to Other C Flags and Other Swift Flags. Now when you build your code TEST_FLAG will be defined in both Objective-C and Swift code within your target. If you don't want it to be defined, just change the value of COMMON_TEST_FLAG to something else. A couple of things to watch for, though:
You cannot make COMMON_TEST_FLAG empty: this will cause the other
flags to be just -D, leading to a build error.
Make sure the value of COMMON_TEST_FLAGdoes not conflict with
macros defined elsewhere.
Swift's preprocessor is (intentionally) waaaaaaay more limited than C's. Macros come with really serious draw backs.
They define regions of code that aren't active in every build target. Because of this, your tests won't be hitting every branch (or even compiling every branch). This quickly becomes a maintenance disaster. For n unique flags, there are 2^npossible sets of values and thus,2^n` possible builds. Do you have to test each of them? Maybe, maybe not, but even just reasoning about what to test isn't easy.
They can get tangled up and incredibly complex, especially with nested blocks.
In general, try to express as much of your code in the main programming language (C, ObjC, Swift), and resort to using macros only when there's a good reason, such as:
Reducing repetition in a way that can't be done with a function.
Improving performance in a critical region of code (by forcing inlining of macro code, e.g. #define max(a, b) ((a) < (b) ? (b) : (a))). Though this is exceptionally rare.
Expressing a piece of logic that can't be expressed in the language. For example, there's no way to express if #available(...) in Swift, without using the preprocessor.
Your example doesn't meet any of these criteria. It's increasing repetition, it's not performance-critical, and it's not doing something that can't be done in regular Swift code.
A much better approach to this (in both Swift and Objective C), is to create a logger that is initialized with different configurations between debug and release builds. Your viewDidLoad method should not concern itself whether or not TEST_FLAG is set. View controllers should control views, not make decisions as to what things should be logged. It should just call the logger to send off whatever log messages it wants to send, and leave it up to the logger to decide how to log those messages (to an output stream, file, in-memory circular buffer, database, stream to an analytics API, ignore them, etc.)

'__bridge' casts have no effect when not using ARC

When compiling an older project in Xcode 4.5, there is a warning:
'__bridge' casts have no effect when not using ARC
What's the proper way to get rid of this warning, such that the code works well in both ARC and non-ARC projects?
Any individual source file has to either be compiled with ARC or not compiled with ARC. You should just make up your mind which it is and always use that method for the particular source file. Provided you stick to the memory management naming conventions, you can mix ARC and non ARC source files in the same project.
The reason I say the above, is that if you have code that was written for ARC and you compile it without ARC, there will be memory leaks and premature deallocations all over the place thanks to the fact that all of the retains, releases and autoreleases are missing.
With a few #define's it is quite possible to write source files which compile correctly for more than one of MRC, ARC & GC.
Xcode does not allow you to enable both ARC & GC, knowing that we can write:
#if defined(__OBJC_GC__)
# define USING_GC 1
#else
# define USING_GC 0
#endif
#if __has_feature(objc_arc)
# define USING_ARC 1
#else
# define USING_ARC 0
#endif
#if USING_ARC || USING_GC
# define USING_MRC 0
#else
# define USING_MRC 1
#endif
which will define just one of USING_GC, USING_MRC and USING_ARC as 1 and the other two as 0. You can put that in your projects prefix header (.pch).
You can use these defines directly in your code to include/exclude things like bridge casts; or better to define macros for memory calls, casting, etc. which expand to the appropriate code based on the memory model in use.
HTH
Simply turn on ARC for that file:
-fobjc-arc
(Go to Build Phases > Compile Sources > Compiler Flags)

Run time check of ARC (Automatic Reference Counting)

How can I check if my program is running in an ARC (Automatic Reference Counting) device?
I can use this code:
#if ! __has_feature(objc_arc)
# define FMDBRelease(__v) ([__v release]);
#else
# define FMDBRelease(__v)
#endif
But this work in compile time. A easy code for this is:
if ([object respondsToSelector:#selector(release)]) {
[object release];
}
But not work. I am not finding it in any place. There are a solution for this?
ARC is a compile time technology, thus the #if __has_feature(objc_arc) way is the correct way of doing this.
You can't. ARC is a compile-time tool. It has no real presence at runtime, except for the insertion of calls that allow for tail-call optimization.

Pragma to explicitly enable ARC?

Is there a #pragma (or otherwise some construct) to explicitly enable automatic reference counting (ARC) in an Objective-C (or Objective-C++) source file? Even better if the source file can cause compilation to fail if ARC is not enabled.
I'm starting to have a number of ARC-only source files that can be potentially shared with other projects. Most of these contain category methods to extend built-in classes. I just don't want to accidentally include these in a non-ARC project and starts leaking out memory.
Thanks in advance!
As far as I can tell there is no way to explicitly enable or disable ARC.
However it is possible to detect if it is enabled. Simply add the following snippet to any file that requires ARC.
#ifndef __has_feature
#define __has_feature(x) 0 /* for non-clang compilers */
#endif
#if !__has_feature(objc_arc)
#error ARC must be enabled!
#endif
More info:
http://clang.llvm.org/docs/AutomaticReferenceCounting.html
http://clang.llvm.org/docs/LanguageExtensions.html#__has_feature_extension

NS_INLINE and Xcode debugger

Single stepping through code that uses any of the NS_INLINE functions from NSGeometry.h causes the debugger to lose sync with the current instruction pointer, making debugging the routines very difficult.
I've tried #undef NS_INLINE at the top of my implementation file,#define NS_INLINE in the precompiled header, looking for pragmas, compiler switches, etc., but no matter what, the functions always compile inline for my debug builds.
FWIW - NSMakeRect, NS_MakeSize, etc. all compile inline.
Question is, how do I get NS_INLINE to compile to nothing?
NS_INLINE is wrapped in #if !defined(NS_INLINE). You just need to define it appropriately before you include the Foundation headers. Glancing at the original declaration, you'll probably just need to remove __attribute__((always_inline)) for the debugger to catch your symbols (assuming you're generating all debug symbols and running a debug build - if not, then you could do a little more work to get them to all be visible. Ideally, you'll just create your own label local to your project/group/libs so you can debug your own code more easily.
Is this really about stepping through built-in API functions, or are you trying to do something similar in your own code? If it's the former, is it just due to curiosity, or some other reason for debugging? If it's the latter, I'd suggest commenting out the NS_INLINE while debugging. If you're trying to change the inlining behavior of existing API functions, you may be disappointed, and there are probably better ways to go about it. If your intent is something else, please clarify so we can answer adequately.