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上查看我的订课信息了。