分类目录归档:iOS

客户端调起方案解析(scheme、intent uri、Universal Links)

移动互联时代,很多互联网服务都会同时具备网站以及移动客户端,很多人认为APP的能帮助建立更稳固的用户关系,于是经常会接到各种从浏览器、webview、短信、甚至是在其他APP中唤醒APP的运营需求。

运营推广场景

  • 微信、QQ等 -> 唤醒APP
    用户通过某APP分享了一条链接至微信或QQ,用户B点开该链接后,会引导用户B打开该APP或者下载该APP。
  • 浏览器 -> 唤醒APP
    用户A通过浏览器打开了某APP的M站或者官网,如果检测到A来自手机端,则会引导用户打开该APP或者下载该APP。
  • 短信、邮件、二维码等 -> 唤醒APP
    用户A打开了某APP的推广短信,邮件或者扫描二维码等,会引导用户打开该APP或者下载该APP。
  • 其他APP -> 唤醒APP
    用户A通过第三方APP分享了(任何可以分享信息的品台或工具:IM或者短信等)一条链接至用户B,用户B点开该链接后,链接会引导用户B打开指定APP或者下载指定APP。

APP服务化理念

所谓APP的服务化就是利用唤醒功能将APP的特定页面做为一个单独的服务或者内容,通过一定的渠道和载体传播出去,并且能够像传统的网页链接那样被一键唤醒。

更多关于APP服务化理念,推荐大家看看这篇文章

那么移动平台提供了哪些唤醒APP的方法呢?

如何唤醒APP

目前常见的唤醒APP方式有几种:

  • URL Scheme

URL Scheme是iOS,Android平台都支持,只需要原生APP开发时注册scheme, 那么用户点击到此类链接时,会自动唤醒APP,借助于URL Router机制,则还可以跳转至指定页面。比如:

<!-- 唤醒APP并跳转至指定的path页面 -->
<a href="<scheme>://<path>?<params>=<value>">打开APP</a>

<!-- JS设置iframe src跳转至指定的path页面 -->
//创建一个隐藏的iframe
var ifr = document.createElement('iframe');
ifr.src = '<scheme>://<path>?<params>=<value>';
ifr.style.display = 'none';
document.body.appendChild(ifr);
//记录唤醒时间
var openTime = +new Date();
window.setTimeout(function(){
    document.body.removeChild(ifr);
    //如果setTimeout 回调超过2500ms,则弹出下载
    if( (+new Date()) - openTime > 2500 ){
        window.location = '指定的下载页面';
    }
},2000)

这种方式是当期使用最广泛,也是最简单的,但是需要手机,APP支持URL Scheme
优点: 开发成本低,绝大多数都支持,web-native协议制定也简单。
缺点: 错误处理情况因平台不同,难以统一处理,部分APP会直接跳错误页(比如Android Chrome/41,iOS中老版的Lofter);也有的停留在原页面,但弹出提示“无法打开网页”(比如iOS7);iOS8以及最新的Android Chrome/43 目前都是直接停留在当前页,不会跳出错误提示。
支持情况: iOS在实际使用中,腾讯系的微信,QQ明确禁止使用,iOS9以后Safari不再支持通过js,iframe等来触发scheme跳转,并且还加入了确认机制,使得通过js超时机制来自动唤醒APP的方式基本不可用;Android平台则各个app厂商差异很大,比如Chrome从25及以后就同Safari情况一样。

  • Android intent

这是Android平台独有的,使用方式如下:

intent:
HOST/URI-path // Optional host 
#Intent; 
  package=[string]; 
  action=[string]; 
  category=[string]; 
  component=[string]; 
  scheme=[string]; 
end;

这里的HOST/URI-path, 与普通http URL 的host/path书写方式相同, package是Android APP的包名,其它参数如action、category、component不是很理解, 有兴趣可以去了解官方文档。代码如下:

<!-- 唤醒APP并跳转至指定的path页面 -->
<a href="intent://<role>/<path>#Intent;scheme=<scheme>;package=com. domain;end"">打开APP</a>

如果手机能匹配到相应的APP,则会直接打开;如没有安装,则会跳到手机默认的应用商店,比如Google原生系统Nexus 5,将会直接跳到Google Play,对于国内各厂商定制过的系统,则跳转到各自的默认应用商店,或者弹出商店供选择。intentscheme相对完善的一点是,提供一个打开失败去向URL的选项,可以通过指定参数S.browser_fallback_url来指定去向URL。比如打开APP动作,如果打开失败,则跳转到APP下载页,这对于国内的特殊网络环境,还是挺有用的。

  • Safari内置APP广告条

在页面Head中增加如下meta, 添加智能App广告条,可以自动判断是否已安装应用,只能用于Safari,在第三方应用中就不行了。

<meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"
  • Android Chrome内置APP安装提示

这个是Mobile Chrome 43 beta新加入的特性,在用户浏览某一个网站多次后,如果Chrome发现该站点有原生APP,则会提示用户下载原生APP,此项特性开发者无法干预,完全是Google的推荐行为。

  • Universal Links

在2015年的WWDC大会上,Apple推出了iOS 9的一个功能:Universal Links通用链接。如果你的App支持Universal Links,那就可以访问HTTP/HTTPS链接直接唤起APP进入具体页面,不需要其他额外判断;如果未安装App,访问此通用链接时,可以一个自定义网页。

优点:

  • 唯一性:不像自定义的scheme,因为它使用标准的HTTP/HTTPS链接到你的web站点,所以它不会被其它的app所声明.另外,Custom URL scheme 因为是自定义的协议,所以在没有安装 app 的情况下是无法直接打开的,而Universal Links本身是一个HTTP/HTTPS链接,所以有更好的兼容性;
  • 安全:当用户的手机上安装了你的app,那么iOS将去你的网站上去下载你上传上去的说明文件(这个说明文件声明了APP可以打开哪些类型的http链接)。因为只有你自己才能上传文件到你网站的根目录,所以你的网站和你的app之间的关联是安全的;
  • 可变:当用户手机上没有安装你的app的时候,Universal Links也能够工作。如果你愿意,在没有安装APP的时候,用户点击链接,会在safari中展示你网站的内容;
  • 简单:一个URL链接,可以同时作用于网站和app,可以定义统一的web-native协议;
  • 私有:其它APP可以在不需要知道是否安装了的情况下和你的APP相互通信;

缺点:

  • 只支持iOS9及以上系统;当使用Universal Link打开APP之后,状态栏右上角会出现链接地址,点击它会取消Universal Link,需引导用户重新使用Safari再次打开该链接,弹出Safari内置APP广告条,再点击打开重新开启Universal Link。

iOS9开启Universal Links

首先,你必须有一个域名,且这个域名的网站需要支持https,然后拥有网站的上传到.well-known目录的权限(这个权限是为了上传一个Apple指定的文件apple-app-site-association),有了这个先决条件才能够继续下面的步骤:

  • 创建一个json格式的命名为apple-app-site-association文件,注意这个文件必须没有后缀名,文件名必须为`apple-app-site-association!!!
{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "9JA89QQLNQ.com.apple.wwdc", 
                "paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"]
            },
            {
                "appID": "ABCD1234.com.apple.wwdc", 
                "paths": [ "*" ]
            }
        ]
    }
}

说明: appID = teamId.yourapp’s bundle identifier
paths = APP支持的路径列表,只有这些指定的路径的链接,才能被APP所处理,大小写敏感。举个例子,如果你的网站是www.domain.com,你的path写的是”/support/*”,那么当用户点击www.domain.com/support/<path>?<params>=<value>,就可以唤醒APP了,相反www.domain.com/other就不会。此外Apple为了方便开发者,提供了一个网址来验证我们编写的这个apple-app-site-association是否合法有效。

  • 激活Xcode工程中的Associated Domains能力,在其中的Domains中填入你想支持的域名(这里不是随便填的,是可以支持你需要的Universal Links的域名), 必须以applinks:为前缀,例如:applinks:www.domain.comApple将会在合适的时候,从这个域名请求apple-app-site-association文件。注意:当你打开Associated Domains后,Xcode会在你的工程中添加.entitlements文件,并且登录开发者中心,可以看到Associated Domains处于Enable状态。
  • AppDelegate里实现如下代理方法:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {
    // NSUserActivityTypeBrowsingWeb 由Universal Links唤醒的APP
    if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
        NSURL *webpageURL = userActivity.webpageURL;
        NSString *host = webpageURL.host;
        if ([host isEqualToString:@"yohunl.com"]) {
            //进行我们需要的处理
        } else {
            [[UIApplication sharedApplication]openURL:webpageURL];
        }
    }
    return YES;
}

至此APP已经开启Universal Links,可以通过链接唤醒APP,并跳转至指定页面了。

  • Android App Links

在2015年的Google I/O大会上,Android M宣布了一个新特性:App Links让用户在点击一个普通web链接的时候可以打开指定APP的指定页面,前提是这个APP已经安装并且经过了验证,否则会显示一个打开确认选项的弹出框。在推动deep linking上Google和Apple可谓英雄所见略同,优缺点也大致相同,只支持Android M以上系统。

Android M开启Universal Links

开启Android App Links的方式也大致同iOS一致:

先决条件:
  1. 注册一个域名
  2. 域名的SSL通道
  3. 具有上传JSON文件到域名的能力
  4. Android Studio 1.3 Preview 及以上
  5. Gradle 版本 — com.android.tools.build:gradle:1.3.0-beta3 及以上
  6. 设置 compileSdkVersion 为 android-MNC 及以上
  7. buildToolsVersion — 23.0.0 rc2 及以上
  8. 创建一个json格式的web-app关联文件,如assetlinks.json,上传到web端
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "example.com.puppies.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
  },
  {
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "example.com.monkeys.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

其中
package_name: manifest中声明的包名。
sha256_cert_fingerprints: 可以使用如下命令生成APP的sha256指纹签名

// keystore中持有app release keys的app路径。
// 这个路径依赖于项目设置,因此不同的app是不同的。
keytool -list -v -keystore my-release-key.keystore

上传这个文件到服务器的.well-known/assetlinks.json,为了避免今后每个app链接请求都访问网络,安卓只会在app安装的时候检查这个文件。

  • 创建一个处理App Links的activity,这个activity的目的是为了实现一种这样的机制:负责捕获与解析深度链接,同时转发用户到正确的视图。同时配置激活App Links能力,如下所示:
<activity
  android:name="com.your.app.activity.ParseDeepLinkActivity"
  android:alwaysRetainTaskState="true"
  android:launchMode="singleTask"
  android:noHistory="true"
  android:theme="@android:style/Theme.Translucent.NoTitleBar">

  // 此处激活 App Links
  <intent-filter android:autoVerify="true">
      // 注意yourdomain.com 与 www.yourdomain.com 被看成两个不同的域名,因此你需要为每个域名添加一对http和https
    <data android:scheme="http" android:host="yourdomain.com" />
    <data android:scheme="https" android:host="yourdomain.com" />
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
  </intent-filter>
</activity>
  • 实现App Linksactivity的处理逻辑
public class ParseDeepLinkActivity extends Activity {
  public static final String PRODUCTS_DEEP_LINK = "/products";
  public static final String XMAS_DEEP_LINK = "/campaigns/xmas";

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Extrapolates the deeplink data
    Intent intent = getIntent();
    Uri deeplink = intent.getData();

    // Parse the deeplink and take the adequate action 
    if (deeplink != null) {
      parseDeepLink(deeplink);
    }
  }

  private void parseDeepLink(Uri deeplink) {
    // The path of the deep link, e.g. '/products/123?coupon=save90'
    String path = deeplink.getPath();

    if (path.startsWith(PRODUCTS_DEEP_LINK)) {
      // Handles a product deep link
      Intent intent = new Intent(this, ProductActivity.class);
      intent.putExtra("id", deeplink.getLastPathSegment()); // 123
      intent.putExtra("coupon", deeplink.getQueryParameter("coupon")); // save90
      startActivity(intent);
    } else if (XMAS_DEEP_LINK.equals(path)) {
      // Handles a special xmas deep link
      startActivity(new Intent(this, XmasCampaign.class));
    }  else {
      // Fall back to the main activity
      startActivity(new Intent(context, MainActivity.class));
    }
  }
}

至此APP已经开启App Links,可以通过链接唤醒APP,并跳转至指定页面了。

后记

总结以上各种方案,唤醒能力似乎都不是很完美,从长远技术趋势来看都是Deep Links,都需要

  • 一个支持HTTPS的web站

但面对移动互联网浪潮中海量APP的唤醒能力需求,一定会有创业公司来做这件事,比如国外的HoKoLinks,国内的魔窗,是自己造轮子,还是用轮子,各有利弊。

微信小程序架构解析,工作原理解析

作者介绍:

渠宏伟,腾讯高级工程师,从事Web前端开发5年,先后负责企鹅电竞、腾讯视频VIP、腾讯OA开发框架、腾讯微信HR助手等项目。对Web前端架构、.NET架构有丰富的经验。

| 导语 微信小程序的公测掀起了学习小程序开发的浪潮,天生跨平台,即用即走、媲美原生体验、完善的文档、高效的开发框架,小程序给开发者带来了很多惊喜。通过这篇文章和大家一起分析小程序的架构,分享开发经验。

一、小程序介绍

1、小程序特点

微信小程序原理解析

2、小程序演示


视频地址:https://v.qq.com/x/page/w0353d7co6y.html

3、小程序为什么那么快

微信小程序原理解析
Page Frame

Native预先额外加载一个WebView
当打开指定页面时,用默认数据直接渲染,请求数据回来时局部更新
返回显示历史View
退出小程序,View状态不销毁

4、小程序入口

微信小程序原理解析

扫码进入小程序

搜索小程序

小程序发送到桌面(Android)

发送给朋友

二、小程序架构

微信小程序的框架包含两部分View视图层、App Service逻辑层,View层用来渲染页面结构,AppService层用来逻辑处理、数据请求、接口调用,它们在两个线程里运行。

视图层使用WebView渲染,逻辑层使用JSCore运行。

视图层和逻辑层通过系统层的JSBridage进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。

微信小程序原理解析

 

小程序启动时会从CDN下载小程序的完整包

微信小程序原理解析

 

三、View (页面视图)

视图层由 WXML 与 WXSS 编写,由组件来进行展示。

将逻辑层的数据反应成视图,同时将视图层的事件发送给逻辑层。

1、View – WXML

WXML(WeiXin Markup Language)

支持数据绑定

支持逻辑算术、运算

支持模板、引用

支持添加事件(bindtap)

微信小程序原理解析

wxml编译器:wcc  把wxml文件 转为 js   执行方式:wcc index.wxml

2、View – WXSS

WXSS(WeiXin Style Sheets)

支持大部分CSS特性

添加尺寸单位rpx,可根据屏幕宽度自适应

使用@import语句可以导入外联样式表

不支持多层选择器-避免被组件内结构破坏

微信小程序原理解析

wxss编译器:wcsc 把wxss文件转化为 js 执行方式: wcsc index.wxss

3、View – WXSS Selectors

WXSS目前支持如下选择器:

微信小程序原理解析
4、View – Component

小程序提供了一系列组件用于开发业务功能,按照功能与HTML5的标签进行对比如下:

微信小程序原理解析

小程序的组件基于Web Component标准

使用Polymer框架实现Web Component

微信小程序原理解析

 

5、View – Native Component

目前Native实现的组件有 <canvas/> <video/> <map/> <textarea/>

Native组件层在WebView层之上

微信小程序原理解析

 

四、App Service(逻辑层)

逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈

1、App( ) 小程序的入口;Page( ) 页面的入口

3、提供丰富的 API,如微信用户数据,扫一扫,支付等微信特有能力。

4、每个页面有独立的作用域,并提供模块化能力。

5、数据绑定、事件分发、生命周期管理、路由管理

运行环境

IOS – JSCore

Android – X5 JS解析器

DevTool – nwjs Chrome 内核

1、App Service – Binding

数据绑定使用 Mustache 语法(双大括号)将变量包起来,动态数据均来自对应 Page 的 data,可以通过setData方法修改数据。
事件绑定的写法同组件的属性,以 key、value 的形式,key 以bind或catch开头,然后跟上事件的类型,如bindtap, catchtouchstart,value 是一个字符串,需要在对应的 Page 中定义同名的函数。

微信小程序原理解析

微信小程序原理解析

 

2、App Service – Life Cylce

微信小程序原理解析

 

3、App Service – API

API通过JSBridge和Native 进行通信

微信小程序原理解析

 

4、App Service – Router

navigateTo(OBJECT)

保留当前页面,跳转到应用内的某个页面,使用navigateBack可以返回到原页面。页面路径只能是五层

redirectTo(OBJECT)

关闭当前页面,跳转到应用内的某个页面。

navigateBack(OBJECT)

关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages()) 获取当前的页面栈,决定需要返回几层。

 

五、小程序开发经验

1、小程序存在的问题

小程序仍然使用WebView渲染,并非原生渲染
需要独立开发,不能在非微信环境运行。
开发者不可以扩展新组件。
服务端接口返回的头无法执行,比如:Set-Cookie。
依赖浏览器环境的js库不能使用,因为是JSCore执行的,没有window、document对象。
WXSS中无法使用本地(图片、字体等)。
WXSS转化成js 而不是css,为了兼容rpx。
WXSS不支持级联选择器。
小程序无法打开页面,无法拉起APP。

小程序不能和公众号重名,于是小程序的名字就成了:自选股+、滴滴出行DiDi 。

 

2、小程序可以借鉴的优点

提前新建WebView,准备新页面渲染。
View层和逻辑层分离,通过数据驱动,不直接操作DOM。
使用Virtual DOM,进行局部更新。
全部使用https,确保传输中安全。
使用离线能力。
前端组件化开发。
加入rpx单位,隔离设备尺寸,方便开发。

 

3、脱离微信的“小程序”:PWA 渐进式应用

PWA 全称是 Progressive Web Apps ,译成中文就是渐进式应用,是 Google 在 2015 年 6 月 15 日提出的概念。
Progressive Web Apps 是结合了 web 和 原生应用中最好功能的一种体验。对于首次访问的用户它是非常有利的, 用户可以直接在浏览器中进行访问,不需要安装应用。随着时间的推移当用户渐渐地和应用建立了联系,它将变得越来越强大。它能够快速地加载,即使在弱网络环境下,能够推送相关消息, 也可以像原生应用那样添加至主屏,能够有全屏浏览的体验。

微信小程序原理解析
PWA具有如下特点:

渐进增强 – 支持的新特性的浏览器获得更好的体验,不支持的保持原来的体验。
离线访问 – 通过 service workers 可以在离线或者网速差的环境下工作。
类原生应用 – 使用app shell model做到原生应用般的体验。
可安装 – 允许用户保留对他们有用的应用在主屏幕上,不需要通过应用商店。
容易分享 – 通过 URL 可以轻松分享应用。
持续更新 – 受益于 service worker 的更新进程,应用能够始终保持更新。
安全 – 通过 HTTPS 来提供服务来防止网络窥探,保证内容不被篡改。
可搜索 – 得益于 W3C manifests 元数据和 service worker 的登记,让搜索引擎能够找到 web 应用。
再次访问 – 通过消息推送等特性让用户再次访问变得容易。

Web App Manifest使Web更像Native

Web App Manifest以JSON的格式定义Web应用的相关配置(应用名称、图标或图像连接、启动URL、自定义特性、启动默认配置、全屏设置等)。

微信小程序原理解析

Service Workers增强Web能力

通过Service Works实现资源离线缓存和更新

微信小程序原理解析

App Shell 提升显示效率

App Shell(应用外壳)是应用的用户界面所需的最基本的 HTML、CSS 和 JavaScript,首次加载后立刻被缓存下来,不需要每次使用时都被下载,而是只异步加载需要的数据,以达到UI保持本地化。

微信小程序原理解析

了解更多pwa资料:
https://developers.google.com/web/progressive-web-apps/

 

iOS 10 适配 ATS(app支持https通过App Store审核)

iOS 10 适配 ATS

一. HTTPS

其实HTTPS从最终的数据解析的角度,与HTTP没有任何的区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。从HTTP切换到HTTPS是一个非常简单的过程,在做具体的切换操作之前,我们需要了解几个概念:

SSL/TLS

为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。SSL目前的版本是3.0,被IETF(Internet Engineering Task Force)定义在RFC 6101中,之后IETF对SSL 3.0进行了升级,于是出现了TLS(Transport Layer Security) 1.0,定义在RFC 2246。实际上我们现在的HTTPS都是用的TLS协议,但是由于SSL出现的时间比较早,并且依旧被现在浏览器所支持,因此SSL依然是HTTPS的代名词,但无论是TLS还是SSL都是上个世纪的事情,SSL最后一个版本是3.0,今后TLS将会继承SSL优良血统继续为我们进行加密服务。

简单的来说,SSL/TSL通过四次握手。SSL协议的工作流程:

服务器认证阶段:

客户端向服务器发送一个开始信息“Hello”以便开始一个新的会话连接;
服务器根据客户的信息确定是否需要生成新的主密钥,如需要则服务器在响应客户的“Hello”信息时将包含生成主密钥所需的信息;
客户根据收到的服务器响应信息,产生一个主密钥,并用服务器的公开密钥加密后传给服务器;
服务器恢复该主密钥,并返回给客户一个用主密钥认证的信息,以此让客户认证服务器。

用户认证阶段:

在此之前,服务器已经通过了客户认证,这一阶段主要完成对客户的认证。
经认证的服务器发送一个提问给客户,客户则返回(数字)签名后的提问和其公开密钥,从而向服务器提供认证。

二. App Transport Security

iOS9中新增App Transport Security(简称ATS)特性, 主要使到原来请求的时候用到的HTTP,都转向TLS1.2协议进行传输。这也意味着所有的HTTP协议都强制使用了HTTPS协议进行传输。在 iOS 9 和 OS X 10.11 中,默认情况下非 HTTPS 的网络访问是被禁止的。当然,因为这样的推进影响面非常广,作为缓冲,我们可以在 Info.plist 中添加 NSAppTransportSecurity 字典并且将 NSAllowsArbitraryLoads 设置为 YES 来禁用 ATS。

不过,WWDC 16 中,Apple 表示将继续在 iOS 10 和 macOS 10.12 里收紧对普通 HTTP 的访问限制。从 2017 年 1 月 1 日起,所有的新提交 app 默认是不允许使用 NSAllowsArbitraryLoads 来绕过 ATS 限制的,也就是说,我们最好保证 app 的所有网络请求都是 HTTPS 加密的,否则可能会在应用审核时遇到麻烦。

三. iOS10 NSAppTransportSecurity

通过在info.plist中配置这个键,开发者可以自定义网络安全策略。例如:

允许针对个别服务器的不安全访问。
允许不安全的 web 或媒体内容访问,但不影响整个app的ATS策略。
启用新的安全特性,例如Certificate Transparency。
对NSAppTransportSecurity的支持自 iOS9.0,OS X v10.11 开始,适用于 app 和 app extension。

自 iOS10.0,macOS 10.12 开始,增加了对下列子键的支持:

  • NSAllowsArbitraryLoadsInMedia
  • NSAllowsArbitraryLoadsInWebContent
  • NSRequiresCertificateTransparency
  • NSAllowsLocalNetworking

ATS Configuration Basics / ATS 配置基础知识

对于使用 iOS9.0, OS X v10.11 SDK 及以上的 app 来说,ATS(App Transport Security)默认开启,NSAllowsArbitraryLoads是字典NSAppTransportSecurity的根键,默认值NO。

在启用 ATS 的情况下,所有的 HTTP 请求必须为 HTTPS(RFC 2818) 连接。任何不安全的 HTTP 请求都将失败。ATS 使用 TLS(Transport Layer Security)v1.2(RFC 5246)。

下面是字典NSAppTransportSecurity的总体结构,所有键都是非必填项:

NSAppTransportSecurity : Dictionary {
    NSAllowsArbitraryLoads : Boolean
    NSAllowsArbitraryLoadsInMedia : Boolean
    NSAllowsArbitraryLoadsInWebContent : Boolean
    NSAllowsLocalNetworking : Boolean
    NSExceptionDomains : Dictionary {
        <domain-name-string> : Dictionary {
            NSIncludesSubdomains : Boolean
            NSExceptionAllowsInsecureHTTPLoads : Boolean
            NSExceptionMinimumTLSVersion : String
            NSExceptionRequiresForwardSecrecy : Boolean   // Default value is YES
            NSRequiresCertificateTransparency : Boolean
        }
    }
}

可以看出,所有键可以分为两类:主键,这些键用来定义 app 的总体 ATS 策略;子键,即NSExceptionDomains下面的键,使用这些键针对某个域名单独配置。

主键包括:

  • NSAllowsArbitraryLoads

    设置为 YES,解除整个 app 的 ATS 限制;但是,通过NSExceptionDomains进 行的配置依然有效。默认值为 NO。
    注意:设置为 YES,会引发 App Stroe 的审查,开发者必须说明原因。

  • NSAllowsArbitraryLoadsInMedia

    设置为 YES,解除通过 AV Foundation 框架访问媒体内容时的 ATS 限制;启用这个 键,务必确保载入的媒体内容已经被加密,例如受FairPlay保护的文件,或者是安全的 HLS流媒,其中不包含敏感的个人信息。默认为 NO。

  • NSAllowsArbitraryLoadsInWebContent

    设置为 YES,解除通过 web view 发出的网络请求的 ATS 限制。启用这个键,可以使 app 访问任意网页内容,但不影响 app 的总体 ATS 策略。此键值默认为 NO。

  • NSAllowsLocalNetworking

    设置为 YES,使得 app 可以载入任意本地资源,但不影响 app 的总体 ATS 策略。默 认为 NO。

  • NSExceptionDomains

    为一个或多个域名单独配置 ATS。
    被单独配置的域名,默认受到完全的 ATS 限制,不管NSAllowsArbitraryLoads的值 如何;需要通过子键,进一步配置

所有的子键都属于NSExceptionDomain。向Info.plist中添加这一主键:

  • 创建字典,针对一个或多个域名,以便进行 ATS 配置。
  • 这意味着之前使用主键所做的设置,对于这个域名来说,已经无效。

例如,及时之前设置NSAllowsArbitraryLoadsInMedia为 YES,然而NSExceptionDomain所代表的域名依然不能访问不安全的媒体内容。

基于这样的设定,可以针对域名进行 ATS 配置,增加或减少安全措施。例如:

  • 将NSExceptionAllowsInsecureHTTPLoads设置为 YES,就 ;这样做会引发 App Store 的审查,详情见App Store Review for ATS。
  • 通过配置NSExceptionRequiresForwardSecrecy为 NO,取消正向保密。
  • 通过配置NSExceptionMinimumTLSVersion,更改 TLS 最低版本。

NSExceptionDomains字典构成:

  • <域名字符串>
    代表想要配置的特定域名。可以添加多个域名(即添加多个这样的键),为它们统一配置 ATS 策略。这个键对应一个字典,包含以下子键:

    • NSIncludesSubdomains
      * 设置为 YES,当前域名的 ATS 策略适用于其所有子域名。默认为 NO
    • NSExceptionAllowsInsecureHTTPLoads
      * 设置为 YES,可以同时通过 HTTP 和 HTTPS 访问当前域名。默认为 NO。
      注意,配置这个键值,将引发 App Store 的审查,开发者必须说明原因。
    • NSExceptionMinimumTLSVersion
      * 指定 TLS 的最低版本,因此可以使用版本较低,有安全漏洞的 TLS 协议。
      注意,配置这个键值,将引发 App Store 的审查,开发者必须说明原因。
    • NSExceptionRequiresForwardSecrecy
      * 设置为 NO,允许针对当前域名使用不支持正向保密的 TLS 加密算法。默认为 YES
    • NSRequiresCertificateTransparency
      * 设置为 YES,将验证域名服务器证书的Certificate Transparency时间戳 。默认为 NO

Requirements for Connecting Using ATS / 使用 ATS 的前提条件

在 ATS 完全开启的情况下,系统要求 app 的 HTTPS 连接必须满足以下要求:

X.509 数字证书必须满足下列标准中的一项:

  • 由操作系统内嵌的根证书颁发机构签发
    • 由通过操作系统管理员或用户主动安装的根证书颁发机构签发
      • TLS 版本必须为1.2,任何不使用或使用较低版本 TLS / SSL 的连接,都将失败。
  • 连接必须使用 AES-128 或 AES-256 对称加密算法。 TLS 算法套装必须以 ECDSA 密钥交换的形式支持正向保密,加密算法必须为下面之一:
    • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
    • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
    • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
    • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
    • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
    • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
    • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
  • 服务端的叶证书签名密钥必须为下面之一:
    • 至少2048位的 RSA 密钥
    • 至少256位的 ECC 密钥
    • 此外,服务器证书的哈希算法必须为 SHA-2,其摘要长度至少位256位(即 SHA-256 及以上)。
      上面的标准,未来可能会发生变化。但不会影响到 app 二进制包的兼容性。

App Store Review for ATS / App Store 对于 ATS 相关项的审核

某些对 ATS 的配置会引发 App Store 的审核,开发者必须说明原因。这些键有:

  • NSAllowsArbitraryLoads
  • NSExceptionAllowsInsecureHTTPLoads
  • NSExceptionMinimumTLSVersion

以下是一些原因说明例子,供参考:

  • 必须连接由其他机构控制的服务器,其还不支持安全连接。
  • 必须支持那些还未升级至可使用安全连接,不得不通过公共域名访问网络的设备。
  • 必须通过 web 展示来源不一的各种网络内容,但又不能完全使用NSAllowsArbitraryLoadsInWebContent所管理的类。

向 App Store 提交审核时,开发者应主动提供足够的信息,以便解释 app 无法使用安全连接的原因。

四. 实现支持安全ATS策略

<font color=red size=5>ATS相关设置对iOS8及以下系统无效</font>

需要解决的问题(iOS 9、iOS10及以上)

1、app内服务器网络请求访问支持https(即一般的请求)

2、webview内支持任意http访问

3、第三方sdk接入与支持http访问

主要是围绕info.pilst配置文件作相关的安全ATS策略

Info.plist文件是向操作系统描述应用程序的XML属性列表,是iPhone应用程序文件夹包含所有重要的Info.plist文件

你可能注意到一些关键字像是使用了一些其他关键字中的词但是在前面加上了”ThirdParty”字样,在功能上,这些关键字与不含有”ThirdParty”的关键字有同样的效果。而且实际运行中所调用的代码将会完全忽略是否使用”ThirdParty”关键字。

简单粗暴的方案:

--------------------------------------------

NSExceptionDomains 的设置方法如下, 比如我们要将 weibo.com 这个域名排除在 ATS 验证之外,就可以这样:

key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>weibo.com</key>
        <dict>
            <key> NSIncludesSubdomains </key>
            <true/>
            <key> NSExceptionRequiresForwardSecrecy </key>
            <false/>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

注意:每个需添加的域都需要设置此三个属性。如果请求的网络图片是HTTP,也是需要设置的图片的域。

注意⚠️这个方案风险较大,有可能被拒绝。“需要访问的域名是第三方服务器,他们没有进行 HTTPS 对应”会是审核时的一个可选理由,但是这应该只需要针对特定域名,而非全面开放。如果访问的是自己的服务器的话,可能这个理由会无法通过。

------------------------------------------------

实现方案

1、app内服务器网络请求访问支持https

解决方案:

搭建https服务器

搭建https服务器需要ssl证书

  1. ssl自制证书:称自签名ssl证书,容易被假冒伪造,浏览器不信任。(审核不通过)
  2. 免费CA证书:部分CA机构提供免费的SSL证书,如wosign,starts等(App Store pass掉不通过)
  3. 付费CA证书:多指企业级及以上的数字证书。

HTTPS服务器满足ATS默认的条件,而且SSL证书是通过权威的CA机构认证过的,那么我们在使用Xcode开发的时候,对网络的适配什么都不用做,我们也能正常与服务器通信。但是,当我们对安全性有更高的要求时或者我们自建证书时,我们需要本地导入证书来进行验证。

使用AFNetworking来支持https

AFNetworking是iOS/OSX开发最流行的第三方开源库之一,现在iOS oc 代码90%以上都是用这个框架网络请求。AFNetworking已经将上面的逻辑代码封装好,甚至更完善,在AFSecurityPolicy文件中,有兴趣可以阅读这个模块的代码;以下就是在AFNetworking 2.6.0以前版本和3.0.0版本基于支持https的验证方式

步骤:

  1. 新建一个manager
  2. 在mainBundle中寻找我们刚才拖进项目中的https.cer, 并且将相关的数据读取出来
  3. 新建一个AFSecurityPolicy,并进行相应的配置
  4. 将这个AFSecurityPolicy 实例赋值给manager

代码实现:

NSURL * url = [NSURL URLWithString:@"https://www.google.com"];
AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url];
dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue");
requestOperationManager.completionQueue = requestQueue;

AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];

//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;
//validatesCertificateChain 是否验证整个证书链,默认为YES
//设置为YES,会将服务器返回的Trust Object上的证书链与本地导入的证书进行对比,这就意味着,假如你的证书链是这样的:
//GeoTrust Global CA 
//    Google Internet Authority G2
//        *.google.com
//那么,除了导入*.google.com之外,还需要导入证书链上所有的CA证书(GeoTrust Global CA, Google Internet Authority G2);
//如是自建证书的时候,可以设置为YES,增强安全性;假如是信任的CA所签发的证书,则建议关闭该验证,因为整个证书链一一比对是完全没有必要(请查看源代码);
securityPolicy.validatesCertificateChain = NO;

requestOperationManager.securityPolicy = securityPolicy;

另afnetworking 3.0.0以上版本用的是AFHTTPSessionManager

 AFHTTPSessionManager * manager = [AFHTTPSessionManager manager];
    NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"];
    NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
    NSLog(@"%@", cerData);
    manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSArray alloc] initWithObjects:cerData, nil]];
    manager.securityPolicy.allowInvalidCertificates = YES;
    [manager.securityPolicy setValidatesDomainName:NO];
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
    manager.responseSerializer = [AFJSONResponseSerializer serializer];
    NSDictionary * parameter = @{@"username":self.username, @"password":self.password};
    [manager POST:@"https://192.168.1.4:9777" parameters:parameter success:^(NSURLSessionDataTask * task, id responseObject) {
        NSLog(@"success %@", responseObject);
        }
        failure:^(NSURLSessionDataTask * task, NSError * error) {
            NSLog(@"failure %@", error);
        }]
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key> //设置为 YES,解除整个app的ATS限制;但是通过NSExceptionDomains进行的配置依然有效
        <false/>
        <key>NSAllowsArbitraryLoadsInMedia</key> //设置为 YES,解除通过AVFoundation框架访问媒体内容时的 ATS 限制
        <true/>
        <key>NSAllowsArbitraryLoadsInWebContent</key> //设置为 YES,解除通过webview发出的网络请求的ATS限制
        <true/>
        <key>NSAllowsLocalNetworking</key> //设置为 YES,使得app可以载入任意本地资源,但不影响app的总体 ATS 策略
        <true/>

2、webview内支持任意http访问

对于网页浏览和视频播放的行为,iOS 10 中新加入了 NSAllowsArbitraryLoadsInWebContent 键。通过将它设置为 YES,可以让 app 中的 WKWebView 和使用 AVFoundation 播放的在线视频不受 ATS 的限制。这也应该是绝大多数使用了相关特性的 app 的选择。但是坏消息是这个键在 iOS 9 中并不会起作用。

如果app只支持 iOS 10,并且有用户可以自由输入网址进行浏览的功能,或者是在线视频音频播放功能的话,简单地加入 NSAllowsArbitraryLoadsInWebContent,并且将组件换成 WKWebKit 或者 AVFoundation 就可以了。如果你还需要支持 iOS 9,并且需要访问网页和视频的话,可能只能去开启 NSAllowsArbitraryLoads 然后提交时进行说明,并且看 Apple 审核员决定让不让通过了。

另外,当 NSAllowsArbitraryLoads 和 NSAllowsArbitraryLoadsInWebContent 同时存在时,根据系统不同,表现的行为也会不一样。简单说,iOS 9 只看 NSAllowsArbitraryLoads,而 iOS 10 会先看 NSAllowsArbitraryLoadsInWebContent。在 iOS 10 中,要是 NSAllowsArbitraryLoadsInWebContent 存在的话,就忽略掉 NSAllowsArbitraryLoads,如果它不存在,则遵循 NSAllowsArbitraryLoads 的设定

UIWebView 在 NSAllowsArbitraryLoadsInWebContent 为 YES 时访问 HTTP,无效。WKWebView 在 NSAllowsArbitraryLoadsInWebContent 为 YES 时在iOS 10 中访问 HTTP,有效,iOS 9无效。如果用WkWebView替换UIWebView,iOS 7 将无法使用WkWebView,可做适配加载,没有特殊的什么需求的话,尽早将 UIWebView 全部换为 WkWebView 会比较好。所以为了能让WebView在所有版本都能访问非https内容,只能把NSAllowsArbitraryLoads设置为YES。

解决方案一:

开启 NSAllowsArbitraryLoads 为 YES,然后提交时进行说明

解决方案二:

设置 NSExceptionDomains 属性来访问指定域名,然后提交时进行说明

3、第三方sdk接入与支持http访问

但是按照国内的现状,关闭这个限制也许是更实际的做法。至于原因就太多了,第三方SDK(几乎都是访问http),合作伙伴接入(不能要求它们一定要支持https)

第三方sdk,同样需要遵守ATS规则,即第三方sdk也有被ATS过滤的风险,微信,qq,分享,登陆功能都能正常,微博登陆不能正常通过。另在网上找到了一些可能存在有问题的sdk,目前已知的有:

  • 友盟 (已经有最新的v1.4.0版本sdk,支持https,待验证)
  • 百度地图

解决方案一:

更新最新sdk,接入并测试

解决方案二:

可以设置 NSExceptionDomains属性来将需要排除强制验证的域名写进来:

五. 总结

开启 NSAllowsArbitraryLoads 为 YES

对第三方访问的服务器设置NSExceptionDomains方式添加白名单

提交审核说明:

  • 必须连接由其他机构控制的服务器,其还不支持安全连接。
  • 必须通过 web 展示来源不一的各种网络内容,但又不能完全使用NSAllowsArbitraryLoadsInWebContent所管理的类。

 

文/SN_Simon(简书作者)
原文链接:http://www.jianshu.com/p/36ddc5b009a7
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

iOS工程师面试必会基础知识36题

这一篇是iOS基础知识,转自网友的的面试总结,希望对大家有帮助。

1、必须熟悉的关键字

@interface声明类,@implementation实现类。@protocol声明协议,@optional与@protocol配合使用,说明协议中的某个或者某几个方法可以不实现。@required与@protocol配合使用,说明协议中的某个方法或者某几个方法必须实现。@end与@interface ,@implementation,@protocol配合使用,代表声明或者实现结束。

2、id

id是指向Objective-C类对象的指针,它可以声明为任何类对象的指针,当在Objective-C中使用id时,编译器会假定你知道,id指向哪个类的对象。与void*是不同的是,void*编译器不知道也不假定指向任何类型的指针。

3、nil

定义为一个常量,如果一个指针的值为nil,代表这个指针没有指向任何对象。

4、self

在Objective-C中,关键字self与c++中this是同一概念,就是类对象自身的地址,通过self可以调用自己的实例变量和方法。

5、Super

当子类需要调用父类的方法时,会用到Super关键字. Super指向的是父类的指针,子类重写父类的方法时,调用父类的方法是一个比较好的习 惯。因为 当我们不知道父类在该方法中实现的功能时,如果不调用父类的方法,有可能我们重写的方法会失去该功能,这是我们不愿意看到的情况。

6、NSNull

NSNull是没有的意思,如果一个字典的值为NSNull,那说明与该值对应的Key是没有值的,例如Key为address,说明与address对应的是值是没有。

7、类方法 +

如果想声明属于类而不属于类对象的方法,用+。+用来修饰类的方法,使用+修饰的类方法,是整个类的方法,不属于哪一个类对象,这与C++中的static在类中使用的概念一样。

8、%@

在NSLog中,使用%@表示要调用对象的description方法。

9、类

是一种结构,它表示对象的类型,就像int与 char 一样,也可以声明类的变量(对像)。

10、实例化

为类的对象分配内存和初始化,达到可以使用该 类对象的目的。

11、对象(实例)

类的实例化后的产物。

12、消息

在Object-C中,类的对象执行的操作,是通过给该类或者该类对象发送消息实现,如:[object func];就是给object对象发送 func消息,类似C++中的方法调用。给object对象发送func消息后,object对象查询所属类的func方法执行。

13、方法调度

当向一个对象发送消息时(调用方法),这个方法是怎么被调用的呢?这就依赖于方法调度程序,方法调度程序查找的方法如下:

在本类的方法中,找被调用的方法,如果找到了,就调用,如果找不到被沿着继承路径去查找,从哪个类找到,就调用哪个类的方法,如果到最根上的类还是没有找到,那编译就会出错。

14、继承与复合

在Objective-C中支持继承,但只是支持单一继承(有且只有一个父类),如果想使用多继承的特性,可以使用分类和协议技术。继承是is-a,复合是has-a。复合是通过包含指向对象的指针实现的。

15、装箱与拆箱

由于NSArray,NSDirectory等类不能直接存储基本数据类型,所以要想在NSArray\NSDirectory中使用基本数据类型,就得使用装箱与拆箱。

在Objective-C中,可以使用NSNumber和NSValue来实现对数据类型的包装,NSNumber可以实现对基本数据类型的包装,NSValue可以实现对任意类型数据的包装。

将基本类型封装成对象叫装箱,从封装的对象中提取基本类型叫拆箱(取消装箱),其它语言如Java原生支持装箱与拆箱,Ojbective-C不支持自动装箱与拆箱,如果需要得需要自己来实现装箱与拆箱。(NSKeyedArchiver、NSKeyedUnarchiver)。

16、存取方法

在使用类对象的实例变量(成员数据)时,不要直接使用对象中的实例,要使用存以方法来获取或者修改实例,既setter和getter,在 Cocoa中, 存取方法有命名习惯,我们得符合这种习惯,以便于与其它团队成员合作。setter方法是修改或者设置实例值,命名习惯为set+实例名,例有一个类有 path实例变量,那setter命名为setPath,getter命名为Path,为什么不是getPath,因为get在Cocoa中有特殊的含 义,这个含义就是带有get的方法就意味着这个方法通过形参指针(传入函数的参数指针)来返回值。我们要遵守这个命名习惯或者说规则。

在Objective-C 2.0中加入了@property和@synthesize来代替setter和getter,这两个关键字为编译器指令。还有点表达式,存取类成员的值时,可以使用点表达式。

Object.attribute,当点表达式在=号左边时,调用的是setter方法,在=号右边时,调用的是getter方法。

17、@property 语法为:@property (参数) 类型 变量名.

在这里主要说明一下参数.

参数分为三种:

第一种:读写属性包括(readonly/readwrite/)

第二种:setter属性(assign,copy,retain),assign是简单的赋值,copy是释放旧成员变量,并新分配内存地址给成 员 变量,将传入参数内容复制一份,给成员变量。retain是将传入 参数引用计数加1,然后将原有的成员变量释放,在将成员变量指向该传入参数。

第三种:与多线程有关(atomic,nonatomic).当使用多线程时,使用atomic,在不使用多线程时使用nonatomic

18、对象创建与初始化

在Objective-C中创建对象有两种方法,一种是[类 new];另一种是[[类 alloc] init],这两种方法是等价的,但按惯例来讲,使用[[类 alloc] init];

alloc操作是为对象分配内存空间,并将对象的数据成员都初始,int 为0,BOOL 为NO, float 为0.0等。

初始化,默认的初始化函数为init,init返回值为id,为什么回返回id呢,因为要实现链式表达式,在Objective-C中叫嵌套调用。

为什么要嵌套调用?因为初始化方法init返回值可能与alloc返回的对象不是同一个?为什么会发生这种情况?基于类簇的初始化,因为init可以接受参数,在init内部有可能根据不同的参数来返回不同种类型的对象,所以最会发生上面说的情况。

在初始化时,建议使用if (self = [super init])

19、便利初始化(下面解释太绕, 不用看了)

当一个类需要根据不同的情况来初始化数据成员时,就需要便利初始化函数,与init初始化不同的是,便利初始化函数有参数,参数个数可以有1到N个,N是类数据成员个数。

指定初始化函数:什么是指定初始化函数?在类中,某个初始化函数会被指定为指定的初始化函数,确定指定初始化函数的规则是初始化函数中,参数最多的为指定初始化函数,

其它未被指定为指定初始化函数的初始化函数要调用指定初始化函数来实现。对于该类的子类也是一样,只要重写或者直接使用父类的指定初始化函数。

20、自动释放池(深入了解看:《Objective-C高级编程》第一章)

内存管理是软件代码中的重中之重,内存管理的好坏,直接影响着软件的稳定性。在Cocoa中,有自动释放池,这类似于C++中的智能指针。

NSObject有一个方法是autorelease,当一个对象调用这个方法时,就会将这个对象放入到自动释放池中。

drain,该方法是清空自动释放池,不是销毁它。drain方法只适用于Mac OS X 10.4以上的版本,在我们写的代码中要使用release,release适用于所有版本。

自动释放池是以栈的方式实现,当创建一个自动释放池A时,A被压入栈顶,这时将接入autorelease消息的对象放入A自动释放池,这时创建一 个新的 B自动释放池,B被压入栈顶,创建完成后删除B,这个接收autorelease消息的对象依然存在,因为A自动释放池依然存在。

21、类别

什么是类别?类别是一种为现有类添加新方法的方式。

为什么使用类别或者说使用类别的目的是什么?有以下三点:

第一,可以将类的实现分散到多个不同的文件或多个不同的框架中。

如果一个类需要实现很多个方法,我们可以将方法分类,把分好的类形成类别,可以有效的管理和驾驭代码。

第二,创建对私有方法的前向引用。

第三,向对象添加非正式协议。

22、委托(下面的解释不好,可跳过。以后找机会专门总结一下这个知识点。我的软肋之一。)

委托的意思就是你自己想做某事,你自己不做,你委托给别人做。

在Ojbective-C中,实现委托是通过类别(或非正式协议)或者协议来实现。

举个例子:Apple要生产iPhone,Apple自己不生产(种种原因,其中之一就是在中国生产成本低,他们赚的银子多),Apple委托富士 康来生 产,本来富士康原来不生产iPhone,现在要生产了,所以他得自己加一个生产iPhone的生产线(类别,增加生产iPhone方法),这就是通过类别 来实现委托。

23、非正式协议(同上)

创建一个NSObject的类别, 称为创建一个非正式协议。为什么叫非正式协议呢?

也就是说可以实现,也可以不实现被委托的任务。

24、选择器

选择器就是一个方法的名称。选择器是在Objective-C运行时使用的编码方式,以实现快速查找。可以使用@selector预编译指令,获取 选择器 @selector(方法名)。NSObject提供了一个方法respondsToSelector:的方法,来访问对象是否有该方法(响应该消息)。

25、正式协议

与非正式协议比较而言,在Ojbective-C中,正式协议规定的所有方法必须实现。在Ojbective-C2.0中,Apple又增加了两个关键字,协议中的方法也可以不完全实现,是哪个关键字见关键字部份的@optional,@required。

26、什么是框架

框架是一种聚集在一个单元的部件集合,包含头文件,库,图像,声音文件等。苹果公司将cocoa,Carbon,QuickTime和OpenGL 等技术 作为框架集提供。cocoa的组成部分有Foundation和Application Kit框架。还有一个支持框架的套件,包含 Core Animation和Core Image,这为Cocoa增添了多种精彩的功能。

每个框架都是一个重要的技术集合,通常包含数十个甚至上百个头文件。每个框架都有一个主头文件,它包含了所有框架的各个头文件。通过使用#import导入主头文件,可以使用所有框架的特性。

27、OOP中得一些术语

类:类是一种结构,它表示对象的类型。对象引用类来获取和本身有关的各种信息,特别是运行什么代码来处理每种操作。

对象:对象是一种结构,它包含值和指向其类的隐藏指针。

实例:实例是“对象”的另一种称呼。

消息:消息是对象可以执行的操作,用于通知对象去做什么。

方法:方法是为响应消息而运行的代码。根据对象的类,消息可以调用不同的方法。

方法调度程序:是objective-c使用的一种机制,用于推测执行什么方法以响应某个特定的消息。

接口:接口是对象的类应该提供的特性的描述。接口不提供实现细节。

实现:实现是使接口正常工作的代码。

28、键/值编码 KVC

是一种间接改变对象状态的方式,其实现方法是使用字符串描述要更改的对象状态部分。

1)valueForKey与setValue:forKey:

这两种方法的工作方式相同。他们首先查找名称的setter(getter)方法,如果不存在setter(getter)方法,他们将在类中查找名为名称或_名称的实例变量。然后给它赋值(取值)。无需通过对象指针直接访问实例变量。

2)路径

键路径的深度是任意的,具体取决于对象图。

键路径不仅能引用对象值,还可以引用一些运算符来进行一些运算,例如获取一组值的平均值或返回这组值中得最小值和最大值。

例如:NSNumber *count;

count = [garage valueForKeyPath:@”cars.@count”];

NSLog(@”We have %@ cars”, count);

我们将路径“cars.@count”拆开,cars用于获取cars属性,它是来自garage的NSArray类型的值。接下来的部分是@count ,其中@符号意味着后面将进行一些运算。

和 cars@sun.mileage

最大值 cars@min.mileage

最小值 cars@max.mileage

3)整体操作

KVC非常棒的一点是,如果向NSArray请求一个键值,它实际上会查询数组中得每个对象来查找这个键值,然后将查询结果打包到另一个数组中并返回给你。这种方法也适用于通过键路径访问的对象内部的数组。

4)批处理

KVC包含两个调用,可以使用他们对对象进行批量更改。第一个调用是dictionaryWith-ValuesForKeys:。它接受一个字符串数组。该调用获取一些键,对每个键使用valueForKey:,然后为键字符串和刚才获取的值构建一个字典。

29、Object-C有多继承吗?没有的话用什么代替?cocoa 中所有的类都是NSObject 的子类

多继承在这里是用protocol 委托代理 来实现的,你不用去考虑繁琐的多继承 ,虚基类的概念.多态特性 在 obj-c 中通过委托来实现.

30、#import和#include的区别,@class代表什么?

@class一般用于头文件中需要声明该类的某个实例变量的时候用到,在m文件中还是需要使用#import

而#import比起#include的好处就是不会引起重复包含。

31、线程和进程的区别?

进程和线程都是由操作系统下的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一 个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程 序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

32、堆和栈的区别?

管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

申请大小:

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因 此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出

分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的.

33、为什么很多内置的类,如TableViewController的delegate的属性是assign不是retain?

避免循环引用。

34、tableView的重用机制?

查看UITableView头文件,会找到NSMutableArray* visiableCells,和 NSMutableDictionery* reusableTableCells两个结构。visiableCells内保存当前显示的 cells,reusableTableCells保存可重用的cells。

TableView显示之初,reusableTableCells为空,那么 tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都 是通过 [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] 来创建,而且cellForRowAtIndexPath只是调用最大显示cell数的次数。

比如:有100条数据,iPhone一屏最多显示10个cell。程序最开始显示TableView的情况是:

1.[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] 创建10次cell,并给cell指定同样的重用标识(当然,可以为不同显示类型的cell指定不同的标识)。并且10个cell全部都加入到 visiableCells数组,reusableTableCells为空。

2.向下拖动tableView,当cell1完全移出屏幕,并且cell11(它也是alloc出来的,原因同上)完全显示出来的时候。 cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。

3.接着向下拖动tableView,因为reusableTableCells中已经有值,所以,当需要显示新的 cell,cellForRowAtIndexPath再次被调用的时 候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。 cell1加入到visiableCells,cell1移出reusableTableCells;cell2移出 visiableCells,cell2加入到reusableTableCells。之后再需要显示的Cell就可以正常重用了。

35、怎么理解MVC,在Cocoa中MVC是怎么实现的?

Model: 代表你的应用程序是什么(不是怎么展现)。

Controller: 控制你的Model怎么展现给用户(UI逻辑)。

View: Controller的奴隶。

1 Model,Controller,View相互通讯的规则:

Controller可以直接和Model通信。

Controller也可以直接和View通信。

Model和View永远不能直接通信。

iOS中View和Controller的通信是透明和固定的,主要通过outlet和action实现。

View使用Delegate接口和Controller同步信息。

View不直接和数据通信,使用dataSource接口从Controller处获取数据。

View的delegate和dataSource一般就是Controller。

Controller负责为View翻译和格式化Model的数据。

Model使用Notification & KVO的方式分发数据更新信息,Controller可以有选择的监听自己感兴趣的信息。

View也可以监听广播信息,但一般不是Model发出的信息。

一个完整的App就是很多MVC的集合。

36、delegate和notification区别,分别在什么情况下使用?

Delegate:

消息的发送者(sender)告知接收者(receiver)某个事件将要发生,delegate同意,然后发送者响应事件,delegate机制使得接收者可以改变发送者的行为。通常发送者和接收者的关系是直接的一对多的关系。

Notification:

消息的发送者告知接收者事件已经发生或者将要发送,仅此而已,接收者并不能反过来影响发送者的行为。通常发送者和接收者的关系是间接的多对多关系。

1. 效率肯定是delegate比nsnotification高。

2. delegate方法比notification更加直接,最典型的特征是,delegate方法往往需要关注返回值,也就是delegate方法 的结果。比如-windowShouldClose:,需要关心返回的是yes还是no。所以delegate方法往往包含should这个很传神的词。 也就是好比你做我的delegate,我会问你我想关闭窗口你愿意吗?你需要给我一个答案,我根据你的答案来决定如何做下一步。相反 的,notification最大的特色就是不关心接受者的态度,我只管把通告放出来,你接受不接受就是你的事情,同时我也不关心结果。所以 notification往往用did这个词汇,比如NSWindowDidResizeNotification,那么nswindow对象放出这个 notification后就什么都不管了也不会等待接受者的反应。

1)两个模块之间联系不是很紧密,就用notification传值,例如多线程之间传值用notificaiton。

2)delegate只是一种较为简单的回调,且主要用在一个模块中,例如底层功能完成了,需要把一些值传到上层去,就事先把上层的函数通过 delegate传到底层,然后在底层call这个delegate,它们都在一个模块中,完成一个功能,例如 说 NavgationController 从 B 界面到A 点返回按钮 (调用popViewController方法) 可以用delegate 比较好。