iOS MVC详解

 

我们今天谈谈cocoa程序设计中的 模型-视图-控制器(MVC)范型。我们将从两大方面来讨论MVC:

什么是MVC?

M、V、C之间的交流方式是什么样子的?

理解了MVC的概念,对cocoa程序开发是至关重要的。

一、MVC的概念

MVC是Model-VIew-Controller,就是模型-视图-控制器,这些都是什么东西呢?

MVC把软件系统分为三个部分:Model,View,Controller。在cocoa中,你的程序中的每一个object(对象)都将明显地仅属于这三部分中的一个,而完全不属于另外两个。

Model = 你的程序是什么(而不是你的程序是如何显示的)

让我们举个例子,我们上中学的时候,我们的步步高电子词典中有个游戏叫“雷霆战机”,也就是“打飞机”的游戏,Model就是:你的小飞机的攻击力是多少?你的小飞机上装的是什么武器,炮弹,导弹,还是激光炮?你的小飞机还有多少血?等等。再概括点说,就是你的程序将要实现的功能,或者是它所能干的事情。

Controller = 如何使你的模型呈现给用户(程序逻辑)

Controller是程序内部的逻辑,大多情况下你将看不到它,它将Model和View捆绑在一起,它将处理用户的输入,例如,你按开炮的键子,Controller就会通过内部的逻辑来处理你的要求,并在屏幕上做出相应的显示,你将看到屏幕上的小飞机发出炮弹击中敌机。这也是Controller控制View的显示的例子。所以你可以把Controller看成是连接M和V的桥梁。

View = 在屏幕上你所看到的(是你的Controller的“奴才”)

接着前面的小飞机,View就是:你的小飞机是什么样子的,有一个还是两个翅膀,有几挺枪炮;还有,你的飞机在屏幕上的位置等等。总之,你在屏幕上看到的组件都可以归类为View。

MVC可以帮助确保帮助实现程序最大程度的可重用性。各MVC元素彼此独立运作,通过分开这些元素,可以构建可维护,可独立更新的程序组建。

二、M V C之间的交流模式

好了,现在我们来讨论MVC中各个元素之间的交流方式。

我们把程序分成三个部分,但并不希望他们完全独立,因为那样的话,我们的程序将毫无意义和功能而言。它们之间必然存在某种联系,使它们能有机的成为一个整体来实现各种强大的功能。而这种联系就是我们提到的交流方式。我们来看看下面的图,此图出自斯坦福大学CS193课程的课件

 

图中有几条线把这三部分划分开,有黄线,虚线,和白色的实线。我们把它们想象成路标。你可以看到,在M和V之间有两条黄线,这表示什么呢?它意味着你不能穿越这黄线,任何一个方向都不行。在图的上部,你可以看到白色的虚线,它意味着你可以自由的穿越它,只要是安全的。那白色的实线呢?它代表你可以穿越,但你必须要买票,或者交点过路费。

好了,如果你觉得前面的比喻没有使你明白的话,让我们来讲点实在的东西。

首先, 我们来看C和M之间的绿色箭头,这箭头的方向就代表着“发起对话”的方向,也就是说,发起对话的是C,而做出回答的是M。C可以问M各种各样的问题,但M只是回答C的问题或要求,它不可以主动的向C要求什么。还记得虚线是畅通无阻的意思吧,所以,C知道M的所有的事情,如果用代码来说明这件事情,就是说,C可以导入M的头文件或是M的接口(API)。因为C可以通过M的API,所以它就可以肆无忌惮的向M要求这要求那了。

我们再来看看另外的一个绿色箭头,它是在C和V之间,和前一个绿色箭头的意义一样,它代表C可以直接地向V进行交流。你可以想想,C要把V放到屏幕上,并设置V的属性,告诉它们什么时候从屏幕上消失,把它们分成组等等。如果C不能自由的向V发号施令的话,程序的显示将会多么的困难,所以,C可以毫无限制地向V说话。

可能你已经注意到了,这个箭头上还有outlet(输出口),outlet可以看作是从C指向V的指针,它在C中被定义。outlet给我们提供了很大的方便,它使我们在C的内部就可以轻松准确地向V施令。C可以拥有很多的outlet,可以不止一个,这也使它可以更高效的和V进行交流。

那M和V之间可以交流么?还记得黄线的意思么?完全不可以通过,所以我们是不允许M和V进行交流的。这是因为我们不希望这三部分之间有过多的交流,你想想,假如V在显示时出现了问题,比如有一个图形没有显示出来,我们就要去查找错误,因为C可以和V交流,M也可以和V交流的话,我们就要去检查两个部分。相反的,只有C可以和V交流的话,在出错时,我们就只需要去C那里查找原因,这样查找错误不就很是简单了么?所以,我们不允许M和V之间有直接的联系,这也是在它们两之间有两根黄线的原因。

好的应用程序要具备与用户交互的能力。如果没有良好的交互性,程序的功能将会受到很大的限制。在MVC中,V是和用户直接接触的,用户看不到M和C,所以,程序与用户的交互必须通过V来实现,但V只是视图而已,它并不能完全处理用户的要求,所以,这就要求V必须有某种手段来向C发送信息,移交用户的交互要求。这手段就是前面白色实线代表的过路费,你知道V不能知道C的一切,但它可以通过某种“手段”来和C进行交流,移交用户交互责任。

我们接下来讨论V是如何向C发送信息的。V对C的交流有三种不同的方式。

第一种我们称为目标操作(target-action)。它是这样工作的,C会在自己的内部“悬挂”一个目标(target),如图中的红白相间的靶子,对应的,它还会分发一个操作(action,如图中的黄色箭头)给将要和它交流的视图对象(可能是屏幕上的一个按钮),当按钮被按时,action就会被发送给与之对应的target,这样V就可以和C交流了。但是在这种情况下,V只是知道发送action给对应的target,它并不知道C中的类,也不知道它到底发送了什么。target-action是我们经常使用的方法。

第二种方式我们叫做委托(delegate)。有时候,V需要和C进行同步,你知道,用户交互不仅仅是什么按按钮,划滑块,还有很多种形式。好了,让我们来看看图中的delegate黄色箭头,你发现箭头上又分出了四个小箭头:should,did,will,还有一个没标注的。绝大部分的delegate信息都是should,will,did这三种形式。和英文意思相对应,should代表视图对象将询问C中的某个对象“我应该这么做么?”,举个例子,有一个web视图,有人点击了一个链接,web视图就要问“我应该打开这个链接么?这样做安全么?”。这就是should信息。那will和did呢?will就是“我将要做这件事了”,did就是“我已经做了这件事”。C把自己设置为V的委托(delegate),它让V知道:如果V想知道更多的关于将如何显示的信息的话,就向C发送delegate信息。通过接受V发过来的delegate信息,C就会做出相应的协调和处理。还有一点,每个V只能有一个delegate。

第三种方式就是数据源(datasource),你知道,V不能拥有它所要显示的数据,记住这点非常重要。V希望别人帮助它管理将要显示的数据,当它需要数据时,它就会请求别人的帮助,把需要的数据给它。再者,iphone的屏幕很小,它不能显示包含大量信息的视图。看图中的datasource箭头,和delegate类似,V会发送cout,data at信息给C来请求数据。

好了,这就是V给C发送信息的三种形式。

最后一个问题。你看到M和C之间的白线,这意味着M不可以直接地,没有限制的对C进行交流。但有时,这个方向的交流是必要的。当M中的一些东西发生变化时,C需要了解这些变化,那我们怎么才能让C知道M的变化呢?通知(Notification)和KVO是解决问题的好方法。它们是这样工作的,当M中的某些东西发生变化时,他们会向C发出通知“嘿,老兄,注意了啊,我这发生变化了”,或者他们会发出指向变化的指针给C,或其他什么的。总之,他们的工作模式是这样的。

下面是我们的一个总结,cocoa忠实于MVC,所以理解cocoa的MVC是我们关键的开始,希望这些能使你明白一些。

C对M:API

C对V:Outlet

V对C:Target-action, Delegate,Datasource

M对C:Notification,KVO


iOS中的设计模式:target-action

Target-action,目标-动作模式。在iOS界面开发中十分常用。
拿使用ARC编写简单的Xcode项目中的示例来说:
如图所示,target是处理交互事件的对象实例,action是target对象中处理该事件的方法。
可以通过Interface Builder将视图对象和action方法连接起来:
在这个示例里,提交按钮,一个可交互的视图控件,点击它后,它指定了一个target(目标对象),并执行在目标对象上的指定的action(方法)。
action方法,可以有几种形式:
- (void)doSomething;
// OR
- (void)doSomething:(id)sender;
// OR
- (IBAction)doSomething:(id)sender;
// OR
- (IBAction)doSomething:(UIButton *) sender;
这里的sender,发送者,就是对提交按钮对象的引用。

Xcode 4.1/4.2/4.3 免证书(iDP)开发+真机调试+生成IPA

 

众所周知,在Xcode上开发的程序只能在模拟器中运行,如果要放到真机上则要花费99美金购买开发者证书iDP。这严重阻碍了我等草根开发者探索的脚步。写个小程序,同学间分享一下这个小小的愿望都不能满足,自然不能善罢甘休。

在没有iDP的情况下,要想将程序放到iPhone上调试,并最终发布IPA用于分享,需要以下几个步骤:

1.自己为自己颁发一个证书用于为生成的程序签名

2.修改工程配置以及Xcode的配置文件和二进制文件以阻止其验证和签名

3.通过自定义生成步骤,用伪造的证书为应用程序签名

4.使用一点小trick来生成IPA文件

开始之前的友情提示:

1. 本文给有一定基础的朋友看,各种命令是免不了的。UNIX中的基本命令比如cp,mv,cd,chmod,sudo啥的还有vim编辑器,如果您都没听说过,强烈建议您不要尝试本文提到的修改。一旦改错了轻则Xcode挂掉,重则系统崩溃。

2. 本文的每一个步骤都是必须的,如果您哪一步没有得到预期的结果,请不要继续,以免发生更加意外的事情。。。

3. 如果遇到解决不了的问题欢迎留言询问,务必给出详细的错误信息,否则无法判断。博主恕不解答类似于如何执行脚本代码,如何赋予执行权限,如何使用vim编辑器(如何保存)等与iOS开发无关的UNIX基础问题。

1.创建证书

创建证书的过程比较简单,打开实用工具-钥匙串访问。然后在菜单栏里点击钥匙串访问-证书助理-创建证书来打开向导。第一个步骤比较重要,必须要把名称命名为iPhone Developer,将类型设定为代码签名,将"让我覆盖这些默认值"选中。之后的步骤无需更改,一路点击“确定”和“继续”来完成这个向导就可以。

2.修改Xcode的配置文件和二进制文件
下面的步骤稍微有点繁琐,您应该了解UNIX命令行的基本操作,并了解一种命令行文本编辑器,本文使用的是vim。尽管这里会给出完整的代码,但是关于修改和保存代码的基本操作,不再赘述。下面的操作请先将Xcode按Command+Q完全关闭。 继续阅读 »

Objective-c的@property 详解

学过c/c++的朋友都知道,我们定义struct/class的时候,如果把访问限定符(public,protected,private)设置为public的话,那么我们是可以直接用.号来访问它内部的数据成员的。比如

//in Test.h
class Test
{
public:
    int i;
    float f;
};

我在main函数里面是可以通过下面的方式来使用这个类的:(注意,如果在main函数里面使用此类,除了要包含头文件以外,最重要的是记得把main.m改成main.mm,否则会报一些奇怪的错误。所以,任何时候我们使用c++,如果报奇怪的错误,那就要提醒自己是不是把相应的源文件改成.mm后缀了。其它引用此类的文件有时候也要改成.mm文件)

 //in main.mm
    Test test;
    test.i =1;
    test.f =2.4f;

    NSLog(@"Test.i = %d, Test.f = %f",test.i,  test.f);

但是,在objc里面,我们能不能这样做呢?请看下面的代码:(新建一个objc类,命名为BaseClass)

//in BaseClass.h
@interface BaseClass : NSObject{
@public
    NSString *_name;
}

接下来,我们在main.mm里面:

 BaseClass *base= [[BaseClass alloc] init];
    base.name =@"set base name";
    NSLog(@"base class's name = %@", base.name);

直接报错!!!!

出错提示“Property 'nam' not found on object of type BaseClass*",意思是,BaseClass这类没有一个名为name的属性。即使我们在头文件中声明了@public,我们仍然无法在使用BaseClass的时候用.号来直接访问其数据成员。而@public,@protected和@private只会影响继承它的类的访问权限,如果你使用@private声明数据成员,那么在子类中是无法直接使用父类的私有成员的,这和c++,java是一样的。

 既然有错误,那么我们就来想法解决啦,编译器说没有@property,那好,我们就定义property,请看代码:

//in BaseClass.h
@interface BaseClass : NSObject{
@public
    NSString *_name;
}
@property(nonatomic,copy) NSString *name;
//in BaseClass.m
@synthesize name = _name;

现在,编译并运行,ok,很好。那你可能会问了@prperty是不是就是让”."号合法了呀?只要定义了@property就可以使用.号来访问类的数据成员了?先让我们来看下面的例子:

@interface BaseClass : NSObject{
@public
    NSString *_name;
}
//@property(nonatomic,copy) NSString *name;

-(NSString*) name;
-(void) setName:(NSString*)newName;

我把@property的定义注释掉了,另外定义了两个函数,name和setName,下面请看实现文件:

-(NSString*) name{
    return _name;
}

-(void) setName:(NSString *)name{
    if (_name != name) {
        [_name release];
        _name = [name copy];
    }
}

现在,编译并运行,ok,很好。那你可能会问了@prperty是不是就是让”."号合法了呀?只要定义了@property就可以使用.号来访问类的数据成员了?先让我们来看下面的例子:

@interface BaseClass : NSObject{
@public
    NSString *_name;
}
//@property(nonatomic,copy) NSString *name;

-(NSString*) name;
-(void) setName:(NSString*)newName;

我把@property的定义注释掉了,另外定义了两个函数,name和setName,下面请看实现文件:

//@synthesize name = _name;

-(NSString*) name{
    return _name;
}

-(void) setName:(NSString *)name{
    if (_name != name) {
        [_name release];
        _name = [name copy];
    }
}

现在,你再编译运行,一样工作的很好。why?因为我刚刚做的工作和先前声明@property所做的工作完全一样。@prperty只不过是给编译器看的一种指令,它可以编译之后为你生成相应的getter和setter方法。

转自:http://www.cnblogs.com/andyque/archive/2011/08/03/2125728.html

浅谈程序员的10个层次

程序员的10个层次
 
  觉得这篇文章写的还蛮意思,全篇很长我简要的把该文改写一下,以突出十个层次的区别:
  中国的程序员水平比西方程序员水平差,还是中国有许多优秀的程序员达到或超过了西方程序员 同等水平呢?要解决这个问题,必须先知道程序员有多少种技术层级,每个层级需要什么样的技 术水平,然后再比较中国和西方在各个技术层级的人数,就可以知道到底有没有差距,差距有多 大。
当然,对于如何划分程序员的技术层级,不同公司或不同人会有不同的划分标准,下面的划分仅 代表个人的观点,如有不当之处,还请砸板砖予以纠正。
一、 菜鸟
  第 1 层楼属于地板层,迈进这层楼的门槛是很低的。基本上懂计算机的基本操作,了解计算 机专业的一些基础知识,掌握一门基本的编程语言如 C/C++,或者 Java,或者 JavaScript,或者O-C..., 均可入门迈进这层。
二、 大虾 
  从第 1 层爬到第 2 层相对容易一些,以 C/C++程序员为例,只要熟练掌握 C/C++编程语言,
掌握 C 标准库和常用的各种数据结构算法,掌握 STL 的基本实现和使用方法,掌握多线程编程 基础知识,掌握一种开发环境,再对各种操作系统的 API 都去使用一下,搞网络编程的当然对 socket 编程要好好掌握一下,然后再学习一些面向对象的设计知识和设计模式等,学习一些测 试、软件工程和质量控制的基本知识,大部分人经过 2~3 年的努力,都可以爬到第 2 层,晋升 为"大虾"。
三、 牛人 
  由于"大虾"们经常被一些疑难问题给卡住,所以有了"大虾"们只好继续学习,他们需要将原
来所学的知识进一步熟练掌握,比如以熟练掌握 C++编程语言为例,除了学一些基础性的 C++ 书籍如《C++ Primer》,《Effective C++》,《Think in C++》,《Exception C++》等之外,更重要 的是需要了解 C++编译器的原理和实现机制,了解操作系统中的内部机制如内存管理、进程和 线程的管理机制,了解处理器的基础知识和代码优化的方法,此外还需要更深入地学习更多的数 据结构与算法,掌握更深入的测试和调试知识以及质量管理和控制方法,对各种设计方法有更好 的理解等。
学习上面说的这些知识不是一挥而就的,不看个三五十本书并掌握它是做不到的。以数据结 构算法来说,至少要看个 5~10 本这方面的著作;以软件设计来说,光懂结构化设计、面向对 象设计和一些设计模式是不够的,还要了解软件架构设计、交互设计、面向方面的设计、面向使 用的设计、面向数据结构算法的设计、情感化设计等,否则是很难进到这个楼层的。
四、 大牛 继续阅读 »

Android开发中保存数据的四种方法

 

在Android开发中我们会接触到四种数据存储方式,每种存储方式都各有不同;以下我分别列举了Android开发中的不同存储方式的特点

一,Preferences

Preferences是一个较轻量级的存储数据的方法,具体使用方法:

在A中保存值:

SharedPreferences sp = getSharedPreferences("preferences",Context.MODE_PRIVATE);
                                     // 添加数据
                                     Editor editor = sp.edit();
                                     editor.putString("name", name);
                                     editor.putInt("age", Integer.parseInt(age));
									 editor.commit();

在B中取值:

 SharedPreferences sp = getSharedPreferences("preferences",Context.MODE_PRIVATE);
                                     // 获取数据
                                     String name = sp.getString("name", "defName");
                                     String age = sp.getInt("age", 0) + "";

二,Files

//创建文件
             file = new File(FILE_PATH , FILE_NAME);
             file.createNewFile();

            //打开文件file的OutputStream
             out = new FileOutputStream(file);
             String infoToWrite = "纸上得来终觉浅,绝知此事要躬行";
             //将字符串转换成byte数组写入文件
             out.write(infoToWrite.getBytes());
             //关闭文件file的OutputStream
             out.close();

            //打开文件file的InputStream
             in = new FileInputStream(file);
             //将文件内容全部读入到byte数组
             int length = (int)file.length();
             byte[] temp = new byte[length];
             in.read(temp, 0, length);
             //将byte数组用UTF-8编码并存入display字符串中
             display =  EncodingUtils.getString(temp,TEXT_ENCODING);
             //关闭文件file的InputStream
             in.close();
         } catch (IOException e) {
             //将出错信息打印到Logcat
             Log.e(TAG, e.toString());
             this.finish();
         }

//从资源读取

InputStream is=getResources().getRawResource(R.raw.文件名)

三,Databases

Android 内嵌了功能比其他手机操作系统强大的关系型数据库sqlite3,我们在大学时候学的SQL语句基本都可以使用,我们自己创建的数据可以用adb shell来操作。具体路径是/data/data/package_name/databases。如,这里演示一下进入 com.android.providers.media包下面的操作。

1,  adb shell

2,  cd /data/data/com.android.providers.media/databases

3,  ls(查看com.android.providers.media下面的数据库)

4,  sqlite3 internal.db

5,  .help---看看如何操作

6,  .table列出internal数据中的表

7,  select * from albums;

DatabaseHelper mOpenHelper;

 private static final String DATABASE_NAME = "dbForTest.db";
 private static final int DATABASE_VERSION = 1;
 private static final String TABLE_NAME = "diary";
 private static final String TITLE = "title";
 private static final String BODY = "body";

 private static class DatabaseHelper extends SQLiteOpenHelper {
  DatabaseHelper(Context context) {
   super(context, DATABASE_NAME, null, DATABASE_VERSION);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {

   String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE
     + " text not null, " + BODY + " text not null " + ");";
   Log.i("haiyang:createDB=", sql);
   db.execSQL(sql);

  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  }
 }

 /**
  * 重新建立数据表
  */
 private void CreateTable() {
  SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE
    + " text not null, " + BODY + " text not null " + ");";
  Log.i("haiyang:createDB=", sql);

  try {
   db.execSQL("DROP TABLE IF EXISTS diary");
   db.execSQL(sql);
   setTitle("数据表成功重建");
  } catch (SQLException e) {
   setTitle("数据表重建错误");
  }
 }

 /**
  * 删除数据表
  */
 private void dropTable() {
  SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  String sql = "drop table " + TABLE_NAME;
  try {
   db.execSQL(sql);
   setTitle("数据表成功删除:" + sql);
  } catch (SQLException e) {
   setTitle("数据表删除错误");
  }
 }

 /**
  * 插入两条数据
  */
 private void insertItem() {
  SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  String sql1 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY
    + ") values('haiyang', 'android的发展真是迅速啊');";
  String sql2 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY
    + ") values('icesky', 'android的发展真是迅速啊');";
  try {
   Log.i("haiyang:sql1=", sql1);
   Log.i("haiyang:sql2=", sql2);
   db.execSQL(sql1);
   db.execSQL(sql2);
   setTitle("插入两条数据成功");
  } catch (SQLException e) {
   setTitle("插入两条数据失败");
  }
 }

 /**
  * 删除其中的一条数据
  */
 private void deleteItem() {
  try {
   SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   db.delete(TABLE_NAME, " title = 'haiyang'", null);
   setTitle("删除title为haiyang的一条记录");
  } catch (SQLException e) {

  }

 }

 /**
  * 在屏幕的title区域显示当前数据表当中的数据的条数。
  */
 private void showItems() {

  SQLiteDatabase db = mOpenHelper.getReadableDatabase();
  String col[] = { TITLE, BODY };
  Cursor cur = db.query(TABLE_NAME, col, null, null, null, null, null);
  Integer num = cur.getCount();
  setTitle(Integer.toString(num) + " 条记录");
 }

四,Network

这是借助Internet来存储我们要的数据,这是CS结构的存储方式,也是点一下名了。

如何使用 Content Provider

下边是用户经常接触到的几个典型Content Provider应用:


    * Content Provider Name : Intended Data

    * Browser : Browser bookmarks, Browser history, etc.

    * CallLog : Missed calls, Call datails, etc.

    * Contacts : Contact details

    * MediaStore : Media files such as audio, Video and Images

    * Settings : Device Settings and Preferences

第 1 页,共 37 页12345...末页 »