Currently, IOS is the second-largest mobile operating system in the world. It also has a very high adoption rate, with more than 85% of users on the latest version. As you might expect, highly engaged users have high expectations.With the demand for IOS developers, many engineers have switched to mobile development. But true IOS expertise extends far beyond basic coding. Below are Some Useful Objective-C Tips for Beginners in IOS Development.
Turn on NSZombieEnabled
As you might know, iPhone applications don’t have an automated garbage collector, so you have to release and retain objects manually. Sometimes you accidentally release an object that you intended to hold on to. Your code will not fail until a message is sent to the deallocated instance, and soon thereafter your application will crash and you’ll have no idea why. Oh, sure, you’ll have the general EXC_BAD_ACCESS error that lets you know you tried to access memory that was deallocated, but that isn’t really useful.
If you turn on NSZombieEnabled, then deallocated instances are held in memory but their class is changed to Zombie, so that when they receive a message they raise an error. Now you can really trobuleshoot those memory problems.
To turn this on :
- Open your project in Xcode
- Expand the Executables section
- Right-click on the name of your executable and select ‘Get Info’
- Click on ‘Arguments’ at the top
- Click the plus underneath Variables to be set in the environment
- For the variable name, type in NSZombieEnabled, and for its value, enter YES
Define descriptions for your custom classes
NSLog is the fundamental of many developer debug workflows but surprisingly not many developers realise you can print out custom descriptions of your own objects instead of displaying the usual hex memory address. You could utilize this to display the contents of your objects similar to how arrays display their keys. To do this simply implement a –(NSString *)description method in your sub-class. If you need the class name and memory address, you can use [ super description ]%p format specifier and pass in self for the memory address. To specify the class name use %@ and pass in NSStringFromClass([self class]).
- (NSString *)description { NSString *className = NSStringFromClass([self class]); return [NSString stringWithFormat:@"<%@: %p %@ - %d>", className, self, self.playerID, self.score]; }
Objective-C Tips in Asynchronous Processes Handling
Let’s consider the following scenario: User opens a screen with the table view, some data is fetched from the server and displayed in a table view.
@property (nonatomic, strong) NSArray *dataFromServer; - (void)viewDidLoad { _weak _typeof(self) weakSelf = self; [[ApiManager shared] latestDataWithCompletionBlock:^(NSArray *newData, NSError *error){ weakSelf.dataFromServer = newData; / / 1 }]; [self.tableView reloadData]; / / 2 } // Other data source delegate methods - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataFromServer.count; }
At first glance, everything looks right: We fetch data from the server and then update the UI. However, the problem is fetching data is an asynchronous process, and won’t return new data immediately, which means reloadData will be called before receiving the new data. To fix this, we should move line #2 after line #1 inside the block.
@property (nonatomic, strong) NSArray *dataFromServer; - (void)viewDidLoad { _weak _typeof(self) weakSelf = self; [[ApiManager shared] latestDataWithCompletionBlock:^(NSArray *newData, NSError *error){ weakSelf.dataFromServer = newData; / / 1 [weakSelf.tableView reloadData]; / / 2 }]; } // and other data source delegate methods - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataFromServer.count; }
Understanding the Pitfalls of Mutable Objects
Mutable objects are very dangerous and can lead to hidden problems. It is a well-known rule that immutable objects should be returned from functions, but most developers don’t know. Let’s consider the following Example :
@interface Box: NSObject @property (nonatomic, readonly, strong) NSArray <Box *> *boxes; @end @interface Box() @property (nonatomic, strong) NSMutableArray <Box *> *m_boxes; - (void)addBox:(Box *)box; @end @implementation Box - (instancetype)init { self = [super init]; if (self) { _m_boxes = [NSMutableArray array]; } return self; } - (void)addBox:(Box *)box { [self.m_boxes addObject:box]; } - (NSArray *)boxes { return self.m_boxes; } @end
The above code is correct, because NSMutableArray is a subclass of NSArray. But the most obvious thing is that another developer might come along and do the following:
NSArray<Box *> *childBoxes = [box boxes]; if ([childBoxes isKindOfClass:[NSMutableArray class]]) { // add more boxes to childBoxes }
The above code will mess up your class. But in that case, it’s a deep trouble, and it’s left up to that developer to pick up the pieces.
Box *box = [[Box alloc] init]; NSArray<Box *> *childBoxes = [box boxes]; [box addBox:[[Box alloc] init]]; NSArray<Box *> *newChildBoxes = [box boxes];
The above case is much worse and demonstrates an unexpected behaviour. The expectation here is that [newChildBoxes count] > [childBoxes count], but if it is not then the class is not well designed because it mutates a value that was already returned.
Fortunately, we can easily fix our code, by rewriting the getter from the first example:
- (NSArray *)boxes { return [self.m_boxes copy]; }
Understanding Concurrency & Multithreading
It is extremely useful and efficient once you know how to utilize it properly and securely. Concurrency can have huge advantages for your application.
- Almost every application has calls to web services [For example, to perform some heavy calculations or read data from a database]. If these tasks are performed on the main queue, the application will freeze for some time, making it non-responsive. Moreover, if this takes too long, IOS will shut down the app completely. Moving these tasks to another queue allows the user to proceed with the application while the operation is being performed without the app appearing to freeze.
- Advanced IOS gadgets have more than one core, so why should the user wait for tasks to finish sequentially when they can be performed in parallel?
Let’s consider the below examples :
Case 1 :
final class SpinLock { private var lock = OS_SPINLOCK_INIT func withLock<Return>(@noescape body: () -> Return) -> Return { OSSpinLockLock(&lock) defer { OSSpinLockUnlock(&lock) } return body() } } class ThreadSafeVar<Value> { private let lock: ReadWriteLock private var _value: Value var value: Value { get { return lock.withReadLock { return _value } } set { lock.withWriteLock { _value = newValue } } } }
The multithreaded code:
let counter = ThreadSafeVar<Int>(value: 0) // this code might be called from several threads counter.value += 1 if (counter.value == someValue) { // do something }
At first glance, everything is synced and appears as if it should work as expected, since ThreadSaveVar wraps counter and makes it thread safe. Unfortunately, this is not true, as two threads might reach the increment line simultaneously and counter.value == someValue will never become true as a result. As a workaround, we can make ThreadSafeCounter which returns its value after incrementing:
class ThreadSafeCounter { private var value: Int32 = 0 func increment() -> Int { return Int(OSAtomicIncrement32(&value)) } }
Case 2 :
struct SynchronizedDataArray { private let synchronizationQueue = dispatch_queue_create("queue_name", nil) private var _data = [DataType]() var data: [DataType] { var dataInternal = [DataType]() dispatch_sync(self.synchronizationQueue) { dataInternal = self._data } return dataInternal } mutating func append(item: DataType) { appendItems([item]) } mutating func appendItems(items: [DataType]) { dispatch_barrier_sync(synchronizationQueue) { self._data += items } } }
In this case, dispatch_barrier_sync was utilized to sync access to the array. This is a favorite pattern to ensure access synchronization. Unfortunately, this code doesn’t consider that struct makes a copy each time we append an item to it, thus having a new synchronization queue each time.
Here, even if it looks correct at first sight, it might not work as expected. It also requires a lot of work to test and debug it, but in the end, you can improve your app speed and responsiveness.
Method Swizzling Feature in Objective-C
Method swizzling is the fancy C code feature that enables to extend methods behaviour in runtime. This is a very powerful feature that enables to overwrite methods in similar way as in class categories but in limited scope. We don’t need to have an access to source code so can customise any method.
@interface NSString (Swizzling) @end @implementation NSString (Swizzling) - (NSString *)swizzled_uppercaseString { NSString *result = [self swizzled_uppercaseString]; // our custom code result = [result stringByAppendingString:@" (swizzled)"]; return result; } @end
Above we have a category for NSString class that will be used to swizzle methods. It can’t be called directly because it contains infinitive loop. In this example out swizzled method just adds suffix to result string bacause our intention is not to replace ‘uppercaseString’ method but only extend it a little bit.
#import <objc/runtime.h> ... NSString *sample = @"abc"; NSLog([sample uppercaseString]); // original method is called Method original, swizzled; original = class_getInstanceMethod([NSString class], @selector(uppercaseString)); swizzled = class_getInstanceMethod([NSString class], @selector(swizzled_uppercaseString)); method_exchangeImplementations(original, swizzled); NSLog( [sample uppercaseString] ); // swizzled method is called method_exchangeImplementations(swizzled, original); NSLog( [sample uppercaseString] ); // again we can call original method ...
Above we replace only implementation of the method in the runtime but not a source code. So in the runtime after methods are swizzled [self swizzled_uppercaseString] invokes [self uppercaseString]. So we could get an infinitive loop if we call directly [self uppercaseString] in our swizzled method.
Objective-C doesn’t really use strong typing, and neither should you
Any Objective-C object can be declared with the type ‘id’ rather than its specific class. This permits you to utilize some potentially dangerous instances of polymorphism :
Duck *aDuck = [[Duck alloc] init]; Goose *aGoose = [[Goose alloc] init]; Pond *aLake = [[Pond alloc] init]; aLake.inhabitant = aDuck; [aLake.inhabitant quack]; // The duck says: "quack!" aLake.inhabitant = aGoose; [aLake.inhabitant quack]; // The goose says: "do I look like a duck to you?"
This is made possible by a simple declaration in the Pond class:
@interface Pond : NSObject { id *inhabitant; } @property (nonatomic, retain) id *inhabitant;
Objective-C doesn’t care what the inhabitant is, just that it descends from NSObject. This will sensibly warn you at compile-time that the inhabitant might not respond to quack, however, so be careful with this power. If you send a bad method to an object it isn’t the same as if you sent a message to nil: your program will crash if the object has no such method defined on it.
But still, you can pass all kinds of different objects around now however you like. Just remember to make sure they respond to the method you pass to them before you use them.
Submit your Objective-C Errors, issues or problems and get it fixed by an Expert IOS Developer