Objective-C面向对象 | 青训营笔记

159 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第1天。

Class的编写

在OC中,一个类由@interface与@implementation两部分组成。

一般分别存放在声明文件(.h)与实现文件(.m)中。

@interface

其中类的 @interface ,它可以写在 .h文件 也可以 放在 .m文件,

  • 如果放在 .h 文件,就能被其他的.h或者.m文件用import的方式引用。
  • 如果放在.m 文件里,那他就只能在这个文件里的其它类调用,变成了文件内私有的。
@interface ByteDancer : NSObject

// 属性部分
@property(nonatomic, copy) NSString *name;

// 构造函数
- (instancetype)initWithName:(NSString *)name;

// 公共实例方法(public)
- (void)sayHello;
- (void)groupWith:(NSString *)name;
- (ByteDancer *)groupWith:(NSString *)name andWith:(NSString *)name2;

// 公共类方法(静态函数static)
+ (int)staticFunction;
@end
  • ByteDancer : NSObject:ByteDancer类继承于NSObject

  • 属性部分:

  • 构造函数

  • 公共实例方法 - (void)groupWith:(NSString *)name;

    • -:interface中带“-”前缀的方法,相当于C++中的public类型。该方法可以被实例对象调用,也可以被子类调用。所有声明过的实例方法,都需要在@implementation中被实现。
    • - (void):函数的返回值类型,此处为void
    • groupWith:(NSString *)name:函数的第一个参数
    • andWith:(NSString *)name2:函数的第二个参数
    • 调用方式:[对象名 groupWith:@"aaa" andWith:@"bbb"]
  • 公共类方法+ (int)staticFunction;

    • interface中带“+”前缀的方法,相当于C++中的static函数,该方法属于类,可以被类调用也可以被类对象调用。
    • 调用方式:[类名 staticFunction]

@implementation

@implementation必须实现@interface中所有的方法。

@implementation只能写在.m文件中,.m文件无法被引入,只能引入.h。

#import "ByteDancer.h"

@implementation ByteDancer

// 构造函数
- (instancetype)initWithName:(NSString *)name {
    self = [super init]; //调用父类的init
    if (self) {
        self.name = @"James";
    }
    NSLog(@"A ByteDancer Joined!");
    return self;
}

// 公共实例方法(public)
- (void)sayHello {
    NSLog(@"My name is %@", self.name);
}
- (void)groupWith:(NSString *)name {
    NSLog(@"Hi, I'm group with %@", name);
}
- (ByteDancer *)groupWith:(NSString *)name andWith:(NSString *)name2 {
    NSString *nameStr = [NSString stringWithFormat:@"%@,%@,%@", self.name, name, name2];
    ByteDancer *group = [[ByteDancer alloc] initWithName:nameStr];
    return group;
}

// 公共类方法(静态函数static)
+ (int)staticFunction {
    return 0;
}


// 私有方法(private):@interface中没有声明,仅在@implementation中定义的方法
- (void)private_method {
    // do something
}

@end
  • #import "ByteDancer.h":引入.h文件,实现其中的接口

  • 构造函数

    • self = [super init];调用父类的初始化函数,并赋给当前类实例。self为该类实例本身,相当于C++中的this。
    • self.name = @"James";由于name属性被@property修饰,会自动在@implementation中生成私有对象NSString *_namesettergetter方法。调用self.name = @"James";,相当于调用了setName方法。具体原理:www.jianshu.com/p/035977d1b…
    • return self;返回实例对象。
  • 私有方法 - (void)private_method

    • 在@interface中没有声明,仅在@implementation中定义的方法,为私有方法。
    • 相当于C++中的private,只对本类中的方法可见。

协议@protocol

协议 Protocol ,一般被用来定义一套公用的方法接口,而不实现这些方法。而别的类可以遵守这个协议,遵守协议之后就可以使用这些方法,方法分为 required 和 optional 两种,required 是遵守协议后必须实现的方法,optional 是可选的方法。

基本使用

  • 协议的定义
#import <Foundation/Foundation.h>

@protocol MyProtocol <NSObject>

- (void)myfunction:(NSString *)attr1 :(NSString *)attr2;
@end

MyProtocol :该协议遵循NSObject协议

  • 实现协议
//@interface部分用<>指明遵循的协议
@interface FirstClass : NSObject<MyProtocol>
@end

//@implementation实现协议中的函数
@implementation FirstClass
- (void)myfunction:(NSString *)attr1 :(NSString *)attr2 {
    NSString * s = [NSString stringWithFormat:@"%@  %@",attr1,attr2];
    NSLog(@"%@",s);
}
@end

协议的两种模式

  • 委托模式:信息通过协议中的方法参数从委托方流向代理方,或者事件发生时
    委托者通知代理者 ,简单表述为:委托方传递信息或者事件到代理方。
  • 数据源模式:数据源模式的信息流向与委托模式正好相反,委托方需要从代理方拉取数据。 简单表述为:代理方传递信息到委托方。

委托模式

juejin.cn/post/684490…

委托代理 Delegate ,代理实际上是一种设计模式,意为在 A 类中定义委托代理属性。在实例化 A 类的对象后可以让 B 类的对象成为其委托代理,这样就可以使用 A.delegate 来调用 B 中的方法了。

举一个简单的例子,A类 中有个方法,其作用为将两个数字进行操作,然后输出到控制台,但是没有定义如何操作这两个数,而是在输出之前调用了其协议中的方法,让遵守协议的类之中去实现这个方法。

也就是说可以在不修改A类的情况下,只修改成为其代理对象的类中所遵守的方法即可实现对两个数字操作的自定义,直接用代码更能说明问题:

首先我们先创建 AClass 来并创建协议

// 创建协议,定义协议名和父类
@protocol AClassDelegate <NSObject>         
// @requierd为必须实现的方法
@required                                   
-(int)doSomethingWithNumber1:(int)num1 andNumber2:(int)num2;
@end

然后我们还需要为 AClass 定义 delegate 属性和数字操作输出的方法。

@interface AClass : NSObject

// delegate属性
@property (nonatomic,weak) id<AClassDelegate> delegate;     

-(void)logAnswerWithNumber1:(int)num1 andNumber2:(int)num2;
@end

然后实现输出功能的方法

#import "AClass.h"

@implementation AClass
-(void)logAnswerWithNumber1:(int)num1 andNumber2:(int)num2{
    int answer = [self.delegate doSomethingWithNumber1:num1 andNumber2:num2];
    NSLog(@"%d",answer);
}
@end

这里可以看出,在这个方法中并没有对两个数进行任何操作而是调用了其协议方法,让遵守协议的对象来完成对数字的操作,然后输出返回值。

到这里协议就定义完了,接下来我们创建 BClass 来遵守协议,首先我们现在 BClass.h 文件中完成对协议的遵守:

#import <Foundation/Foundation.h>
#import "AClass.h"                              //需要先导入头文件
@interface BClass : NSObject <AClassDelegate>   //遵守协议

@end

然后我们在 BClass.m 文件中,实现协议中的方法,例如我们让两个数相加:

#import "BClass.h"

@implementation BClass 

-(int)doSomethingWithNumber1:(int)num1 andNumber2:(int)num2{
    return num1+num2;
}

@end

这样就遵守了协议。接着我们需要对两个类都进行实例化吗,然后让 BClass 的对象成为 AClass 的代理,并调用我们写好的输出方法:

AClass *a = [AClass new];
BClass *b = [BClass new];
    
a.delegate = b;         // b 成为 a 的代理
[a logAnswerWithNumber1:3 andNumber2:5]; //调用 a 的方法

运行后我们可以看到控制台输出了 8 。那么让我们修改 BClass 内的方法改为将两数相减:

#import "BClass.h"

@implementation BClass 

-(int)doSomethingWithNumber1:(int)num1 andNumber2:(int)num2{
    return num1-num2;
}

@end

可以看到控制台输出了 -2,也就是说我们在 AClass 内调用了 BClass 内的方法对两个数进行计算然后又传回到 AClass 内。这样可以让我们在保证不动 AClass 内代码的情况下,对 AClass 的功能进行修改(例如两数相加变为两数相减)。

数据源模式

www.codeleading.com/article/288…

  • 定义协议
// 与 delegate 一样,只是名字不一样罢了?
@protocol testDataSource <NSObject>
 
// 必须实现:
@required
- (NSInteger)numberOfLabels;
 
// 可选实现
@optional
- (NSString *)label:(UILabel *)label titleOfIndex:(NSInteger)index;
 
@end
 
@interface TestDataSource : UIView
@property (nonatomic, weak) id<testDataSource> dateSource;
// 此处 我真不知道,怎么样才能不调用,就实现布局。暂时只能类似tableView 的 reloadData 刷新UI。
- (void)reloadData;
@end
  • 使用datasource链接
- (void)reloadData{
    if (!self.dateSource) {
        return;
    }
    
    // 必选 实现 dataSource
    NSInteger number = [self.dateSource numberOfLabels];
    CGFloat height = CGRectGetHeight(self.bounds) / number;
    
    for (NSInteger a = 0; a < number; a ++) {
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, height * a, CGRectGetWidth(self.bounds), height)];
        [self addSubview:label];
        
        // 可选 实现 dataSource
        if ([self.dateSource respondsToSelector:@selector(label:titleOfIndex:)]) {
            NSString *title = [self.dateSource label:label titleOfIndex:a];
            label.text = title;
        }
    }
}
  • 使用dataSource 回调

参考资料

juejin.cn/post/684490…

www.codeleading.com/article/288…

www.jianshu.com/p/035977d1b…

juejin.cn/post/684490…