Saturday, July 8, 2017

How to: Basic of XCode Methods & Functions in Objective-C

Why ObjC?

Cause we're oldskool! :D


INTRODUCTION

When we start to code, what we need to do is write methods on top of the already available delegates (namely the View Lifecycles delegates like viewDidLoad or viewDidLayoutSubviews). A typical method that we quickly create is IBActions. These are automatically created when you drag an outlet in Storyboard to your header file.


-(IBAction)aButtonWasClicked:(id)sender {
  // your button was clicked by user
}

The aButtonWasClicked: is the name of the method. Note that I include the semicolon at the end. This indicates there is a parameter to be supplied to the method. So if you want to call this method elsewhere programatically, you need to write:




[self aButtonWasClicked:obj];

If you don't know what is obj, and sender is never used in the method, you can supply nil to it:


[self aButtonWasClicked:nil];

And if you want to supply it to a @selector() you do it this way:


@selector(aButtonWasClicked:) // semicolon is important

If you know what sender is (in this case, obviously a UIButton), you can make use of it by using TYPECASTING. Typecast is basically implying a certain type to a generic/unknown object. For example:


-(IBAction)aButtonWasClicked:(id)sender {
   UIButton *button = (UIButton*)sender; // this is typecasting of the sender obj which is type "id" to "UIButton"
   // now you can use button object properties.
   [button setTitle:"I was Clicked!" forState:UIControlStateNormal];
}


Okay. Now on to writing your own method - which is the cool part!. There are quite a few types of methods, but the most common ones are instance method and class method. Instance methods are the ones started with a dash "-". And class methods are the ones started with a plus "+".

INSTANCE METHODS

Instance methods are called by... drumroll... an instance of the object. Most instance methods are normally called by the viewController in which they contain, which is "self", hence we see [self... at the front.

Instance methods are most common, and there are a few variations of it. The most common that I use is like below:


-(void)setColorForView:(UIColor*)color andSize:(CGSize)size {

}

Lets break this method down. "-" indicates it is an instance method. The thing in bracket is the return value of this method. void means nothing. So you will get nil returned by this method if you call it by assignment. For example:


id value = [self setColorForView:clr andSize:size];

// value here will be nil always.

Next, setColorForView: is the NAME of the method AND it also acts as a selector name for the first parameter. andSize is the selector name for the next parameter. You can add to the method as much as you want. But be practical. Such method as below is ridiculous to say the least.


-(void)setColorForView:(UIColor*)color andSize:(CGSize)size andHeight:(CGFloat)height 
withFlag:(int)flag andViewController:(UIViewController*)viewCtrl 
andYourMom:(UIMom*)mom isFat:(UIFat*)fat {
 // crazy!
}


LOL!

Then you ask, what if I want to pass many parameters into the method? Well, you can simplify it by adding your parameters into NSDictionary and simply use:


-(void)setColorForView:(NSDictionary*)params {
   // get color: [params objectForKey:@"Color"];
}

Or, best way is to create a custom object with all the parameters and pass that instead of dictionary.


-(void)setColorForView:(MyParams*)params {
 // get color: params.color;
}

Better and cleaner method name right?

What if you want your method to return a value? Remember the void? Now just replace that with a type that you want to return. For example lets make the method return a Boolean:


-(BOOL)setColorForView:(UIColor*)color andSize:(CGSize)size {
 if (size.width>100)
   return YES; 
 else 
   return NO; 
}

Do you want to be fancy? a simple if/else statement can be reduced to a one-liner like this:


-(BOOL)setColorForView:(UIColor*)color andSize:(CGSize)size {
  return (size.width>0)?YES:NO; // woaaahh mindblown!
}

The method checks for size.width if it is over 100, the method returns YES, and if not returns NO. You can use this method like below:


BOOL widthOverHundred = [self setColorForView:color andSize:size];
// widthOverHundred now will be YES or NO depending on size value.

You can also return an OBJECT from instance method. For example:


-(UIColor*)getColorForView:(int)tag { 
  if (tag==0) return [UIColor redColor]; 
  else return [UIColor greenColor]; 
}


Note that an Object type name includes * to indicate a pointer to the object.

What if you want to return many values and objects??? Hopefully you've guessed it, just use NSDictionary or custom object.


-(NSDictionary*)getParamsForView:(int)tag {
    NSDictionary *dict =// construct dictionary here
    return dict;
}


-(MyParams*)getParamsForView:(int)tag {
    MyParams *params =// construct params here
    return params;
}

That's about all there is to know about instance methods, for noobs like us. Pretty easy peasy lemon squeezy.

CLASS METHODS
Class methods are similar, but implementation is different. I seldom use it except some special cases. A real world example of Class Method is


[NSString stringWithFormat:@"%d",val];

"stringWithFormat:" is the class method. As you can see, we can use it immediately using its class and there is no need for us to create an "instance" of the class before we use it.

Typically a class method is created in custom class. Or when you create a category of an existing class.

Consider a custom class called Vehicle.

With class methods we can init a particular vehicle with different types and the correct seatCapacity.


-(id)init {
 self = [super init];
 if (self) {
 // any default init setup
 }
 return self;
}


+(id)createCar {
  Vehicle *obj = [[self alloc] init];
  obj.seatCapacity = 5.0;
  return obj;
}
 
+(id)createBus { 
  Vehicle *obj = [[self alloc] init];
  obj.seatCapacity = 20.0; 
  return obj; 
} 

To use it:


Vehicle *car = [Vehicle createCar];
Vehicle *bus = [Vehicle createBus];

Pretty cool eh?

Another useful type of Class method is the completion block (which I love very much). It is exactly like having an office boy who does errands for you XD and returns to you when the job is done. Completion block is extremely useful when you want to execute something that takes time, for example, downloading or uploading data. For example, here's a class completion block method to download an image file.




+(void)downloadImageWithURL:(NSString *)urlString
          completedWithData:(void(^)(NSData *data))completedWithData
                    failure:(void(^)(NSString *errorMessage))failure

{
    
    NSURLSession *session = [NSURLSession sharedSession];
    NSMutableCharacterSet *chars = NSCharacterSet.URLQueryAllowedCharacterSet.mutableCopy;
    NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEncodingWithAllowedCharacters:chars]];
    NSURLSessionDataTask *task = [session dataTaskWithURL:url
                                completionHandler:^(NSData *data,
                                                    NSURLResponse *response,
                                                    NSError *error) {
                                    
                                    if ((error==NULL)&&([data length]>0)) {
                                        
                                        completedWithData(data);
                                        
                                    } else {
                                        
                                        failure(@"NO_DATA");
                                        
                                    }
                                    
                                }];
    [task resume];

}


Put this in your custom class, say, "DownloadManager", then in your ViewController.h import DownloadManager.h and then you can you it in your ViewController.m like so:

[DownloadManager downloadImageWithURL:@"https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg" completedWithData:^(NSData *data) {
        
        dispatch_async(dispatch_get_main_queue(), ^{
            self.myImage.image = [UIImage imageWithData:data];
        });
        
    }failure:^(NSString *errorMsg) {
        NSLog(@"ERROR: %@",errorMsg);
        
    }];


This is particularly useful when you want to have an asynchronous image downloader to be used with your TableView or CollectionView. Sure, the class method is not complete, for example, you should also add a way to save the downloaded images into a "cache" of some sort if you are using it with TableView/CollectionView. But here, I just want to demonstrate the Class block method.

So that's all there is to it as a basic introduction to Objective-C methods for iOS. I'll add more if I remember anything that I missed out. Meanwhile, comment below if you have any opinions. Thank you for reading!

No comments:

Post a Comment