iOS Objective-C 基础 (一) - 分类 / 代理 / 懒加载

iOS Oct 16, 2014

由于早期经常随随便便一点知识就写一篇博客出来,现在合并一些相似内容的到一篇里面。

什么是分类

Category 模式用于向已经存在的类添加方法从而达到扩展已有类的目的,在很多情形下 Category 也是比创建子类更优的选择。新添加的方法同样也会被被扩展的类的所有子类自动继承。当知道已有类中某个方法有BUG,但是这个类是以库的形式存在的,我们无法直接修改源代码的时候,Category 也可以用于替代这个已有类中某个方法的实体,从而达到修复 BUG 的目的。然而却没有什么便捷的途径可以去调用已有类中原有的那个被替换掉方法实体了。需要注意的是,当准备有 Category 来替换某一个方法的时候,一定要保证实现原来方法的所有功能,否则这种替代就是没有意义而且会引起新的 BUG 。和子类不同的是,Category 不能用于向被扩展类添加实例变量。Category 通常作为一种组织框架代码的工具来使用。

总结一句就是:扩展方法,但是不能扩展属性

  1. 如果需要添加一个新的变量,则需添加子类。
  2. 如果只是添加一个新的方法,用 Category 是比较好的选择。

用法

//.h文件
@interface ClassName (CategoryName)
    -methodName1
    -methodName2
@end

//.m文件
@implementation ClassName (CategoryName)
    -methodName1
    -methodName2
@end

示例代码1

.h文件

//苹果官方的 UIView 不支持直接修改宽高等等各种属性,这个时候我们可以写一个分类

@interface UIView (MJ)

@property (assign, nonatomic) CGFloat x;
@property (assign, nonatomic) CGFloat y;
@property (assign, nonatomic) CGFloat width;
@property (assign, nonatomic) CGFloat height;
@property (assign, nonatomic) CGSize size;
@property (assign, nonatomic) CGPoint origin;
@end

.m文件

@implementation UIView (MJ)
- (void)setX:(CGFloat)x
{
    CGRect frame = self.frame;
    frame.origin.x = x;
    self.frame = frame;
}
- (CGFloat)x
{
    return self.frame.origin.x;
}
- (void)setY:(CGFloat)y
{
    CGRect frame = self.frame;
    frame.origin.y = y;
    self.frame = frame;
}
- (CGFloat)y
{
    return self.frame.origin.y;
}
- (void)setWidth:(CGFloat)width
{
    CGRect frame = self.frame;
    frame.size.width = width;
    self.frame = frame;
}
- (CGFloat)width
{
    return self.frame.size.width;
}
- (void)setHeight:(CGFloat)height
{
    CGRect frame = self.frame;
    frame.size.height = height;
    self.frame = frame;
}
- (CGFloat)height
{
    return self.frame.size.height;
}
- (void)setSize:(CGSize)size
{
    CGRect frame = self.frame;
    frame.size = size;
    self.frame = frame;
}
- (CGSize)size
{
    return self.frame.size;
}
- (void)setOrigin:(CGPoint)origin
{
    CGRect frame = self.frame;
    frame.origin = origin;
    self.frame = frame;
}
- (CGPoint)origin
{
    return self.frame.origin;
}
@end

示例代码 2

MBProgressHUD 的一个分类,原作者为李明杰
MBProgressHUD 实在太难用,这个分类可以简化很多操作

废话不多说,上代码

//
//  MBProgressHUD+LJ.h
//  lntuApp
//
//  Created by JieLee on 15-01-05.
//  Copyright (c) 2015年 PUPBOSS. All rights reserved.
//

#import "MBProgressHUD.h"

@interface MBProgressHUD (LJ)

+ (void)showSuccess:(NSString *)success toView:(UIView *)view;
+ (void)showError:(NSString *)error toView:(UIView *)view;

+ (MBProgressHUD *)showMessage:(NSString *)message toView:(UIView *)view;


+ (void)showSuccess:(NSString *)success;
+ (void)showError:(NSString *)error;

+ (MBProgressHUD *)showMessage:(NSString *)message;

+ (void)hideHUDForView:(UIView *)view;
+ (void)hideHUD;

@end


//
//  MBProgressHUD+LJ.h
//  lntuApp
//
//  Created by JieLee on 15-01-05.
//  Copyright (c) 2015年 PUPBOSS. All rights reserved.
//

#import "MBProgressHUD+LJ.h"

@implementation MBProgressHUD (LJ)

#pragma mark 显示信息
+ (void)show:(NSString *)text icon:(NSString *)icon view:(UIView *)view
{
    if (view == nil) view = [[UIApplication sharedApplication].windows lastObject];
    
    // 快速显示一个提示信息
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:view animated:YES];
    hud.labelText = text;
    
    // 设置图片
    hud.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:@"MBProgressHUD.bundle/%@", icon]]];
    
    // 再设置模式
    hud.mode = MBProgressHUDModeCustomView;
    
    // 隐藏时候从父控件中移除
    hud.removeFromSuperViewOnHide = YES;
    
    // 0.9秒之后再消失
    [hud hide:YES afterDelay:0.9];
}

#pragma mark 显示错误信息
+ (void)showError:(NSString *)error toView:(UIView *)view {
    
    [self show:error icon:@"error.png" view:view];
}

+ (void)showSuccess:(NSString *)success toView:(UIView *)view {
    
    [self show:success icon:@"success.png" view:view];
}

#pragma mark 显示一些信息
+ (MBProgressHUD *)showMessage:(NSString *)message toView:(UIView *)view {
    
    if (view == nil) view = [[UIApplication sharedApplication].windows lastObject];
    
    // 快速显示一个提示信息
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:view animated:YES];
    hud.labelText = message;
    
    // 隐藏时候从父控件中移除
    hud.removeFromSuperViewOnHide = YES;
    
    // YES代表需要蒙版效果
    hud.dimBackground = YES;
    
    return hud;
}

+ (void)showSuccess:(NSString *)success
{
    [self showSuccess:success toView:nil];
}

+ (void)showError:(NSString *)error
{
    [self showError:error toView:nil];
}

+ (MBProgressHUD *)showMessage:(NSString *)message
{
    return [self showMessage:message toView:nil];
}

+ (void)hideHUDForView:(UIView *)view
{
    [self hideHUDForView:view animated:YES];
}

+ (void)hideHUD
{
    UIView *view = [[UIApplication sharedApplication].windows lastObject];
    [self hideHUDForView:view];
}
@end

Delegate

今天仔细研究了下代理模式,之前是只会用,不知道具体实现,更不会自己编写。

废话不多说,上代码!

拟定一份协议

//
//  AssistantDelegate.h
//  141018Test
//
//  Created by JieLee on 10/18/14.
//  Copyright (c) 2014 PUPBOSS. All rights reserved.
//

#import <Foundation/Foundation.h>

@protocol AssistantDelegate <NSObject>
- (void)heisthirsty;
- (void)heishungry;
@end

编写需要代理的类

.h文件

//
//  Professor.h
//  141018Test
//
//  Created by JieLee on 10/18/14.
//  Copyright (c) 2014 PUPBOSS. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "AssistantDelegate.h"

@interface Professor : NSObject
@property (nonatomic, weak) id<AssistantDelegate> assis;
- (void)thirsty;
- (void)hungry;
@end

.m文件

//
//  Professor.m
//  141018Test
//
//  Created by JieLee on 10/18/14.
//  Copyright (c) 2014 PUPBOSS. All rights reserved.
//

#import "Professor.h"

@implementation Professor
- (void)thirsty {

    NSLog(@"i am thirsty");
    [_assis heisthirsty];
}
- (void)hungry {

    NSLog(@"i am hungry");
    [_assis heishungry];
}
@end

编写实现代理的类

.h文件

//
//  Assistant.h
//  141018Test
//
//  Created by JieLee on 10/18/14.
//  Copyright (c) 2014 PUPBOSS. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "AssistantDelegate.h"

@interface Assistant : NSObject <AssistantDelegate>
@end

.m文件

//
//  Assistant.m
//  141018Test
//
//  Created by JieLee on 10/18/14.
//  Copyright (c) 2014 PUPBOSS. All rights reserved.
//

#import "Assistant.h"

@implementation Assistant
- (void)heishungry {

    NSLog(@"give you food");
}
- (void)heisthirsty {

    NSLog(@"give you water");
}
@end

编写main函数

//
//  main.m
//  141018Test
//
//  Created by JieLee on 10/18/14.
//  Copyright (c) 2014 PUPBOSS. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Professor.h"
#import "Assistant.h"

int main(int argc, const char * argv[]) {

    Professor *p = [Professor new];
    p.assis = [Assistant new];
    [p hungry];
    [p thirsty];
    return 0;
}

运行结果

2014-10-19 00:07:33.328 141018Test[2527:90902] i am hungry
2014-10-19 00:07:33.330 141018Test[2527:90902] give you food
2014-10-19 00:07:33.330 141018Test[2527:90902] i am thirsty
2014-10-19 00:07:33.330 141018Test[2527:90902] give you water
Program ended with exit code: 0

即使删除了 Assistant 这个类,程序也不会报错。

方法名上面可以加 @required@optional 来修饰。

举个栗子:

@protocol AssistantDelegate <NSObject>

@required //必须实现的方法
- (void)heisthirsty;

@optional //可选实现的方法
- (void)heishungry;
@end

请注意 不声明的话默认是 @required 但是 编译不会报错,因为 Obj-C 是弱语法

不过问题就来了,这样做岂不是特别耗性能?不管代理有没有实现方法,都会通知代理做这件事情。

懒加载

原本是研究 Location 框架的,学学定位相关的知识,无奈折腾一夜,iOS8 和之前有改动,网上也有人说 Xcode6 有 bug,定位方面的。总之,就是没弄成。

好了扯淡到这里,进入正题,说懒加载。

懒加载,英文名 LazyLoad。也称为延迟加载,即在需要的时候才加载(效率低,占用内存小)。所谓懒加载,写的是其 get 方法。

  • 不必将创建对象的代码全部写在 viewDidLoad 方法中,代码的可读性更强。
  • 每个控件的 getter 方法中分别负责各自的实例化处理,代码彼此之间的独立性强,松耦合。
  • 一定要先判断是否存在。

上代码

@interface ViewController () <CLLocationManagerDelegate>

@property (nonatomic, strong) CLLocationManager *locMgr;
@end


- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 开始定位
    [self.locMgr startUpdatingLocation];
}

- (CLLocationManager *)locMgr {
    if (!_locMgr) { //判断是否为空
        _locMgr = [[CLLocationManager alloc] init];
        // 设置代理
        _locMgr.delegate = self;
        [_locMgr requestAlwaysAuthorization];
    }
    return _locMgr;
}

Tags

Jie Li

🚘 On-road / 📉 US Stock / 💻 Full Stack Engineer / ®️ ENTJ