I'm trying to debug a mysterious crash I'm seeing in Crashlytics, but haven't been able to reproduce myself.
The error message looks like this:
Fatal Exception: NSInvalidArgumentException
-[NSNull compare:]: unrecognized selector sent to instance 0x1e911bc30
-[NSOrderedSet initWithSet:copyItems:]
Here is the full stacktrack if interested
Because I haven't been able to pinpoint the origin of the crash, I thought I would add a new method to NSNull in order to further debug it via logging.
However I'm not sure how to do it. I think I'd need to add a compare method to NSNull, but I have limited knowledge of objc. I got the idea from this answer. The proposed solution for a similar problem looks like this
BOOL canPerformAction(id withSender) {
return false;
}
- (void)viewDidLoad {
[super viewDidLoad];
Class class = NSClassFromString(#"UIThreadSafeNode");
class_addMethod(class, #selector(canPerformAction:withSender:), (IMP)canPerformAction, "##:");
}
How could I do this in Swift for adding compare to NSNull?
You could add a compare method to NSNull like this:
Objective-C:
#import <objc/runtime.h>
static inline NSComparisonResult compareNulls(id self, SEL _cmd, NSNull *other) {
if([other isKindOfClass:[NSNull class]]) {
return NSOrderedSame; // Nulls are always the same.
}
return NSOrderedDescending;
}
#implementation NSNull (Comparisons)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
const char *encoding = [[NSString stringWithFormat:#"%s#:#", #encode(NSComparisonResult)] UTF8String];
class_addMethod([self class], #selector(compare:), (IMP)compareNulls, encoding);
});
}
#end
Swift:
// Add this code to your AppDelegate.swift file:
import ObjectiveC
fileprivate func compareNulls(_ self: AnyObject, _ _cmd: Selector, _ other: AnyObject) -> ComparisonResult {
if other is NSNull {
return .orderedSame
}
return .orderedDescending
}
fileprivate func addNSNullCompareImplementationIfNecessary() {
let sel = NSSelectorFromString("compareNulls:")
guard class_getMethodImplementation(NSNull.self, sel) == nil else {
return
}
let types = "i#:#"
class_addMethod(NSNull.self, sel, imp_implementationWithBlock(compareNulls), types)
}
// Add this line to your -didFinishLaunching: function:
addNSNullCompareImplementationIfNecessary()
This is only a temporary solution that will stop the crashes.
I would nevertheless encourage you to a) file a bug report, and b) continue investigating why this happened - clearly having an NSNull in this case wasn't expected by Parse...
Related
Basically, I want to print each time a class object is instantiated. The following code shows the intent.
#interface NSObject (ILogger)
+ (void)initialize;
#end
#implementation NSObject (ILogger)
+ (void)initialize
{
NSLog(#"Initializing %s", class_getName([self class]));
}
#end
This does not work because NSObject already has a +initialize method so this approach results in undefined behavior. The compiler also warns about the issue: warning: category is implementing a method which will also be implemented by its primary class
One idea would be to somehow swizzle +[NSObject initialize] and do the logging. How do I do that safely?
EDIT:
Maybe I'm using the wrong terminology but the goal is to know if a class is used at all in the app. If many objects of a class are created, there is no need to log every time, once is sufficient.
After Edit Answer
You are correct about use of +[NSObject initialize] method for tracking the first use of a class. I don't know anything more appropriate for that. The swizzling would look like this:
#import <objc/runtime.h>
#implementation NSObject (InitializeLog)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getClassMethod(self, #selector(initialize));
Method swizzledMethod = class_getClassMethod(self, #selector(tdw_initializeLog));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
+ (void)tdw_initializeLog {
const char *className = class_getName(self);
printf("Initializing %s\n", className);
[self tdw_initializeLog];
}
#end
There are a few things to be advised about:
initialize doesn't fallback to the NSObject implementation (which is swizzled above) if derived classes have this method implemented AND don't have [super initialize]; called. Thus for any custom class inherited from Cocoa classes either don't implement this method OR call [super initialize]; somewhere in your implementation:
+ (void)initialize {
[super initialize];
...
}
Cocoa classes are rarely as straightforward as they look like. Quite a lot of interfaces and classes are hidden under the same name and sometimes the logs will be somewhat misleading (e.g. in place of NSNumber you will get NSValue class reported). Thus, take any logging out of Foundation classes with a grain of salt and always double-check where it comes from (also be ready that those classes won't be reported at all).
First use of NSLog also triggers some classes to initialise themselves and it make them to call +[NSObject initialize]. In order to avoid an infinite loop or bad_access errors I decided to use printf to log the fact of initialisation in my implementation.
Original Answer
The + (void)initialize method has very little to do with objects instantiation, since it gets called for each Objective-C class shortly before it's first time used in your client code. It might be called multiple times if subclasses of a given class don't have this method implemented and never gets called afterward. Thus it's just a bad choice if you want to track objects instantiation.
However there are still a few options you may want to employ to track occasions of objects instantiation.
Swizzling -[NSObject init]
First, I would consider init method of NSObject:
#import <objc/runtime.h>
#implementation NSObject (InitLog)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, #selector(init));
Method swizzledMethod = class_getInstanceMethod(self, #selector(initLog_tdw));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (instancetype)initLog_tdw {
self = [self initLog_tdw];
if (self) {
const char *className = class_getName([self class]);
NSLog(#"Instantiating %s", className);
}
return self;
}
#end
It will work fine as long as instances falls back to the -[NSObject init] method. Unfortunately quite a lot of Cocoa classes don't do that. Consider the following scenario:
NSObject *obj = [NSObject new]; // NSLog prints "Instantiating NSObject"
NSString *hiddenStr = [[NSMutableString alloc] initWithString:#"Test"]; // NSLog is silent
NSURL *url = [[NSURL alloc] initWithString:#"http://www.google.com"]; // NSLog is silent
-[NSURL initWithString:] and -[NSMutableString initWithString:] somehow avoids NSObject's default constructor being called. It will still work for any custom classes which don't have any fancy initialisation:
#implementation TDWObject
- (instancetype)initWithNum:(int)num {
self = [super init];
if (self) {
_myNum = num;
}
return self;
}
#end
TDWObject *customObj = [TDWObject new]; // NSLog prints "Instantiating TDWObject"
TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Instantiating TDWObject"
Swizzling +[NSObject alloc]
Alternatively you can swizzle the alloc method:
#import <objc/runtime.h>
#implementation NSObject (AllocLog)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getClassMethod(self, #selector(alloc));
Method swizzledMethod = class_getClassMethod(self, #selector(tdw_allocLog));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
+ (instancetype)tdw_allocLog {
id allocatedObject = [self tdw_allocLog];
if (allocatedObject) {
const char *className = class_getName([allocatedObject class]);
NSLog(#"Allocating %s", className);
}
return allocatedObject;
}
#end
It will intercept almost all Cocoa classes instantiation (the exception must be some of the fabric methods, where class-specific optimisation takes place, e.g. +[NSNumber numberWith..] family of methods), but there are other problems to be aware of. The allocated instances returned from alloc method are not always that straightforward. E.g. for NSMutableString example above NSLog will print NSPlaceholderMutableString:
TDWObject *customObj = [TDWObject new]; // NSLog prints "Allocating TDWObject"
TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Allocating TDWObject"
NSObject *obj = [NSObject new]; // NSLog prints "Allocating NSObject"
NSString *hiddenStr = [[NSMutableString alloc] initWithString:#"Test"]; // NSLog prints "Allocating NSPlaceholderMutableString"
NSURL *url = [[NSURL alloc] initWithString:#"http://www.google.com"]; // NSLog prints "Allocating NSURL"
That's is because Foundation framework uses Class Cluster design pattern heavily and instances returned by alloc are often some kind of abstract factories, which are later leveraged by Cocoa classes to make a concrete instance of the class requested.
Both approaches have their own downsides, but I struggle to come up with anything more concise and reliable.
I think it's possible to do this with breakpoint if only need logging, I've not tested it with initialize, but does works on my case with dealloc, note that it might print a lot more than you actually needed and slow down performance:
In Xcode, go to the Breakpoint navigator (Cmd+8)
At the bottom-left on the screen, tap '+', then select "Symbolic Breakpoint..." from the menu
Fill the form:
Symbol: -[NSObject initialize]
Action: Select "Log Message"
Enter: --- init #(id)[$arg1 description]# #(id)[$arg1 title]#
Select "Log message to console"
Check "Automatically continue after evaluating actions" so Xcode does not stop at the breakpoint
The only possible solution I found was swizzling -[NSObject init] this was tested only in a small test project
I have an article about swizzling that maybe you will find interesting medium article
extension NSObject {
static let swizzleInit: Void = {
DispatchQueue.once(token: "NSObject.initialize.swizzle") {
let originalSelector = Selector("init")
let swizzledSelector = #selector(swizzledInitialize)
guard let originalMethod = class_getInstanceMethod(NSObject.self, originalSelector),
let swizzledMethod = class_getInstanceMethod(NSObject.self, swizzledSelector)
else {
debugPrint("Error while swizzling")
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
#objc
private func swizzledInitialize() -> Self? {
debugPrint("\(Self.self) has been initialized")
return swizzledInitialize()
}
}
DispatchQueue.once implementation in DispatchQueue.once gist
Then in AppDelegate ...
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
NSObject.swizzleInit
// Override point for customization after application launch.
return true
}
I've spent the better part of a day and half now trying to debug this issue I'm seeing when trying to unarchive a Data blob stored locally (this issue also appears when retrieving it via iCloud, but since they run through the same codepath, I assume they're related).
Background
I originally built this app four years ago, and for reasons that have since been lost to time (but probably because I was more of a novice back then), I relied on the AutoCoding library to get objects in my data model to automagically adopt NSCoding (although I did implement the protocol myself in some places -- like I said, I was an novice) and FCFileManager for saving these objects to the local documents directory. The data model itself is fairly simple: custom NSObjects that have various properties of NSString, NSArray, and other custom NSObject classes (but I will note there are a number of circular references; most of them declared as strong and nonatomic in header files). This combination has (and still does) work well in the production version of the app.
However, in a future update, I'm planning on adding saving/loading files from iCloud. While I've been building that out, I've been looking to trim down my list of third-party dependencies and update older code to iOS 13+ APIs. It so happens that FCFileManager relies on the now-deprecated +[NSKeyedUnarchiver unarchiveObjectWithFile:] and +[NSKeyedArchiver archiveRootObject:toFile:], so I've focused on rewriting what I need from that library using more modern APIs.
I was able to get saving files working pretty easily using this:
#objc static func save(_ content: NSCoding, at fileName: String, completion: ((Bool, Error?) -> ())?) {
CFCSerialQueue.processingQueue.async { // my own serial queue
measureTime(operation: "[LocalService Save] Saving") { // just measures the time it takes for the logic in the closure to process
do {
let data: Data = try NSKeyedArchiver.archivedData(withRootObject: content, requiringSecureCoding: false)
// targetDirectory here is defined earlier in the class as the local documents directory
try data.write(to: targetDirectory!.appendingPathComponent(fileName), options: .atomicWrite)
if (completion != nil) {
completion!(true, nil)
}
} catch {
if (completion != nil) {
completion!(false, error)
}
}
}
}
}
And this works great -- pretty fast and can still be loaded by FCFileManager's minimal wrapper around +[NSKeyedUnarchiver unarchiveObjectWithFile:].
Problem
But loading this file back from the local documents directory has proved to be a massive challenge. Here's what I'm working with right now:
#objc static func load(_ fileName: String, completion: #escaping ((Any?, Error?) -> ())) {
CFCSerialQueue.processingQueue.async {// my own serial queue
measureTime(operation: "[LocalService Load] Loading") {
do {
// targetDirectory here is defined earlier in the class as the local documents directory
let combinedUrl: URL = targetDirectory!.appendingPathComponent(fileName)
if (FileManager.default.fileExists(atPath: combinedUrl.path)) {
let data: Data = try Data(contentsOf: combinedUrl)
let obj: Any? = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
completion(obj, nil)
} else {
completion(nil, ServiceError.generic(message: "Data not found at URL \(combinedUrl)"))
}
} catch {
completion(nil, error)
}
}
}
}
I've replaced FCFileManager's +[NSKeyedUnarchiver unarchiveObjectWithFile:] with the new +[NSKeyedUnarchiver unarchiveTopLevelObjectWithData:], but I'm running into EXC_BAD_ACCESS code=2 crashes when getting execution flowing through that line. The stacktrace is never particularly helpful; it's usually ~1500 frames long and jumping between various custom -[NSObject initWithCoder:] implementations. Here's an example (comments added for context, clarity, and conciseness):
#implementation Game
#synthesize AwayKStats,AwayQBStats,AwayRB1Stats,AwayRB2Stats,AwayWR1Stats,AwayWR2Stats,AwayWR3Stats,awayTOs,awayTeam,awayScore,awayYards,awayQScore,awayStarters,gameName,homeTeam,hasPlayed,homeYards,HomeKStats,superclass,HomeQBStats,HomeRB1Stats,HomeRB2Stats,homeStarters,HomeWR1Stats,HomeWR2Stats,HomeWR3Stats,homeScore,homeQScore,homeTOs,numOT,AwayTEStats,HomeTEStats, gameEventLog,HomeSStats,HomeCB1Stats,HomeCB2Stats,HomeCB3Stats,HomeDL1Stats,HomeDL2Stats,HomeDL3Stats,HomeDL4Stats,HomeLB1Stats,HomeLB2Stats,HomeLB3Stats,AwaySStats,AwayCB1Stats,AwayCB2Stats,AwayCB3Stats,AwayDL1Stats,AwayDL2Stats,AwayDL3Stats,AwayDL4Stats,AwayLB1Stats,AwayLB2Stats,AwayLB3Stats,homePlays,awayPlays,playEffectiveness, homeStarterSet, awayStarterSet;
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
// ...lots of other decoding...
// stack trace says the BAD_ACCESS is flowing through these decoding lines
// #property (atomic) Team *homeTeam;
homeTeam = [aDecoder decodeObjectOfClass:[Team class] forKey:#"homeTeam"];
// #property (atomic) Team *awayTeam;
// there's no special reason for this line using a different decoding method;
// I was just trying to test out both
awayTeam = [aDecoder decodeObjectForKey:#"awayTeam"];
// ...lots of other decoding...
}
return self;
}
Each Game object has a home and away Team; each team has an NSMutableArray of Game objects called gameSchedule, defined as such:
#property (strong, atomic) NSMutableArray<Game*> *gameSchedule;
Here's Team's initWithCoder: implementation:
-(id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
if (teamHistory.count > 0) {
if (teamHistoryDictionary == nil) {
teamHistoryDictionary = [NSMutableDictionary dictionary];
}
if (teamHistoryDictionary.count < teamHistory.count) {
for (int i = 0; i < teamHistory.count; i++) {
[teamHistoryDictionary setObject:teamHistory[i] forKey:[NSString stringWithFormat:#"%ld",(long)([HBSharedUtils currentLeague].baseYear + i)]];
}
}
}
if (state == nil) {
// set the home state here
}
if (playersTransferring == nil) {
playersTransferring = [NSMutableArray array];
}
if (![coder containsValueForKey:#"projectedPollScore"]) {
if (teamOLs != nil && teamQBs != nil && teamRBs != nil && teamWRs != nil && teamTEs != nil) {
FCLog(#"[Team Attributes] Adding Projected Poll Score to %#", self.abbreviation);
projectedPollScore = [self projectPollScore];
} else {
projectedPollScore = 0;
}
}
if (![coder containsValueForKey:#"teamStrengthOfLosses"]) {
[self updateStrengthOfLosses];
}
if (![coder containsValueForKey:#"teamStrengthOfSchedule"]) {
[self updateStrengthOfSchedule];
}
if (![coder containsValueForKey:#"teamStrengthOfWins"]) {
[self updateStrengthOfWins];
}
}
return self;
}
Pretty simple other than for the backfilling of some properties. However, this class imports AutoCoding, which hooks into -[NSObject initWithCoder:] like so:
- (void)setWithCoder:(NSCoder *)aDecoder
{
BOOL secureAvailable = [aDecoder respondsToSelector:#selector(decodeObjectOfClass:forKey:)];
BOOL secureSupported = [[self class] supportsSecureCoding];
NSDictionary *properties = self.codableProperties;
for (NSString *key in properties)
{
id object = nil;
Class propertyClass = properties[key];
if (secureAvailable)
{
object = [aDecoder decodeObjectOfClass:propertyClass forKey:key]; // where the EXC_BAD_ACCESS seems to be coming from
}
else
{
object = [aDecoder decodeObjectForKey:key];
}
if (object)
{
if (secureSupported && ![object isKindOfClass:propertyClass] && object != [NSNull null])
{
[NSException raise:AutocodingException format:#"Expected '%#' to be a %#, but was actually a %#", key, propertyClass, [object class]];
}
[self setValue:object forKey:key];
}
}
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
[self setWithCoder:aDecoder];
return self;
}
I did some code tracing and found that execution flows the -[NSCoder decodeObject:forKey:] call above. Based on some logging I added, it seems like propertyClass somehow gets deallocated before getting passed to -[NSCoder decodeObjectOfClass:forKey:]. However, Xcode shows that propertyClass has a value when the crash occurs (see screenshot: https://imgur.com/a/J0mgrvQ)
The property in question in that frame is defined:
#property (strong, nonatomic) Record *careerFgMadeRecord;
and has the following properties itself:
#interface Record : NSObject
#property (strong, nonatomic) NSString *title;
#property (nonatomic) NSInteger year;
#property (nonatomic) NSInteger statistic;
#property (nonatomic) Player *holder;
#property (nonatomic) HeadCoach *coachHolder;
// … some functions
#end
This class also imports AutoCoding, but has no custom initWithCoder: or setWithCoder: implementation.
Curiously, replacing the load method I’ve written with FCFileManager’s version also crashes in the same fashion, so this could be more of an issue with how the data is being archived than how it’s being retrieved. But again, this all works fine when using FCFileManager’s methods to load/save files, so my guess is that there’s some lower-level difference between the implementation of archives in iOS 11 (when FCFileManager was last updated) and iOS 12+ (when the NSKeyedArchiver APIs were updated).
Per some suggestions I've found online (like this one), I also tried this:
#objc static func load(_ fileName: String, completion: #escaping ((Any?, Error?) -> ())) {
CFCSerialQueue.processingQueue.async {
measureTime(operation: "[LocalService Load] Loading") {
do {
let combinedUrl: URL = targetDirectory!.appendingPathComponent(fileName)
if (FileManager.default.fileExists(atPath: combinedUrl.path)) {
let data: Data = try Data(contentsOf: combinedUrl)
let unarchiver: NSKeyedUnarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = false;
let obj: Any? = try unarchiver.decodeTopLevelObject(forKey: NSKeyedArchiveRootObjectKey)
completion(obj, nil)
} else {
completion(nil, ServiceError.generic(message: "Data not found at URL \(combinedUrl)"))
}
} catch {
completion(nil, error)
}
}
}
}
However, this still throws the same EXC_BAD_ACCESS when trying to decode.
Does anyone have any insight into where I might be going wrong here? I’m sure it’s something simple, but I just can’t seem to figure it out. I have no problem providing more source code if needed to dive deeper.
Thanks for your help!
I (somehow) got past this by relying on AutoCoding to manage the NSCoding implementation for the Game class. Peeling back the layers, it seems like there was some issue with using -[NSMutableArray arrayWithObjects:] in -[Game initWithCoder:] that caused the EXC_BAD_ACCESS, but even that seemed to go away when falling back to AutoCoding. Not sure what effect this will have on backwards compatibility, but I guess I'll cross that bridge when I get there.
+ (void)load {
[super load];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL selector = #selector(setBackgroundColor:);
SEL _selector = #selector(cusSetBackgroundColor:);
Method method = class_getInstanceMethod(aClass, selector);
Method _method = class_getInstanceMethod(aClass, _selector);
BOOL did = class_addMethod(aClass, selector, method_getImplementation(_method), method_getTypeEncoding(_method));
if (did) {
class_replaceMethod(aClass, _selector, method_getImplementation(method), method_getTypeEncoding(method));
} else {
class_addMethod(aClass, _selector, method_getImplementation(_method), method_getTypeEncoding(_method));
method_exchangeImplementations(method, _method);
}
});
}
- (void)cusSetBackgroundColor:(UIColor *)backgroundColor
{
NSLog(#"test swizzling");
[self cusSetBackgroundColor:backgroundColor];
}
when i put it into "UITableView+Swizzling". And choose iPad Pro. it will crash. (iPhone runs well)
the crash log is:
Assertion failure in void PushNextClassForSettingIMP(id, SEL()).
Terminating app due to uncaught exception 'NSInternalInconsistencyException'
don't konw why.....
for ipad.
i run 'p class_getInstanceMethod(aClass, #selector(setBackgroundColor:))'
the console can't output the function address.
but for iPhone. i can get the address.
so i tried 'p class_getInstanceMethod(aClass, #selector(_setBackgroundColor))'
problem solved..
but it's just so strange. if you konw other soluetion.
please let me konw.
The same crash with me, and I resolved it use UIView instead of UITableView.
I'm new to Objective-C and I'm trying to determine if the NSString being passed to my method is the same as the NSString previously passed to the same method.
Is there a simple way to do this?
If you're looking to do this per instance of your class (and not globally):
#interface MyClass : NSObject
- (void)myMethod:(NSString *)value;
#end
#interface MyClass ()
#property (copy, nonatomic) NSString *value;
#end
#implementation MyClass
- (void)myMethod:(NSString *)value
{
if ([self.value isEqualToString:value])
{
// Values are the same!
}
else
{
self.value = value;
}
}
#end
Store the string as an instance variable of the class, each time the method is called, compare the instances and replace with the new parameter.
Just to elaborate on what #Wain said:
Add Instance Variable:
#interface ViewController ()
{
NSString * lastString;
}
Then in your method:
- (void) methodWithString:(NSString *)string {
if ([string isEqualToString:lastString]) {
NSLog(#"Same String");
}
else {
NSLog(#"New String");
lastString = string;
}
}
A variation on the theme most answers are following: If you wish to do this per instance of your class then you can use an instance variable. However as this is something which is really specific to your method, you don't want to declare this variable in any interface and recent compilers help you with this by enabling instance variable declaration in the implementation. For example:
#implementation MyClass
{
NSString *methodWithString_lastArgument_; // make it use clear
}
- (void) methodWithString:(NSString *)string
{
if ([string isEqualToString:methodWithString_lastArgument_])
{
// same argument as last time
...
}
else
{
// different argument
isEqualToString:methodWithString_lastArgument_ = string.copy; // save for next time
...
}
}
(The above assumes ARC.)
The string.copy is shorthand for [string copy] and is there to handle mutable strings - if the method is passed an NSMutableString then this will copy its value as an (immutable) NSString. This protects the method from the mutable string changing value between calls.
If you want to do this on a global basis, rather than per instance, you can declare a static variable within your method and thus completely hide it from outside the method:
- (void) methodWithString:(NSString *)string
{
static NSString *lastArgument = nil; // declare and init private variable
if ([string isEqualToString:lastArgument])
{
// same argument as last time
...
}
else
{
// different argument
isEqualToString:lastArgument = string.copy; // save for next time
...
}
}
HTH
I am following the Big Nerd Ranch book on iOS programming.
There is a sample of a static class:
#import <Foundation/Foundation.h>
#interface BNRItemStore : NSObject
+ (BNRItemStore *) sharedStore;
#end
I have a problem undrstanding the bit below with question mark in the comments.
If I try to alloc this class, the overriden method will bring me to sharedStore, which in turn sets the static pointer sharedStore to nil. The conditional after will hit the first time because the pointer doesn't exist.
The idea is that the second time I am in the same place, it would not alloc a new instance and get the existing instance instead. However with static BNRItemStore *sharedStore = nil; I am setting the pointer to nil and destroy it, isn't it? Hence every time I am creating unintentionally a new instance, no?
#import "BNRItemStore.h"
#implementation BNRItemStore
+ (BNRItemStore*) sharedStore
{
static BNRItemStore *sharedStore = nil; // ???
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}
+(id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
#end
However with static BNRItemStore *sharedStore = nil; I am setting the pointer to nil and destroy it, isn't it?
No, you do not set it to nil: the expression in the static initializer is computed only once; the second time around the initialization has no effect. This looks very confusing, but this is the way the function-static facility works in C (and by extension, in Objective-C).
Try this as an example:
int next() {
static int current = 123;
return current++;
}
int main(int argc, char *argv[]) {
for (int i = 0 ; i != 10 ; i++) {
NSLog(#"%d", next());
}
return 0;
}
This would produce a sequence of increasing numbers starting at 123, even though the code makes it appear as if current is assigned 123 every time that you go through the function.