michealyang / blog Goto Github PK
View Code? Open in Web Editor NEW技术博客
技术博客
iOS9出来已经一段时间了,适配方面的文章也有不少了,这里有一篇比较详细的文章:点我,这里不作全面的介绍,只关注其中的一点,也就是iOS9_Url_Scheme——Apple引入的白名单概念。:joy_cat:
也就是说:在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来表示某个特殊的状态。
在某些地方,程序员可能会觉得某个变量从逻辑上可以保证它不会为空,于是就省略掉了空指针检查。
可是,时过境迁之后,因为代码的各种变化,导致这样的前提不再成立的时候,空指针异常就发生了。
代码因此非常脆弱。而有些谨慎的程序员,为未雨绸缪计,会在各个地方都加上保护性的空指针检查,又让代码变得非常臃肿。
那么病根究竟是出在哪里呢?
> The type of a null-literal is the null type (§11.2.7).
char *myChar = 123; // compile error
std::cout << *myChar << std::endl;
但是,我们把整数的值变一下,它又可以编译通过了
char *myChar = 0;
std::cout << *myChar << 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<&str> = Some("howdy");
match msg {
Some(m) => println!("{}", m), // 如果是Some类型,则m匹配到&str类型,于是它可以调用&str所属的成员函数
None => () // 如果是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("not nullable");
println!("call member function directly. string lenght is {}", not_nullable.len());
// nullable1是Option类型,它可以使用unwrap_or函数,该函数可以提取出里面的值,如果为None,则返回参数中提供的默认值
let nullable1 : Option<String> = Some("hello world".to_owned());
get_length1(nullable1);
// nullable2是Option类型,它可以使用map函数,该函数把一个Option类型通过一个closure映射到另外一个Option类型
let nullable2 : Option<String> = Option::None;
get_length2(nullable2);
}
fn get_length1(nullable : Option<String>) {
let len = nullable.unwrap_or("default value".to_owned()).len();
println!("fall back to default value. string length is {}", len);
}
fn get_length2(nullable : Option<String>) {
let len = nullable.map(|s| s.len());
println!("map an Option to another Option. string length is {:?}.", 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这样的设计有以下几个优点:
所以说,空指针的确是一个编程语言设计史上的重大失误,该错误流毒之广,影响之巨,难有其匹。
怪不得Tony老爷子要感叹一句:一失足成千古恨,再回头已百年身!
参考资料:
转载自:猫头鹰博客
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?
autoresizing无法解决兄弟控件之间的一些位置关系
autolayout的错误和警告
错误
缺乏必要的约束,比如只设置了宽高没有说明X Y值
两个约束冲突 比如两个约束都确定了宽度
约束
约束后的值和控制器上面给定的值不一致,不过不会产生影响,程序运行后,会按照约束的值来设置。
Equals约束
当设置两个view的宽度相等时,约束内部执行的实际上是如下代码
view2.width = (view1.width + Constant) * Multipler
Constant 和 Multipler的值可以在约束指示器中给定,选中具体的约束,右边就会出现约束指示器如下图:
实现上面的方法有两种思路
通过修改约束来实现动画
UIView animateWithDuration:1.5 animations:^{
[self.view setNeedsLayout];
}
ios中把键盘关闭有两种思路
自从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的通信。
尽管extension的bundle是放在containing app的bundle中的,但是两者是两个完全独立的进程,他们之间不能直接通信。
extension和containing app可以共同读写shared resources这么一个存储区域,这是通过App Group来实现的。下面我们来看看App Groups是如何来达到共享数据的。
app groups是iOS8新开放的功能,在OS X上其实早就可以用了,它的作用就是同一group下的app共享同一份读写空间,实现数据的共享。
使用该功能需要登录开发者账号
App Groups位于:
TARGETS-->AppExtensionDemo-->Capabilities-->App Groups
我这里创建的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简介
我于 2011 年在 500px 找到自己的第一份 iOS 开发工作。虽然我已经在大学里做了好几年 iOS 外包开发,但这才是我的一个真正的 iOS 开发工作。我被作为唯一的 iOS 开发者被招聘去实现拥有漂亮设计的 iPad 应用。在短短七周里,我们就发布了 1.0 并持续迭代,添加了更多特性,但从本质上,代码库也变得更加复杂了。
有时我感觉就像我不知道在做什么。虽然我知道自己的设计模式——就像任何好的编程人员那样 —— 但我太接近我在做的产品以至于不能客观地衡量我的架构决策的有效性。当队伍中来了另外一位开发者时,我意识到我们陷入困境了。
从没听过 MVC ?有人称之为 Massive View Controller(重量级视图控制器),这就是我们那时候的感觉。我不打算介绍令人汗颜的细节,但说实在的,如果我不得不再次重来一次,我绝对会做出不同的决策。
我会修改一个关键架构,并将其带入我从那时起就在开发的各种应用,即使用一种叫做 Model-View-ViewModel 的架构替换 Model-View-Controller。
所以,MVVM 到底是什么?与其专注于说明 MVVM 的来历,不如让我们看一个典型的 iOS 是如何构建的,并从那里了解 MVVM:
我们看到的是一个典型的 MVC 设置。Model 呈现数据,View 呈现用户界面,而 View Controller 调节它两者之间的交互。Cool!
稍微考虑一下,虽然 View 和 View Controller 是技术上不同的组件,但它们几乎总是手牵手在一起,成对的。你什么时候看到一个 View 能够与不同 View Controller 配对?或者反过来?所以,为什么不正规化它们的连接呢?
这更准确地描述了你可能已经编写的 MVC 代码。但它并没有做太多事情来解决 iOS 应用中日益增长的重量级视图控制器的问题。在典型的 MVC 应用里,许多逻辑被放在 View Controller 里。它们中的一些确实属于 View Controller,但更多的是所谓的“表示逻辑(presentation logic)”,以 MVVM 属术语来说,就是那些将 Model 数据转换为 View 可以呈现的东西的事情,例如将一个 NSDate 转换为一个格式化过的 NSString。
我们的图解里缺少某些东西,那些使我们可以把所有表示逻辑放进去的东西。我们打算将其称为 “View Model” —— 它位于 View/Controller 与 Model 之间:
看起好多了!这个图解准确地描述了什么是 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 ,它是开源的。去看看吧,如果你有任何疑问,请告诉我。
要问当前市场上最火的技术是什么,一定少不了Android和IOS这两门移动开发技术。这两个热门移动开发技术的支持语言就是Java和Objective-C,当然近来swift已经有赶超Objective-C并取代其成为IOS开发语言的趋势了。今天我们就来聊聊Java和Objective-C之间的一些异同,以及Android与IOS开发之间的那些事。
本文将要对以下几个方面对java和oc进行对比:
一、基本数据类型
OC中的基本数据类型同java没有太大的差别,整体上基本一样,只有一个BOOL类型比较特殊,值为YES、NO,区别于java的true、false。具体看下图:
二、循环控制
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
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.