IOS 学习笔记(MyEF/parse.com/chrome extension)PART 8

题外话,早上看到知乎的一个帖子,题目大约是,如果能花钱解决的问题,绝对不要浪费时间,作者是一个创业公司的创始人,他描述了自己从一个抠逼变成两年连买了俩MBP的团队leader,我深表认同,我早已经开始有付费习惯了,在我经济能力允许的情况下,我更倾向于通过正式的渠道,为开发者付钱,那表示你对这些付出过劳动的人的肯定。

在我的计算机初年代,我买正版瑞星杀毒软件,买正版仙剑奇侠传,然后我买iphone,买MBP,买Mac Mini,我为douban付费买fm服务,我为amazon付费,我为迅雷付费用它的云,我办理了全币种的信用卡,然后美元支付treehouse的课程,如果AV在中国合法销售,我也会为AV付费……

当你撅着屁股一页一页地翻百度找免费的开发教程时,面对一堆一堆的壮阳广告,你不觉得你太不优雅了么?

如果你不为优秀的产品付费,这些优秀的团队就会渐渐地从你的视野中消失。你就只能玩满是陷坑的国产游戏了,我错了,那不叫游戏,那叫花钱就爽的玩具。

你总是期待一个团队产出牛逼的产品,同时还不想付费,凭什么啊?!
我们Team的名字已经从公司的rtx组中被移除了,不过在我的鼓动下,几个剩下的主要成员已经开始进入状态(不管是抱着什么样的心情),想为帝国最后做一些事情,所以每天时间都很紧迫。

Something About Parse.com

半个月之前我就从学习的课程中知道了parse.com,这对我这种前端开发者来说,真是个十足的好东西,因为我基本可以不用再担心后端开发的逻辑,可以完全对象化地操作数据库,只要注册了好账户,并给定你的App名称,你就可以在你的主流前端程序中使用它了,目前支持的API情况如下:

文档简直完善到不能再完善了,并且为几乎所有主流平台提供了SDK支持,只需要下载,并加入到对应的工程中,你的前端APP马上就有后端了。一个典型的创建用户的Objective-C代码如下:

PFUser *newUser = [PFUser user];
newUser.username = userName;
newUser.password = password;
newUser.email = email;

[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
    if (error) {
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Oh NO!!" message:[error.userInfo objectForKey:@"error"] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alertView show];
    }else{
        [self.navigationController popToRootViewControllerAnimated:YES];
    }
}];

一个典型的用户登录的javascript代码如下:

var userName = $("#username").val();
var password = $("#password").val();
Parse.User.logIn(userName, password, {
  success: function(user) {
    window.location.href='./popup.html';
  },
  error: function(user, error) {
    trace(error.message);
  }
});

Block

上面的代码中,可以看到有Block的出现,block是一个C风格的函数,其中包含若干代码,可以作为一个参数传递给任何一个想要执行它的地方,这种特性应该是专门为异步通讯的callBack准备的,详见这里。作为一个常年用AS写异步通讯的我来说,这玩意简直是太熟悉了。

MyEF

我之前一直在想,作为英语培训机构,EF已经提供了的移动App中,Efekta10可以用来做单元练习(个人认为只是直接用WebView呈现了已有的网页内容,完全没有原生化……),还可以用来上在线课程(Adobe Connection),以及背单词的功能(这个做的真不错),不过我更希望能用我的移动终端,实时地了解我预订的要去中心上的课程信息。

首先我尝试直接用iOS应用模拟登录EF的接口,遇到了阻碍,对方还是做了安全措施了,然后我忽然想起之前和大湿讨论过的问题,用Chrome Extension是可以直接操作访问当前正在访问的用户信息的,如果我能写一个Chrome Extension将订课页面上的课程数据挖取出来,并传递给parse.com的后端,然后在iOS App中方位这部分数据,这个简单的程序就可以成为一个很有用的应用了!!虽然对用户还是有一定的门槛限制,比如需要使用Chrome浏览器,且需要安装一个Chrome Extension插件,但在未获得EF正式授权的情况下,这已经非常方便了。

Getting Start with Chrome Extension

我去看了Chrome Extension(以下简称GCE)的Getting Start,发现它是直接用javascript做开发,并提供了极其完备的API支持。有几点需要备忘,

  • GCE的展示界面就是一个标准的html。
  • 这个html中不能写任何javascript,只能写在.js文件中在页面中导入,据说是出于安全考虑。
  • 使用不同的api时需要在manifest.json配置文件中指定对应的权限,否则它是不会奏效的。

manifest.json

manifest.json中,配置了程序的基本信息,起始页面,对应权限等等,我的manifest.json配置样例如下:

{
  "name": "My Booked Coures",
  "description": "Catch your booked courses and send it to your mobile app",
  "version": "2.0",
  "manifest_version": 2,
  "permissions": [
    "tabs",
    "<all_urls>",
    "debugger"
  ],
  "browser_action": {
    "default_icon": "logo.png",
    "default_popup": "popup.html"
  }
}

在Chrome中运行你正在开发中的extension

在chrome地址栏中输入chrome://extensions/,可以打开扩展选单,勾选左上角的开发者模式,然后点击“加载正在开发的扩展程序”,定位到你存放扩展代码的文件夹就可以了。你会发现它已经出现在右上角了。

Content Scripts

我遇到的第一个问题是,如何获得目标页面上的资源,GCE中想达成这一点,用到的是Content Scripts,首先,原理是,将一段预先定义好的javascript注入到当前页面的DOM结构中,它就可以为你所用了。我先写了一段js,存放在一个叫injection.js的文件中,作用是找到特定id的htmlElement,并返回对应的html,内容如下:

function getClassData(doc){
    return doc.getElementById("tblClassList").outerHTML;
}
chrome.extension.sendMessage({
    action: "getClassData",
    source: getClassData(document)
});

然后,我在插件的主脚本中,这样注册一个消息监听器:

chrome.extension.onMessage.addListener(function(request, sender) {
  if (request.action == "getClassData") {
    var classData = $(request.source).get(0);
  }
});

这样便达成了他们之间的通讯注册,下面是我想调用这个脚本的时候:

chrome.tabs.executeScript(null, {
    file: "injections.js"
  }, function() {
    if (chrome.extension.lastError) {
       alert('There was an error injecting script : \n' + chrome.extension.lastError.message);
    }
});

好了,classesData got!

解析返回的html

这涉及到DOM解析TableElement,我都是现学现用,首先是将返回的文本html转成DOM对象,我依赖了jQuery

var classData = $(request.source).get(0);

然后要从这个tableElement对象上获得对应的行列信息:

classData.rows[i].cells[j].innerText

把获得的数据塞入到一个js Object中,然后在这么整一下,我就有了一个标准的JSON字符串了。

coursesJSONData = JSON.stringify({"courses":classes});

Save NSArray to local file

我将这个数据丢向Parse后端,我就能在前端程序中拿到这个数据了。由于我希望这个数据是惰性更新,就是不强制刷新就不同步,因为获取数据的手段并不是自动达成的,所以实时同步是没有意义的。所以我需要将对应的数据做一个本地的Cache,下次打开直接加载就好了。结果我发现OC提供了一个相当方便的功能,就是NSArray的writeToFile方法。

//将数组存放到本地文件中
- (void) saveLocalData{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *myEF = [documentsDirectory stringByAppendingPathComponent:@"myEF.dat"];
    [self.courses writeToFile:myEF atomically:YES];
}
//从本地文件还原数组数据
- (BOOL)getLocalData{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *myEF = [documentsDirectory stringByAppendingPathComponent:@"myEF.dat"];
    self.courses = [[NSMutableArray alloc] initWithContentsOfFile: myEF];
    if(self.courses == nil)
    {
        return NO;
    }
    return YES;
}

至此,我已经能够在自己的iphone上查看我的订课信息了。