Coder Social home page Coder Social logo

blog's People

Contributors

michealyang avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

blog's Issues

在iOS9中分享到QQ和微信

iOS9出来已经一段时间了,适配方面的文章也有不少了,这里有一篇比较详细的文章:点我,这里不作全面的介绍,只关注其中的一点,也就是iOS9_Url_Scheme——Apple引入的白名单概念。:joy_cat:

官方是这么说的:
image of scheme

也就是说:在iOS9中,如果使用 canOpenURL: 方法,该方法所涉及到的 URL scheme 必须在Info.plis中将它们列为白名单,否则不能使用。key叫做LSApplicationQueriesSchemes ,键值内容是

<key>LSApplicationQueriesSchemes</key>
<array>
 <string>urlscheme</string>
 <string>urlscheme2</string>
 <string>urlscheme3</string>
 <string>urlscheme4</string>
</array> 

也就是要在info.plist 文件中将你需要处理的一些scheme提前加入到白名单。

如果白名单中没有添加们则会出现下面的错误:

-canOpenURL: failed for URL: "ABC://app/*******/" - error: "This app is not allowed to query for scheme ABC"的错误,

下面列举一些常用的白名单的scheme

下面这个是Facebook的scheme

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>fbapi</string>
    <string>fbapi20130214</string>
    <string>fbapi20130410</string>
    <string>fbapi20130702</string>
    <string>fbapi20131010</string>
    <string>fbapi20131219</string>    
    <string>fbapi20140410</string>
    <string>fbapi20140116</string>
    <string>fbapi20150313</string>
    <string>fbapi20150629</string>
    <string>fbauth</string>
    <string>fbauth2</string>
    <string>fb-messenger-api20140430</string>
</array>

蓦然回首万事空 ————空指针漫谈(转)

在目前大多数的编程语言中,都存在一个很有意思的特殊的指针(或者引用),它代表指向的对象为“空”,名字一般叫做null、nil、None, Nothing、nullptr等。这个空指针看似简单,但它引发的问题却一点也不少,空指针错误对许多朋友来说都不陌生,它在许多编程语言中都是非常非常常见的。用Java举例来说,我们有一个String类型的引用,String str = null;。如果它的值为null,那么接下来,用它调用成员函数的时候,那么程序就会抛出一个NullPointerException。如果不catch住这个异常呢,整个程序就会crash掉。据说,这一类问题,已经造成了业界无法估量的巨大损失。

源起

在2009年的一个会议中,著名的“快速排序”算法的发明者,Tony Hoare向全世界道歉,忏悔他曾经发明了“空指针”这个玩意。他是这么说的:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965.
At that time, I was designing the first comprehensive type system for references in an
object oriented language (ALGOL W). My goal was to ensure that all use of references should be
absolutely safe, with checking performed automatically by the compiler.
But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement.
This has led to innumerable errors, vulnerabilities, and system crashes,
which have probably caused a billion dollars of pain and damage in the last forty years.

原来,在程序语言中加入空指针设计,其实并非是经过深思熟虑的结果,而仅仅是因为它很容易实现而已。
这个设计是如此的影响深远,以至于后来的编程语言都不假思索的继承了这一设计,这个范围几乎包括了目前业界所有的流行的编程语言。
对许多程序员来说,早就已经习惯了空指针的存在,就像日常生活中的空气和水一样。那么,空指针究竟有什么问题,以至于图灵奖的获得者Tony Hoare都表示后悔了呢?

问题详解

空指针最大的问题在于:null是一个合法存在的不合理的值。许多语言让所有的指针类型都具有“可空性”(nullability)。
比如,在Java中,除了基本类型之外,其他所有类型的引用都是可以赋值为null的。许多程序员已经习惯于使用null来表示某个特殊的状态。
在某些地方,程序员可能会觉得某个变量从逻辑上可以保证它不会为空,于是就省略掉了空指针检查。
可是,时过境迁之后,因为代码的各种变化,导致这样的前提不再成立的时候,空指针异常就发生了。
代码因此非常脆弱。而有些谨慎的程序员,为未雨绸缪计,会在各个地方都加上保护性的空指针检查,又让代码变得非常臃肿。

那么病根究竟是出在哪里呢?

  1. 空指针引发的第一个问题在于,空指针违背了类型系统的初衷。
    我们再来回忆一下,什么是“类型”?类型是用于规范程序的各个组件调用关系的一组规定。我们如果有一个类型Thing,它有一个成员函数doSomeThing(),那么只要是这个类型的变量,它就一定应该可以调用doSomeThing()函数,完成同样的操作,返回同样类型的返回值。
    但是,null违背了这样的约定。一个正常的指针,和一个null指针,哪怕它们是同样的类型,做同样的操作,所得到的结果也不一样。那么,凭什么说,null指针是和普通指针是一个类型?
    在C#标准文档(ECMA C# launguage specification)中,我们可以找到这样的对null literal的描述:
    > The type of a null-literal is the null type (§11.2.7).
    总而言之,null实际上是在类型系统上打开了一个缺口,引入了一个必须在运行期特殊处理的一个特殊的“值”。
    它就像一个全局的无类型的singleton变量一样,可以无处不在,可以随意与任意指针实现自动类型转换。它让编译器的类型检查在此处失去了意义。
    2.空指针引发的第二个问题在于,它鼓励API设计者使用空指针作为标记符号(sentinel value)
    所谓“标记符号”指的是一种特殊的值,用于标记特殊的状态。它指的是这样的一种设计模式:当你需要多个类型A、B、C……的时候,
    不是去创建多个类型来匹配需求,而是转而使用一个简单的、容易实现的类型T,然后把多个类型映射到一个类型的多个区间的值。
    比如说,有些这样的API设计
    • 使用int作为函数的返回值,负数代表错误,非负数代表正常的结果,由使用者去判断这个值的真实含义;
    • 在需要使用enum的场合,使用int类型,然后在每个使用它的地方小心翼翼地检查这个值是否合理;
      关于这一类行为,有网友机智地将其称之为”Primitive Obsession”(基本类型偏执)。空指针就是这一设计的典范。
      从底层原理上来说,指针本身实际上就是用一个整数来表示的,它当然可以取值为0,也就是空指针。
      但是,从语言设计层面,逻辑上来说,我们不该将指针类型与整数类型等同起来,它们所起的作用完全不同,它们能执行的操作完全不同,它们在抽象层面的概念完全不同, 即便它们在机器码层面的表示方式是一模一样的。
  2. 空指针让程序设计语言变得更复杂
    在C++中,我们考虑以下代码,把一个整数赋值给一个指针,它会产生编译错误
char *myChar = 123;                // compile error
std::cout &lt;&lt; *myChar &lt;&lt; std::endl;

但是,我们把整数的值变一下,它又可以编译通过了

char *myChar = 0;
std::cout &lt;&lt; *myChar &lt;&lt; std::endl; // runtime error

在Java中,我们考虑以下代码,它是编译不过的

int x = null;       // compile error

但是,我们改个类型,于是就编译通过了

Integer i = null;
int x = i;          // runtime error

可惜这样更糟糕,它会在运行阶段抛出异常,导致整个逻辑不能继续进行。而且,它发生在隐蔽的地方,我们连函数都没调用。

在javascript中,问题更有意思。如果一个object为空,那么我们说它的值为null。
但是,如果object有一个属性,它的返回值是null,那么我们该怎么区分这个属性不存在,还是这个属性存在,但是值为null?
javascript的设计者于是又添加了一个undefined全局属性来区分这两种情况。
:(。实质上,javascript为了解决null的问题,在语言中又加入了另外一种不同形态的null。

解决方案

空指针在许多程序设计语言中太常见了,以至于有许多人误以为它就像空气和水一样,是我们不可或缺的一份子。恰恰相反,错!

那么,解决方案是什么呢?那就是,把null当成一个“类型”来处理,而不是当成一个特殊的“值”来处理。
编译器和静态检查工具不可能知道一个变量在运行期的“值”,但是可以检查所有变量所属的“类型”,来判断它是否符合了类型系统的各种约定。
如果我们把null从一个“值”上升为一个“类型”,那么静态检查就可以发挥其功能了。

在许多的程序设计语言中,实际上早就已经有了这样的一个设计,叫做Option Type。在scala、haskell、Ocaml、F# 等许多语言中已经存在了许多年。

下面我们以Rust为例,介绍一下Option是如何解决掉空指针问题的。在Rust中,Option实际上只是一个标准库中普通的enum:

pub enum Option {
    /// No value
    None,
    /// Some value `T`
    Some(T)
}

Rust中的enum实际上是一个sum type, 它要求,在使用的时候,必须“完整匹配”。意思是说,enum中的每一种可能性,都必须处理,不能遗漏。比如,有一个可空的字符串msg,我们想打印出其中包含的信息,可以这么做:

let msg : Option&lt;&amp;str&gt; = Some(&quot;howdy&quot;);
match msg {
    Some(m) =&gt; println!(&quot;{}&quot;, m), // 如果是Some类型,则m匹配到&amp;str类型,于是它可以调用&amp;str所属的成员函数
    None =&gt; () // 如果是None类型,那么它无法访问msg内部数据
}

我们可以看到,对于一个可空的类型,我们没有办法直接调用该类型的成员函数,必须用match语句把其中的内容“拆”出来,然后分情况使用。

而对于普通非空类型呢,Rust不允许赋值为None,也不允许不初始化就使用。Rust中,也没有null这样的关键字。所以,在Rust语言中,根本就没有空指针错误这样的问题。

实际上,C++/C#等语言也发现了初始设计中的缺点,并且开发了一些补救措施。C++标准库中加入了std::optional类型,C#中加入了System.Nullable类型。可惜的是,受限于早期版本兼容性的要求,这些设计已经不能作为强制要求使用,因此其作用也就弱化了许多。

Option类型有许多非常方便的成员函数可供使用,如下所示:

fn main() {
    // not_nullable是String类型,因此它永远不可能为None,它可以放心调用String的成员函数
    let not_nullable : String = String::from(&quot;not nullable&quot;);
    println!(&quot;call member function directly. string lenght is {}&quot;, not_nullable.len());

    // nullable1是Option类型,它可以使用unwrap_or函数,该函数可以提取出里面的值,如果为None,则返回参数中提供的默认值
    let nullable1 : Option&lt;String&gt; = Some(&quot;hello world&quot;.to_owned());
    get_length1(nullable1);

    // nullable2是Option类型,它可以使用map函数,该函数把一个Option类型通过一个closure映射到另外一个Option类型
    let nullable2 : Option&lt;String&gt; = Option::None;
    get_length2(nullable2);
}

fn get_length1(nullable : Option&lt;String&gt;) {
    let len = nullable.unwrap_or(&quot;default value&quot;.to_owned()).len();
    println!(&quot;fall back to default value. string length is {}&quot;, len);
}

fn get_length2(nullable : Option&lt;String&gt;) {
    let len = nullable.map(|s| s.len());
    println!(&quot;map an Option to another Option. string length is {:?}.&quot;, len);
}
// 编译执行,输出结果为:
call member function directly. string lenght is 12
fall back to default value. string length is 11
map an Option to another Option. string length is None.

总结来说,Rust这样的设计有以下几个优点:

  1. 再次强调显式比隐式好。如果从逻辑上说,我们需要一个变量确实是可空的,那么就应该显式标明其类型为Option 否则应该直接声明为T类型。从类型系统的角度来说,这二者有本质区别,切不可混为一谈。
  2. 代码更安全。因为类型系统的存在,空指针现在可以被编译器完美检测,从根源上杜绝了这个问题,不可能有漏网之鱼,大幅提高了程序的健壮性。
  3. 执行效率更高。Null不再是到处都可能出现的一个怪物,不再需要程序员到处检查空指针问题。多余的空指针检查是完全没有必要的。
  4. 大家也不必担心这样的设计会导致大量的match语句,使得程序可读性变差。因为Option类型有许多方便的成员函数,再配合上闭包功能,实际上在表达能力和可读性上要更胜一筹。

所以说,空指针的确是一个编程语言设计史上的重大失误,该错误流毒之广,影响之巨,难有其匹。
怪不得Tony老爷子要感叹一句:一失足成千古恨,再回头已百年身!

参考资料:

  1. http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
  2. https://doc.rust-lang.org/stable/std/option/
  3. http://ericlippert.com/2013/07/25/what-is-the-type-of-the-null-literal/

转载自:猫头鹰博客

IOS 关闭键盘的两种方法

ios中把键盘关闭有两种思路

  1. 谁叫出的键盘,谁就是第一响应者,让第一响应者辞职,就可以把键盘回收叫回去。
  2. 让控制器管理的view停止编辑,这样的话,凡是这个view中的子控件调出的键盘都会被回收。

iOS屏幕适配

屏幕适配的两种方法
1.autoresizing
2.autolayout

autoresizing和autolayout二者只能用其一,两者是互斥的。

autoresizing 外面四根线的作用:分别设置距离#父容器#的距离是否保持不变,实线表示距离不变,虚线表示距离是可变的。

anturesizing里面两根线的作用:设置view的宽高是否随着父容器的宽高的变化而变化,实线表示宽高会随着父控件的宽高变化而变化,虚线表示不会随着父容器的宽高的变化而变化

通过代码设置autoresizing
1.将autolayout去掉
2.设置view的autoresizingMask属性

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

Autolayout

autolayout的两个核心概念

  1. 参照
    通过参照其他控件或父控件来设置当前控件的位置和大小
  2. 约束(constrains)
    通过添加约束限制控件的位置和大小

为什么要使用autolayout?
autoresizing无法解决兄弟控件之间的一些位置关系

autolayout

autolayout的错误和警告
错误
缺乏必要的约束,比如只设置了宽高没有说明X Y值
两个约束冲突 比如两个约束都确定了宽度

约束
约束后的值和控制器上面给定的值不一致,不过不会产生影响,程序运行后,会按照约束的值来设置。

Equals约束
当设置两个view的宽度相等时,约束内部执行的实际上是如下代码

view2.width = (view1.width + Constant) * Multipler

Constant 和 Multipler的值可以在约束指示器中给定,选中具体的约束,右边就会出现约束指示器如下图:
yueshu

一个例子

yueshulizi

实现上面的方法有两种思路

image

通过修改约束来实现动画

  1. 修改约束的constant值
  2. 在动画中调用setLayoutIfNeeded方法
UIView animateWithDuration:1.5 animations:^{
        [self.view setNeedsLayout];
    }

IOS 关闭键盘的两种方法

ios中把键盘关闭有两种思路

  1. 谁叫出的键盘,谁就是第一响应者,让第一响应者辞职,就可以把键盘回收叫回去。
  2. 让控制器管理的view停止编辑,这样的话,凡是这个view中的子控件调出的键盘都会被回收。

iOS9开发——Content Block 自定义拦截内容

iOS9开发——Content Block 自定义拦截内容

导语:

自从Apple发布了iOS9以来,基于content block新特性开发的app如雨后春笋般冒出来了,随着时间的推移,app的功能也越来越强大,越来越多样化了,从最初的简单的block广告,到现在出现了各种自定义规则,添加白名单,等等功能。这几个功能的背后,都有一个相同的地方,都需要对blockList.json这个规则文件进行处理,今天我们就来讲讲这个方面的内容。

市面上的产品

目前AppStore上面已经出现了非常多content block功能相关的app。部分产品也出现了可自定义屏蔽规则的功能,下面我们列举几个。

Purify:

提供social button、image、comments、scripts、fonts、tracking等类型的选择

界面如下:

1Blocker:

可选择block各种类型的资源,例如trackers、twitter widgets、web fonts、comments、adult sites等等,非常精细化的定制选择。同时选择多种block类型还需要在pro版本中才能实现。

界面如下:

AdBlocker:

功能同上面的类似,也是提供了一些基础的资源类型的屏蔽,比如image,fonts,scripts

界面如下:

还有很多类似的APP,都实现了针对不同资源进行定制屏蔽规则的功能,这里就不一一介绍了。下面,我们来探究一下,这个功能是如何实现的。

实现原理

我们知道,content blocker实现的原理是通过extension中的blockList.json文件来对Safari加载网页的过程进行资源加载的干预的。那么要实现定制规则,也就是要在工程中对这个blockList.json文件进行更改,或者替换掉,或者往这个json文件中增减内容以达到定制的目的。

那么这里就有一个问题了,containing app与extension之间该如何进行通信呢?(containing app就是包含extension插件的app)

首先,_containing app_需要读取extension中的json文件,然后,用户交互的过程中,我们将不同的规则写入json或者从json中删除,最后,我们需要把删除的json告诉或者说保存到extension中去。这里面的核心内容,其实就是host app和extension的通信。

containing app与extension通信

  • 不能直接通信。

尽管extension的bundle是放在containing app的bundle中的,但是两者是两个完全独立的进程,他们之间不能直接通信。

  • 共享shared resources

extension和containing app可以共同读写shared resources这么一个存储区域,这是通过App Group来实现的。下面我们来看看App Groups是如何来达到共享数据的。

APP Groups

app groups是iOS8新开放的功能,在OS X上其实早就可以用了,它的作用就是同一group下的app共享同一份读写空间,实现数据的共享。

开启APP Groups

使用该功能需要登录开发者账号

  • 在app中开启:

App Groups位于:

TARGETS-->AppExtensionDemo-->Capabilities-->App Groups

打开开关,添加一个group,命名唯一

  • 在extension中开启

我这里创建的extension名为ContentBlocker,因此extension groups位于

TARGETS-->ContentBlocker-->Capabilities-->App Groups

这里需要注意的是app 和extension中得Groups名称必须相同。这样才能保证它们访问的是同一块存储区域。

数据共享

介绍完app groups准备工作就进行得差不多了,下面来进行数据的共享。

通过NSFileManager来读写数据

NSFileManager在iOS7中提供了containerURLForSecurityApplicationGroupIdentifier方法,利用这个方法,可以得到共享数据的根路径。利用NSFileManager,我们可以轻松的保存数据,并读取数据。数据读写成功后,还需要刷新extension中的数据,iOS中提供了reloadContentBlockerWithIdentifier方法,供extension重新加载数据,这样,我们更改过的规则就可以及时的刷新了。

首先,将extension中的blockList.json文件读取出来,存入共享存储区。代码如下:

    //获取共享存储数据区的路径
    NSURL* groupURL=[[NSFileManagerdefaultManager]containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_NAME];
    NSString* Json_path = [groupURL.absoluteString stringByAppendingPathComponent:@"originAdRule.json"];
    //读取extension中的json文件
    NSString* defaultReules = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"blockerList" withExtension:@"json"] encoding:NSUTF8StringEncoding error:nil];
    //将读取出来的json文件内容写入共享存储数据区
    NSString* result = [defaultReules writeToURL:[NSURL URLWithString:Json_path] atomically:YES encoding:NSUTF8StringEncoding error:nil] ? @"Succeed":@"Failed";
    DebugLog(@"use new rules: %@",result);
    //通知content blocker extension重新加载数据
    [SFContentBlockerManager reloadContentBlockerWithIdentifier:APP_EXTENSION_NAME completionHandler:^(NSError * _Nullable error) {
        DebugLog(@"ReloadState:%@",error);
        if(nil == error){
        }
    }];

当用户选择了不同的规则类型的时候,我们通过将不同的规则写入到extension中,然后刷新数据来达到改变规则的目的。代码如下:

当开启相应的规则时:

-(void)enableBlockRuleWithName: (NSString *)ruleFileName
{
    // 1.从对应的blockList中读取文件
    NSString *contentString = [self getLocalJsonStringWithFileName:ruleFileName];

    //获取共享区文件的路径
    NSURL *groupUrl = [self getGroupShareJsonURL];
    //根据路径获取内容
    NSMutableString *originContentString = [NSMutableString stringWithContentsOfURL:groupUrl encoding:NSUTF8StringEncoding error:nil];
    // 将contentString的内容写入到originContentString中去
    [originContentString insertString:contentString atIndex:1];
    //将最新的json写入到共享数据区
    [originContentString writeToURL:groupUrl atomically:YES encoding:NSUTF8StringEncoding error:nil];
    //exstension重新加载group中的数据
    [SFContentBlockerManager reloadContentBlockerWithIdentifier:APP_EXTENSION_NAME completionHandler:^(NSError * _Nullable error) {
        DebugLog(@"ReloadState:%@",error);
        if(nil == error){
            DebugLog(@"extension data reload success");
        }
    }];
}

当关闭相应的规则时:

-(void)disableBlockRuleWithName: (NSString *)ruleFileName
{
    //获取共享区文件的路径
    NSURL *groupUrl = [self getGroupShareJsonURL];
    //根据路径获取内容
    NSMutableString *originContentString = [NSMutableString stringWithContentsOfURL:groupUrl encoding:NSUTF8StringEncoding error:nil];

    NSString *contentString = [self getLocalJsonStringWithFileName:ruleFileName];
    //查找字符串并删除,查找ImageRule并移除
    NSRange range = [originContentString rangeOfString:contentString];
    if (range.location == NSNotFound) {
        DebugLog(@"can't found rules in origin rule");
        return;
    }
    [originContentString deleteCharactersInRange:range];
    DebugLog(@"originContent is %@",originContentString);

    //将数据保存到共享区并刷新
    [originContentString writeToURL:groupUrl atomically:YES encoding:NSUTF8StringEncoding error:nil];
    [SFContentBlockerManager reloadContentBlockerWithIdentifier:APP_EXTENSION_NAME completionHandler:^(NSError * _Nullable error) {
        if (error == nil) {
            DebugLog(@"extension data reload success");
        }
    }];
}

这里还有一个重要的方法,将不同类型的规则分别放到不同的json中,然后根据用户选择的类型,读取相对应的json文件

-(NSString *)getLocalJsonStringWithFileName: (NSString *)ruleFileName
{
    // 1.从对应的blockList中读取文件
    NSString *path =[[NSBundle mainBundle] pathForResource:ruleFileName ofType:nil];
    NSString *contentString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    NSCharacterSet *whiteSpace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    contentString = [contentString stringByTrimmingCharactersInSet:whiteSpace];
    contentString = [contentString stringByAppendingString:@","];
    DebugLog(@"path is %@",path);
    DebugLog(@"contentString is %@",contentString);
    return contentString;
}
-(NSURL *)getGroupShareJsonURL
{
    return [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_NAME ]URLByAppendingPathComponent:JSON_BLOCKLIST_ORIGIN];
}

用户界面如下:

通过打开和关闭按钮,我们就可以实现针对block规则的定制功能了。

MVVM介绍

MVVM简介

我于 2011 年在 500px 找到自己的第一份 iOS 开发工作。虽然我已经在大学里做了好几年 iOS 外包开发,但这才是我的一个真正的 iOS 开发工作。我被作为唯一的 iOS 开发者被招聘去实现拥有漂亮设计的 iPad 应用。在短短七周里,我们就发布了 1.0 并持续迭代,添加了更多特性,但从本质上,代码库也变得更加复杂了。

有时我感觉就像我不知道在做什么。虽然我知道自己的设计模式——就像任何好的编程人员那样 —— 但我太接近我在做的产品以至于不能客观地衡量我的架构决策的有效性。当队伍中来了另外一位开发者时,我意识到我们陷入困境了。

从没听过 MVC ?有人称之为 Massive View Controller(重量级视图控制器),这就是我们那时候的感觉。我不打算介绍令人汗颜的细节,但说实在的,如果我不得不再次重来一次,我绝对会做出不同的决策。

我会修改一个关键架构,并将其带入我从那时起就在开发的各种应用,即使用一种叫做 Model-View-ViewModel 的架构替换 Model-View-Controller。

所以,MVVM 到底是什么?与其专注于说明 MVVM 的来历,不如让我们看一个典型的 iOS 是如何构建的,并从那里了解 MVVM:
Image of mvvm1
我们看到的是一个典型的 MVC 设置。Model 呈现数据,View 呈现用户界面,而 View Controller 调节它两者之间的交互。Cool!

稍微考虑一下,虽然 View 和 View Controller 是技术上不同的组件,但它们几乎总是手牵手在一起,成对的。你什么时候看到一个 View 能够与不同 View Controller 配对?或者反过来?所以,为什么不正规化它们的连接呢?
Image of a
这更准确地描述了你可能已经编写的 MVC 代码。但它并没有做太多事情来解决 iOS 应用中日益增长的重量级视图控制器的问题。在典型的 MVC 应用里,许多逻辑被放在 View Controller 里。它们中的一些确实属于 View Controller,但更多的是所谓的“表示逻辑(presentation logic)”,以 MVVM 属术语来说,就是那些将 Model 数据转换为 View 可以呈现的东西的事情,例如将一个 NSDate 转换为一个格式化过的 NSString。

我们的图解里缺少某些东西,那些使我们可以把所有表示逻辑放进去的东西。我们打算将其称为 “View Model” —— 它位于 View/Controller 与 Model 之间:

Image of mvvm

看起好多了!这个图解准确地描述了什么是 MVVM:一个 MVC 的增强版,我们正式连接了视图和控制器,并将表示逻辑从 Controller 移出放到一个新的对象里,即 View Model。MVVM 听起来很复杂,但它本质上就是一个精心优化的 MVC 架构,而 MVC 你早已熟悉。

现在我们知道了什么是 MVVM,但为什么我们会想要去使用它呢?在 iOS 上使用 MVVM 的动机,对我来说,无论如何,就是它能减少 View Controller 的复杂性并使得表示逻辑更易于测试。通过一些例子,我们将看到它如何达到这些目标。

此处有三个重点是我希望你看完本文能带走的:

MVVM 可以兼容你当下使用的 MVC 架构。
MVVM 增加你的应用的可测试性。
MVVM 配合一个绑定机制效果最好。
如我们之前所见,MVVM 基本上就是 MVC 的改进版,所以很容易就能看到它如何被整合到现有使用典型 MVC 架构的应用中。让我们看一个简单的 Person Model 以及相应的 View Controller:

@interface Person : NSObject

- (instancetype)initwithSalutation:(NSString *)salutation firstName:(NSString *)firstName lastName:(NSString *)lastName birthdate:(NSDate *)birthdate;

@property (nonatomic, readonly) NSString *salutation;
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *lastName;
@property (nonatomic, readonly) NSDate *birthdate;

@end

Cool!现在我们假设我们有一个 PersonViewController ,在 viewDidLoad 里,只需要基于它的 model 属性设置一些 Label 即可。

- (void)viewDidLoad {
    [super viewDidLoad];

    if (self.model.salutation.length > 0) {
        self.nameLabel.text = [NSString stringWithFormat:@"%@ %@ %@", self.model.salutation, self.model.firstName, self.model.lastName];
    } else {
        self.nameLabel.text = [NSString stringWithFormat:@"%@ %@", self.model.firstName, self.model.lastName];
    }

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
    self.birthdateLabel.text = [dateFormatter stringFromDate:model.birthdate];
}

这全都直截了当,标准的 MVC。现在来看看我们如何用一个 View Model 来增强它。

@interface PersonViewModel : NSObject

- (instancetype)initWithPerson:(Person *)person;

@property (nonatomic, readonly) Person *person;

@property (nonatomic, readonly) NSString *nameText;
@property (nonatomic, readonly) NSString *birthdateText;

@end

我们的 View Model 的实现大概如下:

@implementation PersonViewModel

- (instancetype)initWithPerson:(Person *)person {
    self = [super init];
    if (!self) return nil;

    _person = person;
    if (person.salutation.length > 0) {
        _nameText = [NSString stringWithFormat:@"%@ %@ %@", self.person.salutation, self.person.firstName, self.person.lastName];
    } else {
        _nameText = [NSString stringWithFormat:@"%@ %@", self.person.firstName, self.person.lastName];
    }

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
    _birthdateText = [dateFormatter stringFromDate:person.birthdate];

    return self;
}

@end

Cool!我们已经将 viewDidLoad 中的表示逻辑放入我们的 View Model 里了。此时,我们新的 viewDidLoad 就会非常轻量:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.nameLabel.text = self.viewModel.nameText;
    self.birthdateLabel.text = self.viewModel.birthdateText;
}

所以,如你所见,并没有对我们的 MVC 架构做太多改变。还是同样的代码,只不过移动了位置。它与 MVC 兼容,带来更轻量的 View Controllers。

可测试,嗯?是怎样?好吧,View Controller 是出了名的难以测试,因为它们做了太多事情。在 MVVM 里,我们试着尽可能多的将代码移入 View Model 里。测试 View Controller 就变得容易多了,因为它们不再做一大堆事情,并且 View Model 也非常易于测试。让我们来看看:

SpecBegin(Person)
    NSString *salutation = @"Dr.";
    NSString *firstName = @"first";
    NSString *lastName = @"last";
    NSDate *birthdate = [NSDate dateWithTimeIntervalSince1970:0];

    it (@"should use the salutation available. ", ^{
        Person *person = [[Person alloc] initWithSalutation:salutation firstName:firstName lastName:lastName birthdate:birthdate];
        PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person];
        expect(viewModel.nameText).to.equal(@"Dr. first last");
    });

    it (@"should not use an unavailable salutation. ", ^{
        Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];
        PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person];
        expect(viewModel.nameText).to.equal(@"first last");
    });

    it (@"should use the correct date format. ", ^{
        Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];
        PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person];
        expect(viewModel.birthdateText).to.equal(@"Thursday January 1, 1970");
    });
SpecEnd

如果我们没有将这个逻辑移入 View Model,我们将不得不实例化一个完整的 View Controller 以及伴随的 View,然后去比较我们 View 中 Lable 的值。这样做不只是会变成一个麻烦的间接层,而且它只代表了一个十分脆弱的测试。现在,我们可以按意愿自由地修改视图层级而不必担心破坏我们的单元测试。使用 MVVM 带来的对于测试的好处非常清晰,甚至从这个简单的例子来看也可见一斑,而在有更复杂的表示逻辑的情况下,这个好处会更加明显。

注意到在这个简单的例子中, Model 是不可变的,所以我们可以只在初始化的时候指定我们 View Model 的属性。对于可变 Model,我们还需要使用一些绑定机制,这样 View Model 就能在背后的 Model 改变时更新自身的属性。此外,一旦 View Model 上的 Model 发生改变,那 View 的属性也需要更新。Model 的改变应该级联向下通过 View Model 进入 View。

在 OS X 上,我们可以使用 Cocoa 绑定,但在 iOS 上我们并没有这样好的配置可用。我们想到了 KVO(Key-Value Observation),而且它确实做了很伟大的工作。然而,对于一个简单的绑定都需要很大的样板代码,更不用说有许多属性需要绑定了。作为替代,我个人喜欢使用 ReactiveCocoa,但 MVVM 并未强制我们使用 ReactiveCocoa。MVVM 是一个伟大的典范,它自身独立,只是在有一个良好的绑定框架时做得更好。

我们覆盖了不少内容:从普通的 MVC 派生出 MVVM,看它们是如何相兼容的范式,从一个可测试的例子观察 MVVM,并看到 MVVM 在有一个配对的绑定机制时工作得更好。如果你有兴趣学习更多关于 MVVM 的知识,你可以看看这篇博客,它用更多细节解释了 MVVM 的好处,或者这一篇关于我们如何在最近的项目里使用 MVVM 获得巨大的成功的文章。我同样还有一个经过完整测试,基于 MVVM 的应用,叫做 C-41 ,它是开源的。去看看吧,如果你有任何疑问,请告诉我。

转载自:http://objccn.io/issue-13-1/

Java vs Objective-C

Java And Objective-C

要问当前市场上最火的技术是什么,一定少不了Android和IOS这两门移动开发技术。这两个热门移动开发技术的支持语言就是Java和Objective-C,当然近来swift已经有赶超Objective-C并取代其成为IOS开发语言的趋势了。今天我们就来聊聊Java和Objective-C之间的一些异同,以及Android与IOS开发之间的那些事。

java与objective-C语言

本文将要对以下几个方面对java和oc进行对比:

  • 基础语法
    • 基本数据类型
    • 循环控制
    • 方法和消息
    • 类的声明和实现
    • 接口与协议
    • 委托与监听机制
    • 数组和集合
    • 文件的读写
    • 空指针
    • 代码块block
    • Property和Synthesize
  • 内存管理
  • 异常的处理
  • 多线程

一、基本数据类型
OC中的基本数据类型同java没有太大的差别,整体上基本一样,只有一个BOOL类型比较特殊,值为YES、NO,区别于java的true、false。具体看下图:
image of data-type

二、循环控制
OC中的循环控制同java相同,分为for和增强for循环。oc中增强for循环语法为for...in,仅有这点稍微不同,其他方面两者基本上是相同的,见下面的例子
oc中增强for循环:

 for (QueryAppenderRule *rule in rules) {
        [self addRuleToMapping:rule];
    }

Java中的增强for循环:

 for (QueryAppenderRule rule : rules) {
            addRuleToMapping(rule);
        }

一般的for循环,两者完全一样,都遵循语法如:

for(int i = 0; i < 100,i++){
//Do something
}

😂 😂
switch循环也是如此,oc与java中得switch的用法也是完全一致的,这里就不再介绍了。

一、函数的对比
我们先来看最简单的helloworld方法

java:

public void helloworld (boolean isHelloworld){
    //Do something
}

objc:

-(void)helloworld :(bool)isHelloword
{
    //Do something
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.