简易NewsDemo代码分析

界面布局就不多说了.效果图:(自定义了三套Cell,最后一套Cell是页面最下方的"正在加载中..."Cell,图三.)

       

主要分析工程目录和流程.

第一:Helper中的负责请求数据的网络引擎类.

网络请求类NetworkEngine主要完成数据请求的任务.用来简化控制器中的代码.将数据单独存放.

实现步骤分析:

.h文件中:

1.分析完成网络请求需要:①请求的网址 ②请求的参数 ③请求的方式(GET或者POST).

那么将这三个定义为属性.请求参数只有两种,所以定义为枚举类型.然后将请求数据和结束请求数据写成两个方法.

//
// NetworkEngine.h
// NewsDemo #import <Foundation/Foundation.h>
/**
* NetworkEngine 是网络引擎类,完成数据的网络请求操作.
*/ @class NetworkEngine;
@protocol NetworkEngineDelegate <NSObject>
//协议中添加两个方法,一个对应请求数据成功的方法,一个对应请求数据失败的方法
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data;
- (void)networkEngine:(NetworkEngine *)networkEngine requestFailWithData:(NSError *)error;
@end //请求方式(枚举类型)
typedef enum RequsetMethod {
RequsetMethodGET,
RequsetmethodPOST
}RequestMethod; @interface NetworkEngine : NSObject
@property (nonatomic, copy) NSString *linkName; //请求服务器地址
@property (nonatomic, retain) NSDictionary *parmDic; //参数字典,存储所有的请求参数.
@property (nonatomic, assign) RequestMethod requestMethod; //请求方式 @property (nonatomic, assign) id<NetworkEngineDelegate> delegate; //指定代理 //开始请求数据
- (void)startDownloadData;
//结束请求数据
- (void)stopDownloadData; @end

.m文件中:

2.有了服务器址,参数和请求方式,下面开始请求数据.但是前提是要把服务器地址装化为NSURL格式.这里就要根据请求方式来判断是否需要将参数拼接到服务器地址后面了.

GET方式的请求需要在服务器地址后,用?隔开,然后加上请求的参数.例如服务器地址为:

//API 网络接口
#define kNewsAPI @"http://m2.qiushibaike.com/article/list/text"

那么GET请求就需要加上参数变为

http://m2.qiushibaike.com/article/list/text?count=30&page=%25d&AdID=14314020462881C8509990
参数为一个字典, @{count:30,page:25,ADID:14314020462881C8509990}

下面就是判断请求方式和实现参数拼接的代码实现了,这里不详述了.

3.将服务器地址转为NSURL对象之后,下一步创建请求.由于不确定是GET还是POST,这里就用通用的创建请求方式,也是POST的请求方式

//GET创建请求方式(不可变URLRequest)
NSURLRequest *request = [NSURLRequset requestWithURL:]; //POST创建请求方式(可变MutableURLRequest)
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:];

4.如果是POST方式,还要设置请求体(就是GET方式的URL中?后面的参数)和请求方式.

[request setHTTPBody:[[self appendStringByParDic] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:@"POST"];

5.下一步创建连接对象(使用代理方式),将连接对象定义成属性,便于在结束方法中访问.

连接对象创建完毕,就可以连接服务器请求数据,(这里定义一个属性NSMutableData *mData,存储从服务器放回的所有数据.) 这里还要实现代理中的四个方法.

①:didReceiveResponse:收到服务器响应,这时候为mData开辟空间.

②:didReceiveData:开始从服务器接收数据.(一点一点传送),这时候需要进行数据拼接.

③:connectionDidFinishLoading:接收数据完毕. 关键点:这时候NetworkEngine的获取数据的工作已经完成了,它需要能够将数据发送给控制器.怎么发送呢?自然要使用代理了.

在.h文件中定义协议,并且添加方法.然后指定代理.

//协议中添加两个方法,一个对应请求数据成功的方法,一个对应请求数据失败的方法
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data;
- (void)networkEngine:(NetworkEngine *)networkEngine requestFailWithData:(NSError *)error;

并且在.m文件中的connectionDidFinishLoading方法中,给代理指定执行时机.(这本来是使用代理的最后一步,这里提前说.).

    //通知代理执行协议中请求数据成功的方法
if ([self.delegate respondsToSelector:@selector(networkEngine:requestSuccessWithData:)]) {
[self.delegate networkEngine:self requestSuccessWithData:self.mData];
}

到此,网络请求完成.

//  NetworkEngine.m
// NewsDemo #import "NetworkEngine.h" @interface NetworkEngine ()<NSURLConnectionDataDelegate>
@property (nonatomic, retain) NSURLConnection *connection; //存储连接对象
@property (nonatomic, retain) NSMutableData *mData; //存储服务器返回的所有数据
@end @implementation NetworkEngine //开始请求数据
- (void)startDownloadData {
//1.创建NSURL对象
NSURL *url = [NSURL URLWithString:[self urlString]];
//2.创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
if (self.requestMethod == RequsetmethodPOST) {
//如果是POST请求,就需要设置请求体,以及请求方式.
[request setHTTPBody:[[self appendStringByParDic] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:@"POST"];
}
//3.创建连接对象(Delegate方式)
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
} //结束请求数据
- (void)stopDownloadData {
[self.connection cancel]; //中断连接
} #pragma mark - handle
/**
* 获取网址字符串对象
*/
- (NSString *)urlString {
//保存服务器地址
NSString *newLinkName = nil;
//判断请求方式
switch (self.requestMethod) {
case RequsetMethodGET: //GET请求,需要拼接上参数
newLinkName = [self urlStringByGET];
break;
case RequsetmethodPOST: //POST请求,无需拼接
newLinkName = self.linkName;
break;
default:
break;
}
return newLinkName;
} //GET请求获取请求的网址字符串对象
- (NSString *)urlStringByGET {
//判断参数字段中是否有参数
if (!self.parmDic) {
//如果没有参数,则不需要拼接,直接返回服务器地址即可.
return self.linkName;
}
//如果有参数,则需要拼接
return [[self.linkName stringByAppendingFormat:@"?%@", [self appendStringByParDic]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
} //将字典中的所有参数拼接成字符串
- (NSString *)appendStringByParDic {
//1.创建可变数组,存储字典中的key:Value元素
NSMutableArray *tempArr = [NSMutableArray arrayWithCapacity:];
//2.遍历字典,将key:value两部分合成一个字符串对象
for (NSString *key in self.parmDic) {
NSString *newStr = [NSString stringWithFormat:@"%@=%@", key, self.parmDic[key]];
[tempArr addObject:newStr];
}
//3.将数组中的所有元素拼接成一个字符串,中间通过&间隔.
return [tempArr componentsJoinedByString:@"&"];
} #pragma mark - NSURLConnectionDataDelegate //收到服务器响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//开辟空间
self.mData = [NSMutableData data];
} //收到服务器数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//拼接数据
[self.mData appendData:data];
}
//数据传输完毕
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//通知代理执行协议中请求数据成功的方法
if ([self.delegate respondsToSelector:@selector(networkEngine:requestSuccessWithData:)]) {
[self.delegate networkEngine:self requestSuccessWithData:self.mData];
} }
//连接失败
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//通知代理执行协议中请求数据失败的方法
if ([self.delegate respondsToSelector:@selector(networkEngine:requestFailWithData:)]) {
[self.delegate networkEngine:self requestFailWithData:error];
}
} - (void)dealloc {
[_linkName release];
[_parmDic release];
[_connection release];
[_mData release];
[super dealloc];
} @end

接下来呢?我们需要回到视图控制器中,在这里,我们会给NetworkEngine指定服务器地址和参数,让它去真正的请求数据,我们在视图控制器中接收数据并且进行解析,将解析的数据封装成一个一个Model对象存储起来,供tableView显示.

所以首先我们还是先建立Model对象吧,非常简单,我们需要让Cell显示什么数据,就在这个Model中定义什么属性,当然,也有一些东西需要注意.

1.在Models工程文件夹中建立News类

.h文件如下:

//  News.h
// NewsDemo #import <Foundation/Foundation.h>
/*
{
"template": "manual",
"hasCover": false,
"hasHead": 1,
"skipID": "54GK0096|65860",
"replyCount": 2785,
"alias": "Entertainment",
"hasImg": 1,
"digest": "",
"hasIcon": true,
"skipType": "photoset",
"cid": "C1348648351901",
"docid": "9IG74V5H00963VRO_AQ01DJ36zongssupdateDoc",
"title": "[广角镜]何炅的“老师”生涯全回顾",
"hasAD": 1,
"order": 1,
"imgextra": [
{
"imgsrc": "http://img2.cache.netease.com/3g/2015/5/19/201505191431056e75c.jpg"
},
{
"imgsrc": "http://img4.cache.netease.com/3g/2015/5/19/201505191431082bd04.jpg"
}
],
"priority": 200,
"lmodify": "2015-05-19 14:16:05",
"ename": "yule",
"tname": "娱乐",
"imgsrc": "http://img6.cache.netease.com/ent/2015/5/19/2015051914225773b5e.jpg",
"photosetID": "54GK0096|65860",
"ptime": "2015-05-19 14:16:05"
},
*/ @interface News : NSObject
@property (nonatomic, copy) NSString *title; //标题
@property (nonatomic, copy) NSString *digest; //简介
@property (nonatomic, copy) NSString *imgsrc; //图片链接
@property (nonatomic, retain) NSArray *imgextra; //另外两个图片的链接,可能有,可能没有
@property (nonatomic, assign) NSInteger flag; //用来标识当前数据需要那一套Cell来展示,如果为0,对应NewsCell,为1,对应imageCell. - (instancetype)initWithDictionary:(NSDictionary *)dic; @end

上面注释中的内容是我们从网易服务器获取到的网易新闻的新闻源数据,我们做的Demo就是基于此,由于这里我们只需要显示标题,简介,和图片,别的暂时不需要.

所以我们定义属性:标题, 简介, 左侧的图片(第一套NewsCell显示), 多图显示时候的图片(第二套ImageCell显示), 以及用来标示当前数据需要哪一套Cell显示的flag.

因为News中存储的是一条新闻的信息,所以我们还需要添加一个初始化方法,这样在视图控制器中,才能将解析出来的一个新闻数据通过初始化赋值给一个News对象.供Cell来使用,取出需要的数据.

.m文件如下:

- (instancetype)initWithDictionary:(NSDictionary *)dic {
self = [super init];
if (self) {
//KVC赋值
[self setValuesForKeysWithDictionary:dic];
//需要判断属性imgextra有没有赋值,如果赋值了,说明此时含有三张图片链接,则使用ImageCell展示.否则使用NewsCell
if (self.imgextra) {
self.flag = ; //使用ImageCell
} else {
self.flag = ; //使用NewsCell
}
}
return self;
}
//为没有对应的属性的Key实现此方法,防止程序crash
- (void)setValue:(id)value forUndefinedKey:(NSString *)key { } + (id)newsWithDictionary:(NSDictionary *)dic {
return[[[self alloc] initWithDictionary:dic] autorelease];
}

使用KVC赋值,安全起见,一定要调用一下方法,为没有对应属性的key实现此方法.

News创建完毕,回到控制器中.接上第一步,给NetworkEngine指定服务器地址和参数,让它去真正的请求数据,我们在视图控制器中接收数据并且进行解析,将解析的数据封装成一个一个Model对象存储起来,供tableView显示.

部分实现代码如下.

@interface NewsListController ()<NetworkEngineDelegate>
@property (nonatomic, retain) NSMutableArray *dataSource; //存储所有的News对象 @end @implementation NewsListController //懒加载数组
- (NSMutableArray *)dataSource {
if (!_dataSource) {
self.dataSource = [NSMutableArray arrayWithCapacity:];
}
return [[_dataSource retain] autorelease];
} //请求新闻数据
- (void)requsetData {
NetworkEngine *engine = [[NetworkEngine alloc] init];
//设置请求网址
engine.linkName = [NSString stringWithFormat:kNewsAPI, ];//0 - 20条数据
//设置请求方式
engine.requestMethod = RequsetMethodGET; //GET请求方式
//设置代理
engine.delegate = self;
//开始网络请求
[engine startDownloadData];
[engine release];
} #pragma mark - NetworkEngineDelegate
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data {
//解析数据
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
//根据Key获取存储新闻的数组
NSArray *results = dic[@"T1348648517839"];
//将数组中的每个小字典封装成NewsModel对象
for (NSDictionary *dic in results) {
News *news = [[News alloc] initWithDictionary:dic];
//添加到数组中
[self.dataSource addObject:news];
}
//让tableView刷新数据
[self.tableView reloadData]; }
- (void)networkEngine:(NetworkEngine *)networkEngine requestFailWithData:(NSError *)error { } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return ;
} - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSUInteger count = self.dataSource.count;
return count ? count + : ; //因为有最后一个"上拉加载更多的Label",如果没有请求数据成功,那就不展示Cell.
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //如果是最后一行,则显示加载Cell
if (indexPath.row == self.dataSource.count) {
LoadingCell *cell = [tableView dequeueReusableCellWithIdentifier:kLoadingCellReuseIdentifier forIndexPath:indexPath];
return cell;
} News *news = self.dataSource[indexPath.row];
if (news.flag == ) {
//为0使用NewsCell
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:kNewsCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
} else {
//为1,使用imageCell
ImageCell *cell = [tableView dequeueReusableCellWithIdentifier:kImageCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
} } //动态设置每一行Cell的高度
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//对于NewsCell,高度为80,ImageCell为100, loadingCell为30 //如果索引和元素个数相同,说明是最后一行
if (indexPath.row == self.dataSource.count) {
return ;
} News *news = self.dataSource[indexPath.row];
if (news.flag == ) {
return ;
} else {
return ;
}
}

分析:

第一步,肯定是请求新闻数据.给NetworkEngine设定服务器地址,参数,请求方式等.这里写成一个方法,不要忘了在viewDidLoad中调用.

既然请求了数据,我们就要接收,怎么接收呢?我们已经在NetworkEngine中定义了代理属性,就是用来传数据的,所以这里设置控制器为代理对象,并且实现协议中的方法,将接收到的数据进行解析.

//请求新闻数据
- (void)requsetData {
NetworkEngine *engine = [[NetworkEngine alloc] init];
//设置请求网址
engine.linkName = [NSString stringWithFormat:kNewsAPI, ];//0 - 20条数据
//设置请求方式
engine.requestMethod = RequsetMethodGET; //GET请求方式
//设置代理
engine.delegate = self;
//开始网络请求
[engine startDownloadData];
[engine release];
}

在协议方法中进行数据解析:(因为协议方法中传过来的是接收完毕后的数据,所以这里进行解析.)(已知为JSON数据,并且最外层为一个字典).

我们需要先定义一个属性dataSource,用来存储解析之后并封装成News对象的数据.

#pragma mark - NetworkEngineDelegate
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data {
//解析数据
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
//根据Key获取存储新闻的数组
NSArray *results = dic[@"T1348648517839"];
//将数组中的每个小字典封装成NewsModel对象
for (NSDictionary *dic in results) {
News *news = [[News alloc] initWithDictionary:dic];
//添加到数组中
[self.dataSource addObject:news];
}
//让tableView刷新数据
[self.tableView reloadData]; }

到这里,数据解析也已经完成,存储在dataSource中,剩下的工作就是让每一套Cell显示它应该显示的数据.

这里可以很方便的利用我们之前在News中定义的flag属性来进行判断.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSUInteger count = self.dataSource.count;
return count ? count + : ; //因为有最后一个"上拉加载更多的Label",如果没有请求数据成功,那就不展示Cell.
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //如果是最后一行,则显示加载Cell
if (indexPath.row == self.dataSource.count) {
LoadingCell *cell = [tableView dequeueReusableCellWithIdentifier:kLoadingCellReuseIdentifier forIndexPath:indexPath];
return cell;
} News *news = self.dataSource[indexPath.row];
if (news.flag == ) {
//为0使用NewsCell
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:kNewsCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
} else {
//为1,使用imageCell
ImageCell *cell = [tableView dequeueReusableCellWithIdentifier:kImageCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
}
}

最后,需要说明的时是,在自定义Cell的时候,我们使用了第三方的显示图片工具EGOImageView.它为我们提供了方便的显示图片方法,直接将解析出来的NSData数据中存储的图片的NSURL显示成图片.

实例:

.h文件

#import <UIKit/UIKit.h>
#import "EGOImageView.h"
#import "News.h" @interface ImageCell : UITableViewCell
@property (nonatomic, retain) UILabel *titleLabel; //标题
@property (nonatomic, retain) EGOImageView *leftImageView;
@property (nonatomic, retain) EGOImageView *centerImageView;
@property (nonatomic, retain) EGOImageView *rightImageView; //赋值
- (void)configureCellWithNews:(News *)news;
@end .m文件
//赋值
- (void)configureCellWithNews:(News *)news {
//左边的图片
self.leftImageView.imageURL = [NSURL URLWithString:news.imgsrc];
//中间的图片
self.centerImageView.imageURL = [NSURL URLWithString:news.imgextra[][@"imgsrc"]];
//右边的图片
self.rightImageView.imageURL = [NSURL URLWithString:news.imgextra[][@"imgsrc"]];
//Title标题
self.titleLabel.text = news.title;
}

OVER.

自己还是不够熟练,不能够非常熟练的使用MVC,界面之间的数据传输是弱项,以后还要多加练习啊!

iOS学习之网易新闻简易Demo的更多相关文章

  1. iOS界面-仿网易新闻左侧抽屉式交互 续(添加新闻内容页和评论页手势)

     本文转载至  http://blog.csdn.net/totogo2010/article/details/8637430       1.介绍 有的博友看了上篇博文iOS界面-仿网易新闻左侧抽屉 ...

  2. IOS之分析网易新闻存储数据(CoreData的使用,增删改查)

    用过网易新闻客户端的朋友们都知道,获取新闻列表时有的时候他会请求网络有时候不会,查看某条新闻的时候再返回会标注已经查看的效果,接下来分析一下是如何实现的. 首先: 1.网易新闻用CoreData存储了 ...

  3. IOS开发--仿制网易新闻

    学习来源:袁峥老师的<快速集成App中顶部标题滚动条> 此次博文写的是按需求分析写代码,思路条理性杠杠的,可以提高的编码实现速度哦. 效果:   根据这个网易新闻的界面,需求分析:     ...

  4. 【转】 iOS开发 剖析网易新闻标签栏视图切换(addChildViewController属性介绍)

    原文:http://blog.csdn.net/hmt20130412/article/details/34523235 本来只是打算介绍一下addChildViewController这个方法的,正 ...

  5. iOS开发 剖析网易新闻标签栏视图切换(addChildViewController属性介绍)

    本文转载至 http://www.tuicool.com/articles/3ymMzub CSDN博客原文  http://blog.csdn.net/hmt20130412/article/det ...

  6. iOS 网易新闻用到的框架

    网易新闻iOS版在开发过程中曾经使用过的第三方开源类库.组件 1.AFNetworking AFNetworking 采用 NSURLConnection + NSOperation, 主要方便与服务 ...

  7. iOS仿网易新闻栏目拖动重排添加删除效果

    仿网易新闻栏目选择页面的基本效果,今天抽了点时间教大家如何实现UICollectionView拖动的效果! 其实实现起来并不复杂,这里只是基本的功能,没有实现细节上的修改,连UI都是丑丑的样子,随手画 ...

  8. 网易新闻iOS版使用的18个开源组件

    转载来自:http://www.jianshu.com/p/8952944f7566  原文最后编辑时间:2015.05.19 网易新闻iOS版在开发过程中曾经使用过的第三方开源类库.组件 1.AFN ...

  9. Android(java)学习笔记206:利用开源SmartImageView优化网易新闻RSS客户端

    1.我们自己编写的SmartImageView会有很多漏洞,但是我们幸运的可以在网上利用开源项目的,开源项目中有很多成熟的代码,比如SmartImageView都编写的很成熟的 国内我们经常用到htt ...

随机推荐

  1. 转:VC中WORD,DWORD,unsigned long,unsigned short的区别(转)

    typedef unsigned long       DWORD;typedef int                 BOOL;typedef unsigned char       BYTE; ...

  2. (摘)Zebra打印机异常处理

    一.一般条码打印设备按图指示方向,虚线为碳带安装路径,实线为标签路径.回卷后废碳带不易剥落,则在装入前用废标签的光滑底纸卷在回卷轴上,然后再上碳带.安装标签时,根据不同标签宽度调整限纸器.压头弹簧均匀 ...

  3. ural 1494 Monobilliards

    #include <cstdio> #include <cstring> #include <algorithm> using namespace std; ],b ...

  4. C++11之后,对源代码增加了UTF8和UCS4的支持(Windows内部使用Unicode,因为nt内核用的是ucs2,那是89年,utf8到了92年才发明出来)

    在C++编程中, 我们常打交道的无非是编辑器和编译器, 对编辑器起来说,我们常遇到就是乱码问题, 比如中文注释显示或是保存不了等, 解决办法就是把你的文件保存成Unicode(UTF8). 对于编译器 ...

  5. Host myCloudData.net on your own server (支持自建服务器)

    http://www.myclouddata.net/#/home Host myCloudData.net on your own serverUse the myCloudData.net SDK ...

  6. 通过Linux系统Cron执行OwnCloud计划任务

    通过Linux系统Cron执行OwnCloud计划任务 02/02/2013 CRON的确是一个非常有用的功能,它有效减少了系统的负载,在将WordPress和StatusNet的任务计划都转换到Cr ...

  7. 如何获取一个AlertDialog中的EditText中输入的内容

    怎么获取一个AlertDialog中的EditText中输入的内容? new AlertDialog.Builder(this)   .setTitle("请输入")   .set ...

  8. 用QtWebKit开发简单的浏览器

    用QtWebKit开发简单的浏览器 1.代码实现 工程目录结构如下: AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接. 当回车和点击事件发生时 ...

  9. SQL-Delete Duplicate Emails

    Write a SQL query to delete all duplicate email entries in a table named Person, keeping only unique ...

  10. [Qt] IP地址输入框实现

    封装了一个ip地址的输入框.网络上下载了份代码,找不到哪里的了.经过修改之后,尽力让它的行为和windows的IP地址输入框的行为看起来像些.代码如下: //ipaddredit.h #ifndef ...