一、说明
之前有次机会开发iOS系统的邮件系统,本着学习的心态查阅了很多资料,发现大家普遍都是基于Mailcore2开发。于是跟风之。但是在浏览了各种论坛和博客之后发现关于Mailcore2的使用方面的材料真是少的可怜,偶尔有一两篇也都是重复的转载。最后磕磕绊绊总算是基本完善的满足了整个邮件系统的基本功能,这里对于邮件系统的其他方面暂不做说明,只说我在使用Mailcore2的过程中的一些心得,希望能帮助到更多的朋友。
二、使用准备
首先第一步,你想使用Mailcore2必须得先引入它,开始的时候也是各种照着晚上的流程去做,主流的做法有两种,一种是把Mailcore2的project作为依赖工程引入编译,第二种是通过CocoaPods来引入。关于第一种方法本人硬是花费了一整天的功夫才编译成功。各种心酸不必细说,由于项目完结有一段时间了,有些细节也已经想不起来,如果各位看官有需要还请自己Google。我这里最后是使用的CocoaPods来引入,同样,CocoaPods的介绍,安装使用等这里也不再赘述,使用CocoaPods引入Mailcore2的代码为:
pod 'mailcore2-ios'
这些在Mailcore2的github主页都有说明,包括简单的Mailcore2的使用。有兴趣的朋友可以自己去看
https://github.com/MailCore/mailcore2/
</pre><p><strong>三、IMAP使用流程</strong></p><p><span style="white-space:pre"></span><span style="white-space:pre"></span>引入了Mailcore2的代码之后下面就是使用它了,Mailcore2支持IMAP,POP,SMTP几种主流的邮件协议,我的项目中收取邮件使用的IMAP协议,这里重点介绍IMAP协议在使用过程中的一些容易出问题的地方。按照流程,邮件系统在使用之初要进行账号的验证,主流验证代码如下:</p><pre name="code" class="objc"> self.imapSession = [self init];
self.imapSession.hostname = hostname;
self.imapSession.port = 143;//NON SSL
//self.imapSession.port = 993; //SSL
self.imapSession.username = username;
self.imapSession.password = password;
if (oauth2Token != nil) {
self.imapSession.OAuth2Token = oauth2Token;
self.imapSession.authType = MCOAuthTypeXOAuth2;
}
self.imapSession.connectionType = MCOConnectionTypeClear;
//self.imapSession.connectionType = MCOConnectionTypeStartTLS; //SSL
self.imapSession.connectionLogger = ^(void * connectionID, MCOConnectionLogType type, NSData * data) {
};
self.imapCheckOp = [self.imapSession checkAccountOperation];
[self.imapCheckOp start:^(NSError *error) {
if (error == nil) {
if (loadUserResultBlock) {
loadUserResultBlock(YES);
}
} else {
if (loadUserResultBlock) {
loadUserResultBlock(NO);
}
}
}];
这里容易出的问题就是端口和域名不匹配的问题,一定要看好自己使用的是SSL加密传输还是普通传输,并且修改对应的端口和域名。
在账号验证通过之后就可以进行邮件的拉取等各种操作了,比如拉取某个文件夹下面的邮件信息了。在Mailcore2中,拉取某个文件夹下面的邮件信息主要使用
- (MCOIMAPFolderInfoOperation *) folderInfoOperation:(NSString *)folder;
而这里的一个坑就是,如果你的文件夹是INBOX,这里可能还能正常拉取,如果是“发件箱”等文件夹呢?你会发现拉取失败,这是为什么呢,原来Mailcore2使用了默认的defaultnamespace,所以需要经过转换之后才可以:
self.folderName = [self.imapSession.defaultNamespace pathForComponents:@[folder]];
</pre><pre name="code" class="objc">MCOIMAPFolderInfoOperation *folderInfo = [self.imapSession folderInfoOperation:self.folderName];
[folderInfo start:^(NSError *error, MCOIMAPFolderInfo *info) {
int count = info.messageCount;//文件夹下邮件总条数
}];
在拉取到邮件总条数的时候你就可以根据你当前已经缓存的邮件数和总邮件数来自定义你要拉取新的邮件的Range了
static MCOIMAPMessagesRequestKind requestKind = (MCOIMAPMessagesRequestKind)
(MCOIMAPMessagesRequestKindHeaders | MCOIMAPMessagesRequestKindStructure |
MCOIMAPMessagesRequestKindInternalDate | MCOIMAPMessagesRequestKindHeaderSubject |
MCOIMAPMessagesRequestKindFlags);
MCOIMAPFetchMessagesOperation *op = [self.imapSession
fetchMessagesByNumberOperationWithFolder:self.folderName
requestKind:requestKind
numbers:[MCOIndexSet indexSetWithRange:fetchRange]];
[op start:^(NSError *error, NSArray *messages, MCOIndexSet *vanishedMessages) {
int count = messages.count;
for (int i = 0;i < count;i ++) {
MCOIMAPMessage *msg = messages[i];
EmailInfo *info = [EmailInfo new];
info.mailId = [NSString stringWithFormat:@"%d",msg.uid];
info.subject = [self stringReplaceNil:msg.header.subject];
info.name = [self stringReplaceNil:(msg.header.from.displayName?msg.header.from.displayName:[self mailBox2Display:msg.header.from.mailbox])];
info.sendTime = [self stringReplaceNil:[self dateFormatString:msg.header.receivedDate]];
info.attach = msg.attachments.count;
}
}];
这里的RequestKind为你想拉取的邮件信息类型,一般是邮件除了正文以外的一些信息,包括邮件头,邮件flags等等。注意:这里获取到的邮件信息是不包含正文信息的,所以你还需要单独的为每一封邮件获取正文信息。但是如果你们的邮件系统需求是在列表中显示正文简介的话,那么等到获取到邮件内容所有content之后再显示无疑是十分缓慢的一个过程,因为正文信息中很多时候会包含附件信息等等。而Mailcore2提供了几个获取正文Render的函数供你选择。
/** @name Rendering Operations */
/**
Returns an operation to render the HTML version of a message to be displayed in a web view.
MCOIMAPMessageRenderingOperation * op = [session htmlRenderingOperationWithMessage:msg
folder:@"INBOX"];
[op start:^(NSString * htmlString, NSError * error) {
...
}];
*/
- (MCOIMAPMessageRenderingOperation *) htmlRenderingOperationWithMessage:(MCOIMAPMessage *)message
folder:(NSString *)folder;
/**
Returns an operation to render the HTML body of a message to be displayed in a web view.
MCOIMAPMessageRenderingOperation * op = [session htmlBodyRenderingOperationWithMessage:msg
folder:@"INBOX"];
[op start:^(NSString * htmlString, NSError * error) {
...
}];
*/
- (MCOIMAPMessageRenderingOperation *) htmlBodyRenderingOperationWithMessage:(MCOIMAPMessage *)message
folder:(NSString *)folder;
/**
Returns an operation to render the plain text version of a message.
MCOIMAPMessageRenderingOperation * op = [session plainTextRenderingOperationWithMessage:msg
folder:@"INBOX"];
[op start:^(NSString * htmlString, NSError * error) {
...
}];
*/
- (MCOIMAPMessageRenderingOperation *) plainTextRenderingOperationWithMessage:(MCOIMAPMessage *)message
folder:(NSString *)folder;
/**
Returns an operation to render the plain text body of a message.
All end of line will be removed and white spaces cleaned up if requested.
This method can be used to generate the summary of the message.
MCOIMAPMessageRenderingOperation * op = [session plainTextBodyRenderingOperationWithMessage:msg
folder:@"INBOX"
stripWhitespace:YES];
[op start:^(NSString * htmlString, NSError * error) {
...
}];
*/
- (MCOIMAPMessageRenderingOperation *) plainTextBodyRenderingOperationWithMessage:(MCOIMAPMessage *)message
folder:(NSString *)folder
stripWhitespace:(BOOL)stripWhitespace;
/**
Returns an operation to render the plain text body of a message.
All end of line will be removed and white spaces cleaned up.
This method can be used to generate the summary of the message.
MCOIMAPMessageRenderingOperation * op = [session plainTextBodyRenderingOperationWithMessage:msg
folder:@"INBOX"];
[op start:^(NSString * htmlString, NSError * error) {
...
}];
*/
- (MCOIMAPMessageRenderingOperation *) plainTextBodyRenderingOperationWithMessage:(MCOIMAPMessage *)message
folder:(NSString *)folder;
/** @name Fetching Attachment Operations */
/**
Returns an operation to fetch an attachment.
@param urgent is set to YES, an additional connection to the same folder might be opened to fetch the content.
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentByUIDOperationWithFolder:@"INBOX"
uid:456
partID:@"1.2"
encoding:MCOEncodingBase64
urgent:YES];
[op start:^(NSError * error, NSData * partData) {
...
}];
*/
- (MCOIMAPFetchContentOperation *) fetchMessageAttachmentByUIDOperationWithFolder:(NSString *)folder
uid:(uint32_t)uid
partID:(NSString *)partID
encoding:(MCOEncoding)encoding
urgent:(BOOL)urgent DEPRECATED_ATTRIBUTE;
/**
Returns an operation to fetch an attachment.
Example 1:
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentByUIDOperationWithFolder:@"INBOX"
uid:456
partID:@"1.2"
encoding:MCOEncodingBase64];
[op start:^(NSError * error, NSData * partData) {
...
}];
Example 2:
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentByUIDOperationWithFolder:@"INBOX"
uid:[message uid]
partID:[part partID]
encoding:[part encoding]];
[op start:^(NSError * error, NSData * partData) {
...
}];
*/
- (MCOIMAPFetchContentOperation *) fetchMessageAttachmentByUIDOperationWithFolder:(NSString *)folder
uid:(uint32_t)uid
partID:(NSString *)partID
encoding:(MCOEncoding)encoding DEPRECATED_ATTRIBUTE;
/**
Returns an operation to fetch an attachment.
@param urgent is set to YES, an additional connection to the same folder might be opened to fetch the content.
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentOperationWithFolder:@"INBOX"
uid:456
partID:@"1.2"
encoding:MCOEncodingBase64
urgent:YES];
[op start:^(NSError * error, NSData * partData) {
...
}];
*/
- (MCOIMAPFetchContentOperation *) fetchMessageAttachmentOperationWithFolder:(NSString *)folder
uid:(uint32_t)uid
partID:(NSString *)partID
encoding:(MCOEncoding)encoding
urgent:(BOOL)urgent;
/**
Returns an operation to fetch an attachment.
Example 1:
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentOperationWithFolder:@"INBOX"
uid:456
partID:@"1.2"
encoding:MCOEncodingBase64];
[op start:^(NSError * error, NSData * partData) {
...
}];
Example 2:
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentOperationWithFolder:@"INBOX"
uid:[message uid]
partID:[part partID]
encoding:[part encoding]];
[op start:^(NSError * error, NSData * partData) {
...
}];
*/
- (MCOIMAPFetchContentOperation *) fetchMessageAttachmentOperationWithFolder:(NSString *)folder
uid:(uint32_t)uid
partID:(NSString *)partID
encoding:(MCOEncoding)encoding;
/**
Returns an operation to fetch an attachment.
@param urgent is set to YES, an additional connection to the same folder might be opened to fetch the content.
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentOperationWithFolder:@"INBOX"
uid:456
partID:@"1.2"
encoding:MCOEncodingBase64
urgent:YES];
[op start:^(NSError * error, NSData * partData) {
...
}];
*/
- (MCOIMAPFetchContentOperation *) fetchMessageAttachmentOperationWithFolder:(NSString *)folder
number:(uint32_t)number
partID:(NSString *)partID
encoding:(MCOEncoding)encoding
urgent:(BOOL)urgent;
/**
Returns an operation to fetch an attachment.
Example 1:
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentOperationWithFolder:@"INBOX"
number:42
partID:@"1.2"
encoding:MCOEncodingBase64];
[op start:^(NSError * error, NSData * partData) {
...
}];
Example 2:
MCOIMAPFetchContentOperation * op = [session fetchMessageAttachmentOperationWithFolder:@"INBOX"
number:[message sequenceNumber]
partID:[part partID]
encoding:[part encoding]];
[op start:^(NSError * error, NSData * partData) {
...
}];
*/
- (MCOIMAPFetchContentOperation *) fetchMessageAttachmentOperationWithFolder:(NSString *)folder
number:(uint32_t)number
partID:(NSString *)partID
encoding:(MCOEncoding)encoding;
这里你只需要选择适合自己需求的就可以了。到此一个完整的获取邮件并展示的邮件功能就做完了。但其实对于邮件的操作还有很多,邮件发送除外,因为那是SMTP协议的事情。这里再说一下其他邮件操作中遇到的问题。
1、邮件删除
在Mailcore2中使用IMAP协议删除一封邮件并不是那么简单,因为如果你查看了Mailcore2所提供的API之后就会发现并没有所谓的delete方法。在Mailcore2中对邮件的操作是采用修改Flags的方式来进行的。下面是删除邮件的主流代码:
/**
* 删除操作步骤,先copy一份到已删除,再设置删除flag,再清理收件箱
* 这里注意,如果是已删除和草稿箱这两个文件夹,那么我们不必再保存一份到已删除了,而是直接删除就可以了
*/
- (void)deleteMessage:(NSInteger)uid folder:(NSString *)folder
{
self.folderName = [self.imapSession.defaultNamespace pathForComponents:@[folder]];
NSString *deleteFolder = [self.imapSession.defaultNamespace pathForComponents:@[@"已删除"]];
NSString *draftFolder = [self.imapSession.defaultNamespace pathForComponents:@[@"草稿箱"]];
//这里判断是不是“已删除”和“草稿箱”两个文件夹,如果不是那么使用copyMessage来复制邮件到“已删除”
if (![deleteFolder isEqualToString:self.folderName] && ![draftFolder isEqualToString:self.folderName]) {
MCOIMAPCopyMessagesOperation *op = [self.imapSession copyMessagesOperationWithFolder:self.folderName uids:[MCOIndexSet indexSetWithIndex:uid] destFolder:deleteFolder];
[op start:^(NSError *error, NSDictionary *uidMapping) {
[self unturnedDelete:uid];
}];
}else{
[self unturnedDelete:uid];
}
}
/**
* 完全删除邮件操作
*/
- (void)unturnedDelete:(NSInteger)uid
{
//先添加删除flags
MCOIMAPOperation * op2 = [self.imapSession storeFlagsOperationWithFolder:self.folderName
uids:[MCOIndexSet indexSetWithIndex:uid]
kind:MCOIMAPStoreFlagsRequestKindSet
flags:MCOMessageFlagDeleted];
[op2 start:^(NSError * error) {
//添加成功之后对当前文件夹进行expunge操作
MCOIMAPOperation *deleteOp = [self.imapSession expungeOperation:self.folderName];
[deleteOp start:^(NSError *error) {
if(error) {
NSLog(@"Error expunging folder:%@", error);
} else {
NSLog(@"Successfully expunged folder");
}
}];
}];
}
2、设置已(未)读
对邮件设置已(未)读同样是通过操作Flags的方式进行,直接贴代码:
- (void)setReaded:(BOOL)readed message:(NSInteger)uid folder:(NSString *)folder
{
self.folderName = [self.imapSession.defaultNamespace pathForComponents:@[folder]];
MCOIMAPOperation * op2 = [self.imapSession storeFlagsOperationWithFolder:self.folderName
uids:[MCOIndexSet indexSetWithIndex:uid]
kind:(readed?MCOIMAPStoreFlagsRequestKindSet:MCOIMAPStoreFlagsRequestKindRemove)
flags:MCOMessageFlagSeen];
[op2 start:^(NSError * error) {
NSLog(@"%@",error);
}];
}
这里注意set和remove的使用。
3、创建草稿
创建草稿邮件要使用Mailcore2的appendMessage操作,代码如下:
- (void)createDraft:(NSData *)data block:(void(^)(bool success))block
{
NSString *folder = [self.imapSession.defaultNamespace pathForComponents:@[@"草稿箱"]];
MCOIMAPAppendMessageOperation *op = [self.imapSession appendMessageOperationWithFolder:folder messageData:data flags:MCOMessageFlagDraft];
[op start:^(NSError *error, uint32_t createdUID) {
NSLog(@"create message :%@",@(createdUID));
if (error) {
block(false);
}else{
block(true);
}
}];
}
相对来说其实发送邮件的流程相对简单。
1、配置SMTP邮箱
MCOSMTPSession *smtpSession = [[MCOSMTPSession alloc] init];
smtpSession.hostname = @"smtp.exmail.qq.com";
smtpSession.port = 587;
smtpSession.username = @"hello@qq.com";
smtpSession.password = @"passward";
smtpSession.connectionType = MCOConnectionTypeStartTLS;
MCOSMTPOperation *smtpOperation = [self.smtpSession loginOperation];
[smtpOperation start:^(NSError * error) {
if (error == nil) {
NSLog(@"login account successed");
} else {
NSLog(@"login account failure: %@", error);
}
}];
2、构建邮件内容
// 构建邮件体的发送内容
MCOMessageBuilder *messageBuilder = [[MCOMessageBuilder alloc] init];
messageBuilder.header.from = [MCOAddress addressWithDisplayName:@"张三" mailbox:@"111111@qq.com"]; // 发送人
messageBuilder.header.to = @[[MCOAddress addressWithMailbox:@"222222@qq.com"]]; // 收件人(多人)
messageBuilder.header.cc = @[[MCOAddress addressWithMailbox:@"@333333qq.com"]]; // 抄送(多人)
messageBuilder.header.bcc = @[[MCOAddress addressWithMailbox:@"444444@qq.com"]]; // 密送(多人)
messageBuilder.header.subject = @"测试邮件"; // 邮件标题
messageBuilder.textBody = @"hello world"; // 邮件正文
/*
如果邮件是回复或者转发,原邮件中往往有附件以及正文中有其他图片资源,
如果有需要你可将原文原封不动的也带过去,这里发送的正文就可以如下配置
*/
NSString * bodyHtml = @"<p>我是原邮件正文</p>";
NSString *body = @"我是邮件回复的内容";
NSMutableString*fullBodyHtml = [NSMutableString stringWithFormat:@"%@<br/>-------------原始邮件-------------<br/>%@",[body stringByReplacingOccurrencesOfString:@"\n"withString:@"<br/>"],bodyHtml];
[messageBuilder setHTMLBody:fullBodyHtml];
// 添加正文里的附加资源
NSArray *inattachments = msgPaser.htmlInlineAttachments;
for (MCOAttachment*attachmentininattachments) {
[messageBuilder addRelatedAttachment:attachment]; //添加html正文里的附加资源(图片)
}
// 添加邮件附件
for (MCOAttachment*attachmentinattachments) {
[builder addAttachment:attachment]; //添加附件
}
3、发送邮件
// 发送邮件
NSData * rfc822Data =[messageBuilder data];
MCOSMTPSendOperation *sendOperation = [self.smtpSession sendOperationWithData:rfc822Data];
[sendOperation start:^(NSError *error) {
if (error == nil) {
NSLog(@"send message successed");
} else {
NSLog(@"send message failure: %@", error);
}
}];
这里要注意,不同的邮箱可能在这里做的操作不一样,比如有的邮箱会在你发送成功之后系统自动保存一份到已发送文件夹,但是有的邮箱就不会在系统端做这个保存操作,那这个时候就需要开发人员自己去copy一份到已发送了,这个操作和保存草稿箱的类似,但是记住发送使用的SMTP协议,而这里保存或者copy操作你就要切换成IMAP来做了。
至于pop协议的使用这里就不再多说了,这些材料网上比较多。至此Mailcore2的一些使用就讲解完毕了,由于项目做过的时间太长了,一些详细的流程记不太清了,也不想再去梳理了,有需要的朋友再单独聊吧。