Tony.Mo

C程序设计语言读书笔记:导言

| Comments

C语言作为大学软件工程专业入门的第一门编程语言,我其实早就还给老师。从今天开始,重新温习一下,并记下一些非常基础的读书笔记,勉励自己。

iOS程序设计(原书第2版) 这书不错。

1.输出格式问题

printf(“ %3d %6d”,fahr, celsius);

打印fahr与celsius的值,fahr的值占3个数字宽,celsius的值占6个数字宽。

%d      按照十进制整型数打印
%6d     按照十进制整型数打印,至少6个字符宽
%f      按照浮点数打印
%6f     按照浮点数打印,至少6个字符宽
%.2f    按照浮点数打印,小数点后有两位小数
%6.2f   按照浮点数打印,至少6个字符宽,小数点后有两位小数

printf函数还支持下列格式说明:

%o表示八进制数
%x表示十六进制数
%c表示字符
%s表示字符串
%%表示百分号(%)本身
%u表示无符号字符类型
%lu表示无符号长整形类型

2.符号常量

#define指令行的末尾没有分号。

JSDoc安装问题

| Comments

JSDoc安装后出现-bash: sudu: command not found

1.根据JSDoc的github安装教程 Github安装文档

使用CLI指令npm install jsdoc来安装JSDoc后,在命令行输入指令jsdoc -help,CLI提示-bash: sudu: command not found

百度谷歌后,发现是安装的指令的问题,使用指令npm install -g jsdoc@"<=3.3.0"",安装后,在CLI输入jsdoc -help能显示jsdoc的帮组信息。

留一下任务给自己,为什么使用指令npm install -g jsdoc@"<=3.3.0""能够成功使用jsdoc指令?

iOS平台编写Cordova插件

| Comments

由于文章篇幅有点长,所以建议大家可以参考插件的文件目录结构以及各文件内容,边看边拷贝文件内容,快速编写一个插件demo出来,首先有个大概的了解。毕竟纸上得来终觉浅,看完了不写,相当于没看。在2.各文件内容以及作用里面,会详细介绍配置文件以及各元素的作用。(文章末尾有提供安卓平台下编写Cordova插件连接)

1.插件的文件目录结

在桌面或者你认为适合放插件的文件夹下,新建文件夹。新建的文件夹的名称是该插件的id,格式为:com.xxxxxxxx.xxx。

比如,我在桌面新建文件夹,命名为:com.technologystudios.logout

file_name

在新建名称为com.technologystudios.logout的文件夹后,依次新建以下的文件(com.technologystudios.logout不必再新建,只需新建README.md,plugin.xml,src和www文件夹以及下级的文件,其中plugin.xml,src,www为必须的文件)

新建后的文件目录结构(这只是个人新建插件文件目录结构的习惯,并非标准。)

com.technologystudios.logout
├── README.md
├── plugin.xml
├── src
│   └── ios
│       ├── CDVLogoutPlugin.h
│       └── CDVLogoutPlugin.m
└── www
    └── logoutplugin.js

2.各文件内容以及作用

README.md文件

作为帮助文件的存在,内容描述的是插件如何使用以及定义返回的参数格式等。

Logout Plugin
==============

Non-Cross-platform LogoutPlugin 

Note:Just for LogoutPlugin


## Using the plugin ##

LogoutPlugin
    使用方法:
    var logoutPlugin = cordova.require("com.technologystudios.logout.logoutplugin")
    logoutPlugin.executeLogout    
    {
    "type":"Logout"
    }

    返回的参数格式:
    {
        "code":0,
        "data":{},
        "message":"登出成功!",
        "success":"1"
    }
## Licence ##

The PHOENIX License

Copyright (c) 2015 PHOENIX Tony Mo
plugin.xml文件

plugin.xml 是插件的配置文件。 还是先给出例子:

plugin.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
   xmlns:android="http://schemas.android.com/apk/res/android"
   id="com.technologystudios.logout"
   version="0.0.1">

   <name>LogoutPlugin</name>

   <description>LogoutPlugin.</description>

   <engines>
       <engine name="cordova" version=">=3.0.0" />
   </engines>

   <license>PHOENIX</license>

   <!-- ios -->
  <platform name="ios">
      <config-file target="config.xml" parent="/*">
          <feature name="LogoutPlugin">
               <param name="ios-package" value="CDVLogoutPlugin" />
           </feature>
      </config-file>

   <header-file src="src/ios/CDVLogoutPlugin.h"/>
   <source-file src="src/ios/CDVLogoutPlugin.m"/>

  <js-module src="www/logoutplugin.js" name="logoutplugin">
   <clobbers target="plugins.logoutplugin" />
  </js-module>

  </platform>
</plugin>
  • plugin:依次为命名空间、ID、版本,值得我们注意的是属性id。id的值为插件的id值:“com.technologystudios.logout"。
  • name:插件的名称
  • description:描述信息
  • engines:Cordova版本
  • license:授权
  • platform:支持的平台,iOS平台的话,属性name的值为iOS。
  • config-file:封装feature元素注入特定平台(在这里平台为iOS)的config.xml文件
    • feature:属性name为插件的名字
    • param:iOS平台,name的值就是"ios-package";安卓平台,name的值就是"android-package" 使用CLI添加插件后(后面会介绍CLI添加插件的方法),在项目名称/platforms/ios/项目名称/config.xml文件,会注入

      1
      2
      3
      
        <feature name="LogoutPlugin">
                <param name="ios-package" value="CDVLogoutPlugin" />
        </feature>
      
      而又值得我们注意的是,feature元素的属性name的值"LogoutPlugin",是插件的服务名称,需要与logoutplugin.js文件的
      1
      
      cordova.exec(successCallback, errorCallback, 'LogoutPlugin','executeLogout', [options]);
      

      这个方法的第三个参数的值一致,(在logoutplugin.js文件会详细说明)。 而param元素属性value=“CDVLogoutPlugin”,“CDVLogoutPlugin"是native代码的类名,需要作为与src/ios目录下的文件名和类名一致(在CDVLogoutPlugin会详细说明)。

  • header-file,source-file:在iOS平台下,指向iOS原生代码的头文件和实现文件的位置
  • js-module:src指向插件的js文件地址www/logoutplugin.js,属性name的值"logoutplugin",作为插件id的最后一部分。JS使用cordova.require加载logoutPlugin对象的时候,使用到的是插件id + “logoutplugin” ,cordova.require("com.technologystudios.logout.logoutplugin"),为什么通过这个指令就能够得到logoutPlugin对象?

当通过CLI安装插件后,logoutplugin.js被拷贝到:项目名称/platforms/ios/www/plugins/my.plugin.id/www/logoutplugin.js这个目录,同时,插件的id,js文件被拷贝到的目标目录,以及clobbers元素信息,会作为新增的条目被增加到:项目名称/platforms/ios/cordova_plugins.js文件。 cordova_plugins.js文件,我对它的理解,是封装插件的信息,通过插件id加载对应的插件js文件,获得插件对象,而不需要调用端关心需要加载哪一个js文件。

cordova_plugins.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cordova.define('cordova/plugin_list', function(require, exports, module) {
module.exports = [
    {
        "file": "plugins/com.technologystudios.logout/www/logoutplugin.js",
        "id": "com.technologystudios.logout.logoutplugin",
        "clobbers": [
            "plugins.logoutplugin"
        ]
    }
];
module.exports.metadata =
// TOP OF METADATA
{
    "com.technologystudios.logout": "0.0.1"
}
// BOTTOM OF METADATA
});

cordova.define定义名字为'cordova/plugin_list'的模块,改模块向外声明返回的对象类型为数组,该数组对象封装了项目的插件列表。cordova.require("com.technologystudios.logout.logoutplugin"),通过获得'cordova/plugin_list'模块返回的数组对象,并根据id加载对应的js文件logoutplugin.js。而logoutplugin.js文件定义了module.exports = logoutplugin;模块并对外部声明模块返回logoutplugin对象(具体代码内容见logoutplugin.js文件)。所以通过cordova.require(id)能获取插件的对象。

涉及到的mudule.exports以及cordova.define,cordova.require资料

nodejs中exports与module.exports的区别详细介绍

Cordova 3.x 源码分析(3) – cordova.js模块系统require/define

  • clobbers:官网API给出的解释是"指示module.exports作为window.some.value被插入到window对象"。目前对该元素没有很深的理解,target的值我一般赋值"plugins.xxx",xxx的值就是与js-module属性name的值一致。有对这个元素理解深刻的同学麻烦指教一下。

  • framework:插件依赖的framework。例子: <framework src="libz.dylib" />

官网plugin.xml文档plugin.xml字段的详细解释。

src/ios/CDVLogoutPlugin文件

该目录下的文件名以及类名需要与plugin.xml文件里面的

plugin.xml
1
2
3
4
5
<config-file target="config.xml" parent="/*">
          <feature name="LogoutPlugin">
               <param name="ios-package" value="CDVLogoutPlugin" />
           </feature>
      </config-file>

param的value属性值一致。"CDVLogoutPlugin"表示插件名字为"LogoutPlugin"的feature,映射到名为"CDVLogoutPlugin"的服务,插件服务的类名称为"CDVLogoutPlugin",在logoutplugin.js文件中,会使用到这个映射的关系。

CDVLogoutPlugin.h文件的代码例子:

CDVLogoutPlugin.h
1
2
3
4
5
#import <Foundation/Foundation.h>
#import <Cordova/CDVPlugin.h>
@interface CDVLogoutPlugin : CDVPlugin
- (void)executeLogout:(CDVInvokedUrlCommand*)command;
@end

CDVLogoutPlugin.m文件的代码例子:

CDVLogoutPlugin.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "CDVLogoutPlugin.h"

@implementation CDVLogoutPlugin
@synthesize callbackId = _callbackId;

#pragma mark - public interface

- (void)executeLogout:(CDVInvokedUrlCommand*)command
{  
    NSLog(@"CDVLogoutPlugin's function:executeLogout is called");

    NSDictionary * argums = command.arguments[0];

    if (argums !=nil)
    {
        CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsDictionary:@{@"code":@(0),@"data":@{},@"message":@"登出成功!",@"success":@"1"}];
        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
    }

    else
    {
        //return no type error
        CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_ERROR messageAsString:@"no such service"];
        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
    }
}

@end

CDVLogoutPlugin类需要继承CDVPlugin类,CDVLogoutPlugin的方法executeLogout:名称,需要与logoutplugin.js的cordova.exec方法第四个参数一致。executeLogout:方法默认的参数类型是CDVInvokedUrlCommand。

1
cordova.exec(successCallback, errorCallback, 'LogoutPlugin', 'executeLogout', [options]);

在CDVLogoutPlugin.m文件中,executeLogout:方法,command.arguments[0]表示的是cordova.exe函数中的最后一个参数[options],参数的格式可以定义在README.md文件中。该例子的参数格式为{“type”:“Logout”},在javascript传递一个对象到native的插件。

1
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId]

这行代码是返回调用插件的结果给javascript回调函数。Status为CDVCommandStatus_OK的,回调successCallback函数,Status为CDVCommandStatus_ERROR回调errorCallback函数。

www/logoutplugin.js文件

还是直接上例子

logoutplugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
   
var exec = require('cordova/exec'),
cordova = require('cordova');    
var LogoutPlugin = function () {
};

LogoutPlugin.prototype.executeLogout = function (successCallback, errorCallback,options) {
   if (errorCallback == null) {
       errorCallback = function () {
       }
   }

   if (typeof errorCallback != "function") {
       console.log("LogoutPlugin.executeLogout: failure: failure parameter not a function");
       return
   }

   if (typeof successCallback != "function") {
       console.log("LogoutPlugin.executeLogout: success callback parameter must be a function");
       return
   }

   cordova.exec(successCallback, errorCallback, 'LogoutPlugin', 'executeLogout', [options]);
};

var logoutplugin = new LogoutPlugin();
module.exports = logoutplugin;

代码第2,3行,是加载cordova,exec对象;

1
2
var exec = require('cordova/exec'),
cordova = require('cordova');    

第4行,new一个LogoutPlugin对象。

第7行是定义LogoutPlugin对象的executeLogout方法。

1
LogoutPlugin.prototype.executeLogout = function (successCallback, errorCallback,options)

executeLogout方法接收三个参数,第一第二个参数,函数类型。successCallback函数,调用本地插件成功时回调的函数;errorCallback函数,调用本地插件失败时回调的函数。这是javascript与native对接的回调函数,通过native的

1
2
CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsDictionary:@{@"code":@(0),@"data":@{},@"message":@"登出成功!",@"success":@"1"}];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];

CDVPluginResult对象的属性status(CDVCommandStatus_OK/CDVCommandStatus_ERROR)来返回调用对应的成功(successCallback)/失败函数(errorCallback)。

第三个参数,数组类型,为javascript到native的参数。通过

1
NSDictionary * argums = command.arguments[0];

获得,其中commandCDVInvokedUrlCommand*类型。

第23行

1
cordova.exec(successCallback, errorCallback, 'LogoutPlugin', 'executeLogout', [options]);

执行cordova.exec方法,调用'LogoutPlugin'服务的'executeLogout'方法。本地Objective-C代码会执行CDVLogoutPlugin类的executeLogout:方法。

  • 第一第二个参数(successCallback/errorCallback)为外部传递进来的成功/失败回调函数。

  • 第三个参数("LogoutPlugin"),为插件的服务名称,需要与plugin.xml文件的

1
2
3
<feature name="LogoutPlugin">
   <param name="ios-package" value="CDVLogoutPlugin" />
</feature>

feature元素name属性值"LogoutPlugin"保持一致,"LogoutPlugin" 通过元素param设置其映射的Objective-C的类的名字为"CDVLogoutPlugin"。

  • 第四个参数('executeLogout'),为"LogoutPlugin" 映射到Objective-C的"CDVLogoutPlugin"类的方法名字
  • 第五个参数(options),是javascript调用native的插件传递的参数,参数格式为数组,通过被调用的本地方法executeLogout:传入的对象(CDVInvokedUrlCommand*)command, command.arguments[0]来获取。

第27行

1
module.exports = logoutplugin;

创建模块logoutplugin,通过加载logoutplugin.js文件能获取logoutplugin对象。在plugin.xml文件中会详细说明。

大家可以尝试快速新建插件,目录结构以及各文件内容可以直接拷贝粘贴,先写一个雏形插件。假设大家都写好了这个登出的插件。

3.Cordova CLI安装插件

这里没有使用pluginman来对Cordova插件的管理。若需要使用pluginman来管理插件的话,请点击这里这里

假设大家已经搭建好Ionic环境,以及已经新建好Ionic的项目,如果没有,请点击这里来进行环境的搭建以及新建项目,这里不再赘言。

  • 打开CLI,进入项目的目录
  • 输入指令 cordova plugin -h 会出现帮助信息,其中我们关注的是

      add <pluginid>|<directory>|<giturl> [...] ..... add specified plugins
    
                                              pluginid will be matched in --searchpath / registry
    
                                              directory is a directory containing a plugin
    
                                              giturl is a git repo containing a plugin
    
    • pluginid:根据插件的id来搜索插件并安装,该插件需要在Cordova插件库注册成功的。
    • directory:通过插件所在的本地路径来安装(LogoutPlugin就是通过这种方式来安装)
    • giturl:通过url进行插件的安装

      这是使用CLI安装插件前的最外层的plugins目录结构(并非/platforms/ios/www/plugins) preInstall_plgin

      这是/platforms/ios/www/plugins pre_install_plugin_pl

  • 在CLI输入指令 cordova plugin add /Users/Tony/Desktop/plugin/com.technologystudios.logout “/Users/Tony/Desktop/plugin/com.technologystudios.logout"是插件的路径, 敲下回车键之后,出现 Installing "com.technologystudios.logout" for ios信息表示成功安装插件

  • 安装成功后,最外层的plugins目录和/platforms/ios/www/plugins都会多一个刚才add的插件,其中平台里面的plugins是copy最外层的plugins的内容

    af_install_plugin

4.HTML调用本地插件

1.html代码

chat-detail.html
1
2
3
4
5
6
7
8
9
10
 <ion-view >
  <ion-content class="padding">
      <button
      class="button button-block button-balanced"
      ng-click="logout()"
      >
          登出
      </button>
  </ion-content>
</ion-view>

第3-8行代码,描述的是名字为"登出"的按钮,ng-click事件为"logout()“。很容易理解。

2.javascript代码

controllers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
.controller('ChatDetailCtrl', function($scope) {

  $scope.logout = function() {
  
      var newOptions = {
                "type": "Logout"
            };
      var successCallback = function(result){
          console.log("调用logout插件成功");
          console.log(result);
            };
      var errorCallback = function(error){
          console.log("调用logout插件失败");
            };
  
     // 获取 plguin
     var logoutPlugin = false;
     if (typeof cordova !== 'undefined') {
         logoutPlugin = cordova.require("com.technologystudios.logout.logoutplugin")
     };
  
     if (!logoutPlugin) {
         if(angular.isFunction(options.errorCallback)) {
             options.errorCallback({
                 code: 'no plguin',
                 message: '这个功能还没做'
             });
         };
         return false;
     };
      // 尝试调用原生代码
      try
      {
          logoutPlugin.executeLogout(successCallback, errorCallback, newOptions);
      }
      catch (error){
          if(angular.isFunction(options.errorCallback)) {
              options.errorCallback({
                  code: 'no plguin',
                  message: '这个功能还没做'
              });
          };
          };
   };
  })

第3行代码,是登出按钮的logout()事件的定义。

第8-13行代码,简单地输入控制台调用本地插件的情况,以及输入返回的参数。

第5-13行代码,是options参数格式,以及成功回调函数和失败回调函数的定义。

第18行代码,cordova.require("com.technologystudios.logout.logoutplugin") 加载logoutPlugin对象。 Javascript端调用native端的插件,首先需要通过cordova.require("插件id.xxx")来获取得插件对象,其中"xxx"为plugin.xml的元素js-module属性name的值"logoutplugin"。

1
2
3
<js-module src="www/logoutplugin.js" name="logoutplugin">
  <clobbers target="plugins.logoutplugin" />
</js-module>

第33行代码,获取logoutPlugin对象后,通过调用executeLogout(successCallback, errorCallback, newOptions)方法(方法的定义,在logoutplugin.js文件),来调用映射到objective-c的CDVLogoutPlugin类的方法executeLogout:。

3.Objective-C 代码

CDVLogoutPlugin.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#import "CDVLogoutPlugin.h"

@implementation CDVLogoutPlugin
@synthesize callbackId = _callbackId;

#pragma mark - public interface

- (void)executeLogout:(CDVInvokedUrlCommand*)command
{
    self.callbackId = command.callbackId;

    NSLog(@"CDVLogoutPlugin's function:executeLogout is called");

    NSDictionary * argums = command.arguments[0];

    if (argums !=nil)
    {
        CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsDictionary:@{@"code":@(0),@"data":@{},@"message":@"登出成功!",@"success":@"1"}];
        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
    }

    else
    {
        //return no type error
        CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_ERROR messageAsString:@"no such service"];
        [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
    }
}
@end

第12行代码,简单地在控制台输出native的插件被调用的信息。

第18行代码,组装CDVPluginResult类型的result对象,初始化status为CDVCommandStatus_OK,以及返回的参数:@{@"code":@(0),@"data":@{},@"message":@"登出成功!",@"success":@"1"}

5.例子

html_UI

点击HTML5页面的"登出"按钮,在xcode控制台会输出信息

CDVLogoutPlugin's function:executeLogout is called

表示消息成功地传递到native的插件。

然后顺序执行

1
2
CDVPluginResult* result = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsDictionary:@{@"code":@(0),@"data":@{},@"message":@"登出成功!",@"success":@"1"}];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];

返回调用成功会HTML,在前端的控制台会输入下面的信息:

callback_HTML

返回的Object就是在native的插件组装的对象@{@"code":@(0),@"data":@{},@"message":@"登出成功!",@"success":@"1"}

6.Cordova更新插件

一般情况下,被添加的插件会被修改,而且被修改的文件位置在:项目名/platforms/ios/www/plugins/pluginid/src/ios/xxxx,或者是项目名/platforms/ios/www/plugins/pluginid/www/xxx.js,被修改的文件是不会自动更新到:项目名/plugins/pluginid的文件。

那么,被添加的插件如何更新?

很遗憾,目前Cordova的CLI没有提供Cordova plugin update指令来直接更新插件。

笔者解决方案 这里分两种情况:

1.没有在ng-cordova注册的插件

笔者采用一个曲线救国的方法来更新插件,当需要修改:项目名/platforms/ios/www/plugins/pluginid下的文件,我会拷贝修改的文件内容到:项目名称/plugins/对应需要被覆盖的文件。

当插件的修改内容达到需要更新版本号的时候,保存更新的插件文件,更新:项目名称/plugins/pluginid/plugin.xml文件的version="x.x.x",拷贝:项目名称/plugins/pluginid文件夹到桌面或者你认为合适的地方。

进入到项目的路径下,输入指令(使用logout插件id作为例子)

`cordova plugin remove com.technologystudios.logout`

CLI会输出这些信息

`Uninstalling com.technologystudios.logout from ios Removing "com.technologystudios.logout`

指令的意思是移除plugin id为com.technologystudios.logout的插件。

成功执行remove puglin之后,项目名称/plugins/以及项目名称/platforms/ios/www/plugins/下的com.technologystudios.logout文件件会被自动删除。

在CLI输入指令

ionic build ios

cordova plugin add /Users/Tony/Desktop/com.technologystudios.logout

重新安装最新的插件到项目,完成一次插件的更新。

注意 若重新安装后,调用插件,xcode的控制台出现

2015-06-18 18:57:28.677 PluginDemoTabs[18677:645115] ERROR: Plugin 'LogoutPlugin' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.
2015-06-18 18:57:28.677 PluginDemoTabs[18677:645115] -[CDVCommandQueue executePending] [Line 159] FAILED pluginJSON = ["LogoutPlugin1500224491","LogoutPlugin","executeLogout",[{"type":"Logout"}]]
  • 选中使用xcode打开项目,找到CDVLogoutPlugin.m文件,选中,然后在Target Membership,选中项目的target,打钩,然后使用CLI输入指令ionic build ios重新编译项目,问题解决。

file_struture target_member

2.在ng-cordova注册的插件 目前还没有在ng-cordova上传并成功注册过插件,在这里先挖个坑,往后的日子再填。

安卓平台下编写cordova插件 Cordova 开发属于自己的插件(plugin)

(完)