iOS WKWebView适配实战篇

一、Cookie适配

1.现状

WKWebView适配中最麻烦的就是cookie同步问题

WKWebView采用了独立存储控件,因此和以往的UIWebView并不互通

虽然iOS11以后,iOS开放了WKHTTPCookieStore让开发者去同步,但是还是需要考虑低版本的 同步问题,本章节从各个角度切入考虑cookie同步问题

2.同步cookie(NSHTTPCookieStorage->WKHTTPCookieStore)

iOS11+

可以直接使用WKHTTPCookieStore遍历方式设值,可以在创建wkwebview时候就同步也可以是请求时候

// iOS11同步 HTTPCookieStorag到WKHTTPCookieStore
WKHTTPCookieStore *cookieStore = self.wkWebView.configuration.websiteDataStore.httpCookieStore;

- (void)syncCookiesToWKCookieStore:(WKHTTPCookieStore *)cookieStore API_AVAILABLE(ios(11.0)){
  NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
  if (cookies.count == 0) return;
  for (NSHTTPCookie *cookie in cookies) {
    [cookieStore setCookie:cookie completionHandler:^{
      if ([cookies.lastObject isEqual:cookie]) {
        [self wkwebviewSetCookieSuccess];
      }
    }];
  }
}

同步cookie可以在初始化wkwebview的时候,也可以在请求的时候。初始化时候同步可以确保发起html页面请求的时候带上cookie

例如:请求在线页面时候要通过cookie来认证身份,如果不是初始化时同步,可能请求页面时就是401了

iOS11-

通过前端执行js注入cookie,在请求时候执行

//wkwebview执行JS
- (void)injectCookiesLT11 {
  WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:[self cookieString] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
  [self.wkWebView.configuration.userContentController addUserScript:cookieScript];
}
//遍历NSHTTPCookieStorage,拼装JS并执行
- (NSString *)cookieString {
  NSMutableString *script = [NSMutableString string];
  [script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];
  for (NSHTTPCookie *cookie in NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies) {
    // Skip cookies that will break our script
    if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
      continue;
    }
    [script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };\n", cookie.name, [self formatCookie:cookie]];
  }
  return script;
}
//Format cookie的js方法
- (NSString *)formatCookie:(NSHTTPCookie *)cookie {
  NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
            cookie.name,
            cookie.value,
            cookie.domain,
            cookie.path ?: @"/"];
  if (cookie.secure) {
    string = [string stringByAppendingString:@";secure=true"];
  }
  return string;
}

但是上面方法执行js,也无法保证第一个页面请求带有cookie

所以请求时候创建request需要设置cookie,并且loadRequest

-(void)injectRequestCookieLT11:(NSMutableURLRequest*)mutableRequest {
  // iOS11以下,手动同步所有cookie
  NSArray *cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies;
  NSMutableArray *mutableCookies = @[].mutableCopy;
  for (NSHTTPCookie *cookie in cookies) {
    [mutableCookies addObject:cookie];
  }
  // Cookies数组转换为requestHeaderFields
  NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:(NSArray *)mutableCookies];
  // 设置请求头
  mutableRequest.allHTTPHeaderFields = requestHeaderFields;
}

3.反向同步cookie(WKHTTPCookieStore->NSHTTPCookieStorage)

wkwebview产生的cookie也可能在某些场景需要同步给NSHTTPCookieStorage

iOS11+可以直接用WKHTTPCookieStore去同步,

iOS11-可以采用js端获取,触发bridge同步给NSHTTPCookieStorage

但是js同步方式无法同步httpOnly,所以真的遇到了,还是要结合服务器等方式去做这个同步。

二、JS和Native通信

1.Native调用JS

将代码准备完毕后调用API即可,回调函数可以接收js执行结果或者错误信息,So Easy。

[self.wkWebView evaluateJavaScript:jsCode completionHandler:^(id object, NSError *error){}];

2.注入JS

其实就是提前注入一些JS方法,可以提供给JS端调用。

比如有的框架会将bridge直接通过这种方式注入到WK的执行环境中,而不是从前端引入JS,这种好处就是假设前端的JS是在线加载,JS服务器挂了或者网络问题,这样前端页面就失去了Naitve的Bridge通信能力了。

-(instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;

//WKUserScriptInjectionTime说明
typedef NS_ENUM(NSInteger, WKUserScriptInjectionTime) {
  WKUserScriptInjectionTimeAtDocumentStart, /**文档开始时候就注入**/
  WKUserScriptInjectionTimeAtDocumentEnd /**文档加载完成时注入**/
} API_AVAILABLE(macos(10.10), ios(8.0));

3.JS调用Native

3-1.准备代理类

代理类要实现WKScriptMessageHandler

@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>
 @property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
 - (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
@end

WKScriptMessageHandler就一个方法

@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
  self = [super init];
  if (self) {
    _scriptDelegate = scriptDelegate;
  }
  return self;
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
  [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}

3-2.设置代理类

合适时机(一般初始化)设置代理类,并且指定name

NSString* MessageHandlerName = @"bridge";
[config.userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:MessageHandlerName];

3-3.bridge的使用(JS端)

执行完上面语句后就会在JS端注入了一个对象"window.webkit.messageHandlers.bridge"

//JS端发送消息,参数最好选用String,比较通用
window.webkit.messageHandlers.bridge.postMessage("type");

3-4.Native端消息的接收

然后native端可以通过WKScriptMessage的body属性中获得传入的值

- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
  if ([message.name isEqualToString:HistoryBridageName]) {

  } else if ([message.name isEqualToString:MessageHandlerName]) {
    [self jsToNativeImpl:message.body];
  }
}

3-5.思考题

这里我们为什么要使用WeakScriptMessageDelegate,并且再设置个delegate指向self(controller),为什么不直接指向?

提示:可以参考NSTimer的循环引用问题

3-6.完整的示例

-(void)_defaultConfig{
  WKWebViewConfiguration* config = [WKWebViewConfiguration new];
  …… ……
  …… ……
  WKUserContentController* userController = [[WKUserContentController alloc] init];
  config.userContentController = userController;
  [self injectHistoryBridge:config];
  …… ……
  …… ……
}

-(void)injectHistoryBridge:(WKWebViewConfiguration*)config{
  [config.userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:HistoryBridageName];
  NSString *_jsSource = [NSString stringWithFormat:
              @"(function(history) {\n"
              " function notify(type) {\n"
              "  setTimeout(function() {\n"
              "   window.webkit.messageHandlers.%@.postMessage(type)\n"
              "  }, 0)\n"
              " }\n"
              " function shim(f) {\n"
              "  return function pushState() {\n"
              "   notify('other')\n"
              "   return f.apply(history, arguments)\n"
              "  }\n"
              " }\n"
              " history.pushState = shim(history.pushState)\n"
              " history.replaceState = shim(history.replaceState)\n"
              " window.addEventListener('popstate', function() {\n"
              "  notify('backforward')\n"
              " })\n"
              "})(window.history)\n", HistoryBridageName
              ];
  WKUserScript *script = [[WKUserScript alloc] initWithSource:_jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
  [config.userContentController addUserScript:script];
}

3-7.其它问题

在iOS8 beta5前,JS和Native这样通信设置是不行的,所以可以采用生命周期中做URL的拦截去解析数据来达到效果,这里不做赘述,可以自行参考网上类似UIWebview的桥接原理文章

三、实战技巧

1.UserAgent的设置

添加UA

实际过程中最好只是原有UA上做添加操作,全部替换可能导致服务器的拒绝(安全策略)

日志中红线部分是整个模拟器的UA,绿色部门是UA中的ApplicationName部分

iOS9上,WKWebview提供了API可以设置ua中的ApplicationName

config.applicationNameForUserAgent = [NSString stringWithFormat:@"%@ %@", config.applicationNameForUserAgent, @"arleneConfig"];

全部替换UA

iOS9以上直接可以指定wkwebview的customUserAgent,iOS9以下的话,设置NSUserDefaults

if (@available(iOS 9.0, *)) {
  self.wkWebView.customUserAgent = @"Hello My UserAgent";
}else{
  [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":@"Hello My UserAgent"}];
  [[NSUserDefaults standardUserDefaults] synchronize];
}

2.监听进度和页面的title变化

wkwebview可以监控页面加载进度,类似浏览器中打开页面中的进度条的显示

页面切换的时候也会自动更新页面中设置的title,可以在实际项目中动态切换容器的title,比如根据切换的title设置navigationItem.title

原理直接通过KVO方式监听值的变化,然后在回调中处理相关逻辑

//kvo 加载进度
[self.webView addObserver:self
       forKeyPath:@"estimatedProgress"
       options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
       context:nil];
//kvo title
[self.webView addObserver:self
       forKeyPath:@"title"
       options:NSKeyValueObservingOptionNew
       context:nil];

/** KVO 监听具体回调**/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
    ALLOGF(@"Progress--->%@",[NSNumber numberWithDouble:self.webView.estimatedProgress]);
  }else if([keyPath isEqualToString:@"title"]
       && object == self.webview){
    self.navigationItem.title = self.webView.title;
  }else{
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  }
}

/**销毁时候记得移除**/
[self.webView removeObserver:self
      forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
[self.webView removeObserver:self
           forKeyPath:NSStringFromSelector(@selector(title))];

3.Bridge通信实战

下面介绍自己实现的bridge通信框架,前端无需关心所在容器,框架层做适配。

import {WebBridge} from 'XXX'
/**
* 方法: WebBridge.call(taskName,options,callback)
* 参数说明:
*  taskName String task的名字,用于Native处理分发任务的标识
* options Object 传递的其它参数
* callback function 回调函数
*.     回调参数
*           json object native返回的内容
**/
WebBridge.call("Alert",{"content":"弹框内容","btn":"btn内容"},function(json){
   console.log("call back is here",JSON.stringify(json));
});

上面调用了Native的Alert控件,然后返回调用结果。

调用到的Native代码如下:

//AlertTask.m
#import "AlertTask.h"
#import <lib-base/ALBaseConstants.h>
@interface AlertTask (){}
@property (nonatomic,weak) ArleneWebViewController* mCtrl;
@end

@implementation AlertTask
-(instancetype)initWithContext:(ArleneWebViewController*)controller{
  self = [super init];
  self.mCtrl = controller;
  return self;
}
-(NSString*)taskName{
  return @"Alert";
}
-(void)doTask:(NSDictionary*)params{
  ALShowAlert(@"Title",@"message");//弹出Alert
  NSMutableDictionary* callback = [ArleneTaskUtils basicCallback:params];//获取callback
  [callback addEntriesFromDictionary:params];
  [self.mCtrl callJS:callback];//执行回调
}
@end

具体实现原理可以点击下方视频链接:

点击获取框架原理视频

到此这篇关于iOS WKWebView适配实战篇的文章就介绍到这了,更多相关iOS WKWebView适配 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2020-06-22

iOS中WKWebView仿微信加载进度条

本文实例为大家分享了WKWebView仿微信加载进度条的具体代码,供大家参考,具体内容如下 WKWebView添加了estimatedProgress属性(double类型),我们可以利用该属性来设置UIProgressView github代码仓库上存放的Demo 为页面添加UIProgressView属性 @property (nonatomic, strong) WKWebView *mywebView; @property (nonatomic, strong) UIProgressVi

微信小程序iOS下拉白屏晃动问题解决方案

这篇文章主要介绍了微信小程序iOS下拉白屏晃动问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 问题 感觉ios的小程序每个页面都可以下拉出现白屏 有时页面带有滑动的属性会跟着晃动,体验不是很好 解决办法: 先禁止页面下拉 <config> { navigationBarTitleText: "购物车", disableScroll:true } </config> 这样的话页面整个都拉不动了,下面溢

vue 项目 iOS WKWebView 加载

1.首先让前端的同事打一个包(index.html,static文件包含css.资源文件.js等)导入项目: :warning: 注意: 把index.html放入项目根目录下,command+n创建一个资源文件.bundle,资源文件里也的包含一份 index.html 下面开始代码: 懒加载WKWebView 引入#import <WebKit/WebKit.h> #import <WebKit/WKWebView.h> 继承 WKNavigationDelegate,WKUI

简单说说iOS之WKWebView的用法小结

WKWebView的优势 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷新频率(不卡) 1.Xcode新建My.html文件,自定义html内容,主要代码如下: (1)标签为UI样式(写了简单的JS代码,目的用于讲解交互) (2)onClick为JS事件,当JS想给OC传递参数时,采用如下代码:window.webkit.messageHandlers.<方法名>.post

iOs迁至WKWebView跨过的一些坑

前言 在iOS中有两种网页视图可以加载网页除了系统的那个控制器.一种是UIWebView,另一种是WKWebView,其实WKWebView就是想替代UIWebView的,因为我们都知道UIWebView非常占内存等一些问题,但是现在很多人还在使用UIWebView这是为啥呢?而且官方也宣布在iOS12中废弃了UIWebView让我们尽快使用WKWebView.其实也就是这些东西:**页面尺寸问题.JS交互.请求拦截.cookie带不上的问题.**所以有时想要迁移还得解决这些问题,所以还是很烦的

iOS使用WKWebView加载HTML5不显示屏幕宽度的问题解决

最近在项目中我们的商品详情页是一个后台返回的图片标签.需要我们自己去写一个HTML5标签进行整合,(相当于重新写了一个HTML页面) :ok_hand:那就没办法了,我就自己写一个标签咯,应该不难吧.嘻嘻嘻嘻~~~~~ dispatch_async(dispatch_get_main_queue(), ^{ if(self.detailModel.details){ //这里是自己写的简单的加载H5 NSString *header =@"<head><meta name=\v

iOS开发教程之WKWebView与JS的交互

前言 iOS8以后,Apple公司推出了WKWebView,对比之前的UIWebView不论是处理速度还是内存性能,都有了大幅度的提升! 那么下面我就分享一下WKWebView与JS的交互. 首先使用WKWebView.你需要导入WebKit #import 然后初始化一个WKWebView,设置代理,并且执行代理的方法.在网页加载成功的时候,我们会调用一些JS代码对网页进行设置. WKWebView的代理一共有三个:WKUIDelegate,WKNavigationDelegate,WKScr

iOS和JS交互教程之WKWebView-协议拦截详解

前言 由于Xcode8发布之后,编译器开始不支持iOS 7了,这样我们的app也改为最低支持iOS 8.0,既然需要与web交互,那自然也就选择使用了 iOS 8.0之后 才推出的新控件 WKWebView. 相比与 UIWebView, WKWebView 存在很多优势: 支持更多的HTML5的特性 高达60fps滚动刷新频率与内置手势 与Safari相容的JavaScript引擎 在性能.稳定性方面有很大提升占用内存更少 协议方法及功能都更细致 可获取加载进度等. 先解释下标题:"iOS与J

iOS WKWebview 白屏检测实现的示例

前言 自ios8推出wkwebview以来,极大改善了网页加载速度及内存泄漏问题,逐渐全面取代笨重的UIWebview.尽管高性能.高刷新的WKWebview在混合开发中大放异彩表现优异,但加载网页过程中出现异常白屏的现象却仍然屡见不鲜,且现有的api协议处理捕捉不到这种异常case,造成用户无用等待体验很差.     针对业务场景需求,实现加载白屏检测.考虑采用字节跳动团队提出的webview优化技术方案.在合适的加载时机对当前webview可视区域截图,并对此快照进行像素点遍历,如果非白屏颜

iOS中WKWebView白屏问题的分析与解决

前言 随着WKWebView的推出, 解决了很多UIWebView 的问题.比如加载速度慢,内存泄露等问题.WKWebView是在iOS 8 推出,前段时间正好把项目也适配到iOS 8 以上了,终于可以把项目中的UIWebView 替换成WKWebView. WKWebView的特点: 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷新频率(不卡) 但是发现在使用的时候还是有很

iOS中WKWebView的一些特殊使用总结

前言 现在大部分的app只支持iOS8以上的系统了,在接入H5时可以只管最新的WKWebView了. WKWebView的优势 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷新频率(不卡) 本文将给大家总结下iOS中WKWebView的一些特殊使用,下面话不多说了,来一起看看详细的介绍吧 WKWebView 加载本地网页的方式 1.直接加载字符串 - (void)loadHT

iOS中wkwebView内存泄漏与循环引用问题详解

前言 现在大多数网络也面加载都会用到wkwebview,之前在使用wkwebview的时候,网上很多的基础教程使用很多只是说了怎么添加Message Handler 但是并没有告诉到家有这个内存泄漏的风险,如果你只是也没内的数据调用你压根都不会发现这个问题.没存泄漏这个问题说大不大,说小不小,严重的话话直接到时app闪退,所以还是得重视起.好下面说一下怎么解决,话不多说了,来一起看看详细的介绍吧 解决方法 1,在做网页端js交互的时候 我们都会这样去添加js [self.customWebVie

IOS 中动画的暂停与继续播放的详解

IOS 中动画的暂停与继续播放的详解 在使用动画控制UI的时候,可能会碰到通过手势或其他方式要进行暂停正在进行中的动画,然后再继续.如手指按下时,暂停动画,手指离开时继续动画. 实现原理主要是通过UI的layer进行相关的控制. 暂停动画: - (void)pauselayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.s

iOS中关于信鸽推送的使用demo详解

最近在看推送方面的知识,用的是信鸽推送主要是因为后台用的是信鸽 推送用第三方推送,也就是在客户端建一个广播接收器,当服务器发送消息时发送到信鸽,信鸽再发送一次,广播接受器接受下: 我实现的功能比较简单,当app在运行状态时,会在主页展示一个弹窗展示推送的消息:如果app不在运行状态且service没被销毁就展示默认的通知 那么如何在主页展示弹窗:当广播接受器收到我要的消息时,用观察者模式,收到消息在发送个消息个主界面 官方的Demo连接:http://xg.qq.com/xg/help/ctr_

对python中的控制条件、循环和跳出详解

对python中的控制条件.循环和跳出详解 代码缩进(代码块): python用缩进表示代码块,没有其他语言的大括号 缩进是强制检查,整个代码缩进必须一致,否则无法运行 用2.4个空格或者tab缩进 ide自动保证缩进一致 If.elif和else的条件分支: if if...else if...elif..else 没有switch.case语法 空的列表.元祖.字符串.0都被评估为False None被评估为False 控制条件后面必须加":" a=100 if a > 80

Objective-C中block循环引用问题详解

目标:block执行过程中,self不会释放:执行完可以释放. 最初 block中直接使用self会强引用. self.myBlock = ^() { [self doSomething]; }; 或者使用了对象的属性 self.myBlock = ^() { NSString *str = _str; NSString *str2 = self.str; }; 在这样的情况下,self强引用block,block也持有该对象,导致循环引用. 要注意的是,只有在self强引用block的时候才会

一次 Java 内存泄漏的排查解决过程详解

由来 前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理.Bug 排查.运营 issue 处理的事.工作日还好,无论干什么都要上班的,若是轮到周末,那这一天算是毁了. 不知道是公司网络广了就这样还是网络运维组不给力,网络总有问题,不是这边交换机脱网了就是那边路由器坏了,还偶发地各种超时,而我们灵敏地服务探测服务总能准确地抓住偶现的小问题,给美好的工作加点料.好几次值班组的小伙伴们一起吐槽,商量着怎么避过服务保活机制,偷偷停了探测服务而不让人发现(虽然也并不敢). 前些天我就在周末

iOS中日志同步获取NSLog重定向以及其他详解

前言 对于那些做后端开发的工程师来说,看LOG解Bug应该是理所当然的事,但我接触到的移动应用开发的工程师里面,很多人并没有这个意识,查Bug时总是一遍一遍的试图重现,试图调试,特别是对一些不太容易重现的Bug经常焦头烂额. 我们在真机测试时经常会发现一个难题是无法查看真机的NSLog类型的实时日志,这时候需要RD复现问题来定位当时的日志,以方便查找问题.这个问题在测试中是非常常见的,也是功能测试会花费比较长时间的一个原因. 以下我们讨论下能即时查看日志的几种方案. NSLog输出到哪里? 在i

iOS中id类型的理解及底层原理详解

前言 id:是一种数据类型: id类型被定义为指向对象的指针,这可以从id的定义中看出.id在objc.h中的定义为: typedef struct objc_object { Class isa; } *id; id是一个一个比较灵活的对象指针,并且是一个指向任何一个继承了Object(或者NSObject)类的对象.而在cocoa的开发环境里,NSObject是所有类的根类.所以id可以指向任何一个cocoa的合法对象. Objective-C中的id这种数据类型存在的价值是什么? id是一

iOS中常见的视图和图片处理示例详解

前言 众所周知在开发中不可避免的会遇到一些图片和视图的处理,我这里总结的这些只是我遇到的一些,以供下次使用查看.下面话不多说了,来一起看看详细的介绍吧. 图片的旋转 是UIImage的扩展类,直接使用UIImage的对象调用即可 UIImage #import <QuartzCore/QuartzCore.h> #import <Accelerate/Accelerate.h> @implementation UIImage (ImageRotate) -(UIImage *)im

iOS中nil、Nil、NULL、NSNull详解

ObjC 里面的几个空值符号经常会差点把我搞死,这些基础的东西一点要弄清楚才行,以提高码农的基本素质. nil nil 是 ObjC 对象的字面空值,对应 id 类型的对象,或者使用 @interface 声明的 ObjC 对象. 例如: NSString *someString = nil; NSURL *someURL = nil; id someObject = nil; if (anotherObject == nil) // do something 定义: // objc.h #if