Objective-C类设计和数据封装 – Objective-C开发教程

上一章Objective-C开发教程请查看:Objective-C多态性和继承?
Objective-C支持面向对象编程(OOP),对类的设计尤为重要,首先就是数据封装了,数据封装主要是对外隐藏实现细节,按照惯例:

  • 属性一般设为私有,外部若需要访问,可以设计对应的方法进行间接访问,或者直接使用@property声明属性即可。
  • 对外提供功能的方法设为公有,提供内部使用的为私有,但是OC中没有私有方法,不过可以通过extension扩展实现,这在后面的章节会讨论到。
但是要注意,一般来说我们在实现算法的时候是会用到私有方法的,不能使用和公有方法相同的形式,既然OC是C的超集,那么我可以使用C的私有函数来实现OC的私有方法,C中使用static修饰的方法都是文件内部方法,只能在当前文件中可用,而且需要在调用它的函数之前声明,如下:
static void print(NSString *str){ NSLog(@"print: %@", str); }-(instancetype)init{ if(self = [super init]){ print(@"init ANLog......"); } return self; }

类的设计接着我想说下关于类的设计,以前刚刚开始学Java的时候,老是看到OOP强调的继承、封装和多态,实际上,你压根不用担心不会用到这些特性,只要你用的是OOP语言,自然书写类的时候已经是在进行标准的数据封装了,最好注意一下不需暴露的属性和方法设为私有即可。
这里我想说的是,类的设计难点不是要符合OOP编程规范,而是一般可能忽略掉数据结构和算法了。
对于一个类A,其中包含数据属性和方法,创建不同的A类对象的时候,其中的数据属性对于每个对象都占有一份内存空间,方法一般都是只有一份。
最简单的数据结构就是基本数据类型,如int、float、double等,这些数据结构已经隐含了可用的算法了:加减乘除等。我们对类的设计也是设计一种数据类型,那么也需要包含操作数据的算法。而又因为类的数据可能是复合类型的:也就是包含多个属性,那么我们可以得出:
  • 核心操作的属性数据成员,或者需要全局使用的属性,应该整体另外使用一个类封装——或者使用泛型NSObject或id代替。
  • 如有需要有关于数据结构的类封装,这是一个简单类,用于说明数据的基本结构。数据结构一般包含实际或泛型表示的数据。
  • 核心操作算法包含数据结构,并且可能有对应的数据成员,但是这里的成员一般与算法相关。
假设我们需要实现一个任务队列,用于顺序处理用户的请求,那么这里的核心数据就是用户的任务了,数据结构就是队列或优先队列,接着基本操作算法包括:
  • push,添加任务。
  • pop,取出任务。
  • process,处理任务。
对于实际的任务,我们可以使用一个block代码块来表示,或者使用函数指针也是没问题的。
数据封装实例首先第一步是设计任务,然后是数据结点,最后是算法封装类,声明代码如下:
#import < Foundation/Foundation.h>typedef void (^ANBlock)(); // 数据封装类 @interface ANTask : NSObject@property(nonatomic, assign)long tid; @property(nonatomic, copy)NSString *tname; @property(nonatomic, strong)ANBlock task; @end// 结点封装类 @interface ANTaskNode : NSObject@property(nonatomic)ANTask *task; @property(nonatomic)ANTaskNode *prev; @property(nonatomic)ANTaskNode *next; @end// 算法所在类 @interface ANTaskQueue : NSObject@property(nonatomic)ANTaskNode *head; @property(nonatomic)ANTaskNode *tail; @property(nonatomic, assign)int size; -(BOOL)isEmpty; -(void)pushTask: (ANBlock)task withId: (long)tid andName: (NSString*)tname; -(ANTask*)popTask; -(void)processTask: (ANTask*)task; @end

以上设计和声明并不算复杂,喜欢就好,千万不要为简单起见将所有东西都涉及在算法操作类中,否则将会非常之混乱,即使自己看得明白,别人看上去也不想看下去了。
接着就是代码实现的部分了,如果还没有数据结构和算法的继承的,可以参考本网站中的对应内容,其中相当详细地介绍了数据结构和算法的原理和实现。
// //ANTaskQueue.m //basic // //Created by cococ on 2020/1/4. //Copyright ? 2020年 cococ. All rights reserved. //#import "ANTaskQueue.h"@implementation ANTask-(instancetype)init{ if(self = [super init]){ _tid = -1; _tname = nil; _task = nil; } return self; }@end@implementation ANTaskNode-(instancetype)init{ if(self = [super init]){ _prev = _next = nil; _task = [[ANTask alloc]init]; } return self; }@end@implementation ANTaskQueue-(instancetype)init{ if(self = [super init]){ _head = _tail = nil; _size = 0; } return self; }-(BOOL)isEmpty{ return _size == 0; }static ANTaskNode* taskNode(ANBlock task, long tid, NSString *tname){ ANTaskNode *node = [[ANTaskNode alloc]init]; node.task.tid = tid; node.task.tname = tname; node.task.task = task; return node; }-(void)pushTask: (ANBlock)task withId: (long)tid andName: (NSString*)tname{ if(task == nil || tname == nil){ NSLog(@"task or tname is nil"); return; } ANTaskNode *node = taskNode(task, tid, tname); if(_size == 0){ node.prev = node.next = node; _head = _tail = node; _size++; } else{ node.prev = _tail; node.next = _head; _tail.next = node; _head.prev = node; _tail = node; _size++; } }-(ANTask*)popTask{ if(_size == 0) return nil; ANTaskNode *head = _head; ANTask *task = head.task; head.prev.next = head.next; head.next.prev = head.prev; _head = head.next; head = nil; _size--; return task; }-(void)processTask: (ANTask*)task{ NSLog(@"id: %ld", task.tid); NSLog(@"name: %@", task.tname); task.task(); NSLog(@""); }@end

【Objective-C类设计和数据封装 – Objective-C开发教程】最后就是测试用例了,先添加任务,然后使用while循环逐个拿出来进行处理:
void testTaskQueue(void){ ANTaskQueue *taskQueue = [[ANTaskQueue alloc]init]; [taskQueue pushTask:^(){ NSLog(@"Task A: Get Message From Server......."); } withId:54656 andName:@"Server Request"]; [taskQueue pushTask:^(){ NSLog(@"Task B: Post Info And Login......."); } withId:3249 andName:@"User Login"]; [taskQueue pushTask:^(){ NSLog(@"Task C: Enter User Info Detail......."); } withId:8418 andName:@"Visit Info"]; [taskQueue pushTask:^(){ NSLog(@"Task D: Timer Task......."); } withId:78499 andName:@"Timer"]; while (![taskQueue isEmpty]) { [taskQueue processTask:[taskQueue popTask]]; } }int main(int argc, const char * argv[]) { testTaskQueue(); return 0; }

    推荐阅读