工具

iTunes

下载 iTunes 12.6.3 支持 iPhone 8,iPhone X和iOS 11,获取AppStore应用的IPA包。

强制删除其他版本的iTunes:

1
sudo rm -rf /Applications/iTunes.app

iOS-Images-Extractor

iOS-Images-Extractor

打开软件后,拖拽ipa、car文件、png、jpg或者整个文件夹到软件窗口,点击Start按钮即可开始提取图片资源文件。

图片默认输出到/User/用户名/Downloads/iOSImagesExtractor文件夹,可直接点击Output Dir按钮快速跳转到输出文件夹。

参考文章

解决 iTunes 12.7 无法下载 AppStore应用的IPA包问题

iOS中如何找到一款app的所有图片

在全屏模式下使用xcode模拟器

  • 开启模拟器Internal菜单
1
2
3
sudo mkdir /AppleInternal

OSX10.11的一个新特性 Rootless,也叫System Integrity Protection(SIP)和SELinux差不多,都是限制root用户的权限。其实可以在RecoveryMode关闭这个特性,这样就直接可以读写/usr/bin了,不过不建议。(关闭方法:开机的时候按住option出现选择磁盘的界面按command + R进入RecoveryMode,选择实用工具终端,输入csrutil disable回车搞定)
  • 设置模拟器允许全屏模式
1
Xcode9模拟器-->Internal-->Allow Fullscreen Mode
  • mac添加桌面(快捷键:四指向上调出)–>添加Xcode9 –>添加模拟器

xcode9

  • 参考链接

提升 iOS 開發效率! Xcode 9 內置模擬器的新功能與技巧

最近调试iOS移动端H5遇到两个问题,一个是访问本地站点文件,一个是使用Chrome调试。

http-server快速创建node.js 静态服务器

安装

全局安装http-server

1
npm install -g http-server

启动

进入自己的文件目录,运行

1
http-server -a 192.168.91.218 -p 8080

启动node.js静态服务器,监听8080端口,静态目录就是当前运行命令所在的目录。

Chrome DevTool调试iOS设备的webView

iOS WebKit Debug Proxy

启动proxy

终端执行

1
ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html

返回结果为

1
2
Listing devices on :9221
Connected :9222 to “lizhu”的 iPhone (f008f6196e655d8d30aa00ab943508a726f1f562)

在chrome中打开 localhost:9221 ,可以看到当前已连接的设备列表才算成功。

参考

使用Chrome DevTool调试iOS设备的webView

背景

在ios开发过程中,有时候会用到第三方的静态库(.a文件),然后导入后发现编译正常但运行时会出现selector not recognized的错误,从而导致app闪退。接着仔细阅读库文件的说明文档,你可能会在文档中发现诸如在Other Linker Flags中加入-ObjC或者-all_load这样的解决方法。

那么,Other Linker Flags到底是用来干什么的呢?还有-ObjC-all_load到底发挥了什么作用呢?

链接器

首先,要说明一下Other Linker Flags到底是用来干嘛的。说白了,就是ld命令除了默认参数外的其他参数。ld命令实现的是链接器的工作,详细说明可以在终端man ld查看。

如果有人不清楚链接器是什么东西的话,我可以作个简单的说明。

一个程序从简单易读的代码到可执行文件往往要经历以下步骤:

1
源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件

源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。

为什么会闪退

苹果官方Q&A上有这么一段话:

1
The "selector not recognized" runtime exception occurs due to an issue between the implementation of standard UNIX static libraries, the linker and the dynamic nature of Objective-C. Objective-C does not define linker symbols for each function (or method, in Objective-C) - instead, linker symbols are only generated for each class. If you extend a pre-existing class with categories, the linker does not know to associate the object code of the core class implementation and the category implementation. This prevents objects created in the resulting application from responding to a selector that is defined in the category.

翻译过来,大概意思就是Objective-C的链接器并不会为每个方法建立符号表,而是仅仅为类建立了符号表。这样的话,如果静态库中定义了已存在的一个类的分类,链接器就会以为这个类已经存在,不会把分类和核心类的代码合起来。这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。

解决方法

解决方法在背景那块我就提到了,就是在Other Linker Flags里加上所需的参数,用到的参数一般有以下3个:

-ObjC

-all_load

-force_load

下面来说说每个参数存在的意义和具体做的事情。

首先是-ObjC,一般这个参数足够解决前面提到的问题,苹果官方说明如下:

1
This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.

简单说来,加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中,虽然这样可能会因为加载了很多不必要的文件而导致可执行文件变大,但是这个参数很好地解决了我们所遇到的问题。但是事实真的是这样的吗?

如果-ObjC参数真的这么有效,那么事情就会简单多了。

1
Important: For 64-bit and iPhone OS applications, there is a linker bug that prevents -ObjC from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the -allload or -forceload flags.

当静态库中只有分类而没有类的时候,-ObjC参数就会失效了。这时候,就需要使用-all_load或者-force_load了。

-all_load会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。

-force_load所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载。

iphone6s的一个显著卖点应该是3D Touch,其原理是增加了一个压力感触,通过区分轻按和重按来进行不同的用户交互。

我们可以通过3D手势,在主屏幕上的应用Icon处,直接进入应用的响应功能模块。用户可以通过3D Touch手势在view上来预览一些预加载信息,这样的设计可以使app更加简洁大方,交互性也更强,也是对app的一个优化。

Home Screen Quick Actions

通过主屏幕的应用Icon,我们可以用3D Touch呼出一个菜单,进行快速定位应用功能模块相关功能的开发。

静态标签

静态标签是我们在项目的配置plist文件中配置的标签,在用户安装程序后就可以使用,并且排序会在动态标签的前面。

在Info.plist文件中添加UIApplicationShortcutItems数组,在改数组下建立一个包括UIApplicationShortcutItemTitle、UIApplicationShortcutSubtitle、UIApplicationShortcutItemIconFile、UIApplicationShortcutType键值对的字典即可,系统并没有提示,只能手打上去。

动态标签

动态标签是我们在程序中,通过代码添加的,与之相关的类,主要有三个:UIApplicationShortcutItem、UIMutableApplicationShortcutItem、UIApplicationShortcutIcon。

在appDelegate的-application:didFinishLaunchingWithOptions:方法中添加如下代码:

1.图标
UIApplicationShortcutIcon *icon1=[UIApplicationShortcutIcon iconWithTemplateImageName:@”iCon1”];

系统自带图标:[UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeSearch]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
typedef NS_ENUM(NSInteger, UIApplicationShortcutIconType) {
UIApplicationShortcutIconTypeCompose,
UIApplicationShortcutIconTypePlay,
UIApplicationShortcutIconTypePause,
UIApplicationShortcutIconTypeAdd,
UIApplicationShortcutIconTypeLocation,
UIApplicationShortcutIconTypeSearch,
UIApplicationShortcutIconTypeShare,
UIApplicationShortcutIconTypeProhibit NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeContact NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeHome NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeMarkLocation NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeFavorite NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeLove NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeCloud NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeInvitation NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeConfirmation NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeMail NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeMessage NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeDate NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeTime NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeCapturePhoto NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeCaptureVideo NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeTask NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeTaskCompleted NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeAlarm NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeBookmark NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeShuffle NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeAudio NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeUpdate NS_ENUM_AVAILABLE_IOS(9_1)
} NS_ENUM_AVAILABLE_IOS(9_0) __TVOS_PROHIBITED;

2.创建shortcut item
UIMutableApplicationShortcutItem *item1=[[UIMutableApplicationShortcutItem alloc] initWithType:@”com.zhuli8.dynamic” localizedTitle:@”Dynamic Shortcut” localizedSubtitle:@”available after first lauch” icon:icon1 userInfo:nil];

3.获取已经存在的shortcut item,例如静态的shortcut item
NSArray *existingItems=[UIApplication sharedApplication].shortcutItems;

4.当前所有的shortcut item赋值给当前应用程序
NSArray *updateItems=[existingItems arrayByAddingObjectsFromArray:@[item1]];
[UIApplication sharedApplication].shortcutItems=updateItems;

到此动态创建3d touch已经基本完成。如果要响应标签的行为,需要实现appDelegate中的一个方法-application:willFinishLaunchingWithOptions:

1
2
3
4
5
6
7
8
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler{

if ([shortcutItem.type isEqualToString:@"com.zhuli8.deep1"]) {
ZLSecondeViewController *secondVC=[[ZLSecondeViewController alloc] init];
self.window.rootViewController=secondVC;
[self.window makeKeyAndVisible];
}
}

peep/pop

这个功能是一套全新的用户交互机制,在使用3D Touch时,ViewController中会有如下三个交互阶段:

  1. 提示用户这里有3D Touch的交互,会使交互控件周围模糊
  2. 继续深按,会出现预览视图
  3. 通过视图上的交互控件进行进一步交互

在控制器的-viewWillAppear:方法中判断设备是否有3d touch特性,根据返回值决定是否设置代理。

1
2
3
if (self.traitCollection.forceTouchCapability==UIForceTouchCapabilityAvailable) {
[self registerForPreviewingWithDelegate:self sourceView:self.view];
}

实现UIViewControllerPreviewingDelegate的两个代理方法。

1
2
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location NS_AVAILABLE_IOS(9_0);
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit NS_AVAILABLE_IOS(9_0);

具体实例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location{
//check if we're not already displaying a preview controller
if ([self.presentedViewController isKindOfClass:[ZLPreviewViewController class]]) {
return nil;
}

ZLPreviewViewController *previewVC=[[ZLPreviewViewController alloc] init];
return previewVC;
}

- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit{
//deep press:bring up the commit view controller(pop)
ZLCommitViewController *commitVC=[[ZLCommitViewController alloc] init];
[self showViewController:commitVC sender:self];
}

重写父类的previewActionItems方法配置preview菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSArray<id<UIPreviewActionItem>> *)previewActionItems{
UIPreviewAction *action1=[UIPreviewAction actionWithTitle:@"action 1" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
ZLLogDebug(@"UIPreviewActionStyleDefault trggered");
}];

UIPreviewAction *action2=[UIPreviewAction actionWithTitle:@"destructive action" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
ZLLogDebug(@"UIPreviewActionStyleDestructive trggered");
}];

UIPreviewAction *action3=[UIPreviewAction actionWithTitle:@"selected action" style:UIPreviewActionStyleSelected handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
ZLLogDebug(@"UIPreviewActionStyleSelected trggered");
}];

UIPreviewActionGroup *group1=[UIPreviewActionGroup actionGroupWithTitle:@"action group" style:UIPreviewActionStyleDefault actions:@[action1,action2,action3]];//可以返回组
return @[action1,action2,action3];
}

Force Properties

iOS9为我们提供了一个新的交互参数:力度。我们可以检测某一交互的力度值,来做相应的交互处理。例如,我们可以通过力度来控制快进的快慢,音量增加的快慢等。

总结一下项目中用到的工具和技术,以便以后的查看。

图标

在线通过1024 * 1024图片生成iPhone 1倍、2倍、3倍图片

CocoaPods

CocoaPods详解之—-使用篇

CocoaPods详解之—-进阶篇

CocoaPods详解之—-制作篇

怎样在Swift中使用CocoaPods

怎样在swift中创建一个CocoaPods

使用Cocoapods创建私有podspec

线程方案

在iOS开发中其实有4套多线程方案,他们分别是:

pthreads

POSIX线程(POSIX threads),简称pthreads,是线程的POSIX标准。该标准定义了创建和操作线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中都使用pthreads作为操作系统的线程。

NSThread

GCD

NSOperation&NSOperationQueue

iOS 并发编程之 Operation Queues

并发编程之Operation Queue

关于iOS多线程,你看我就够了

RunLoop

RunLoop的简单介绍

iOS8适配

在iOS 8中使用UIAlertController

WWDC 2014 Session笔记 - iOS界面开发的大一统

Alamofire网络库基础教程

iOS9适配

iOS9之适配ATS

如何使用ATS提高应用的安全性

苹果新『应用通信安全』的理解和使用

UIStackView如何让你的开发更简单

整理iOS9适配中出现的坑(图文)

iOS性能调优

Analyze静态分析

逻辑错误、声明错误、api调用错误基本在编译时都会有警告,Analyze的主要的优势在于静态分析内存泄露及代码逻辑错误。

  1. 逻辑错误:访问空指针或未初始化的变量等。
  2. 内存管理错误:如内存泄露等。
  3. 声明错误:从未使用过的变量。
  4. api调用错误:未包含使用的库和框架。

Leaks内存泄露

把Snapshot Interval 间隔时间设置为10秒,勾选Automatic Snapshotting,Leaks会自动进行内存捕捉分析。在你怀疑有内存泄露的操作前和操作后点击Snapshot Now进行手动捕捉。

Leaked Object的表格中显示了内存泄露的类型、数量及内存空间。

点击具体的某个内存泄露对象,在右侧Detail窗口中会出现导致泄露可能的位置,其中黑色部分代表了最可能的位置。双击即可进入代码。

开启了ARC并不是就不会存在内存问题,苹果有句名言:ARC is only for NSObject。

补充

1、正常编译没问题,在Profile 中就编译通不过。

选择Profile,将Build Configuration设置为Debug,这样在.pch文件中,#ifdef DEBUG 编译条件下定义的宏才生效。

2、Symbol Name 列的友好显示。

BuildOpthions–>DebugInfomationFormat=DWARF with dSYM File

Xcode编译项目后,我们会看到一个同名的 dSYM 文件,dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件。

Allocation内存使用

  1. 在进入一个视图前或操作前,我们在Allocation面板左侧点击Mark Generation,这时候会产生Generation A节点,显示内存当前的情况。
  2. 在进入视图后再点一次Mark Generation。
  3. 在视图退出后再点一次Mark

这样三次产生的 Generation分别记录了进入前、进入后、关闭后,在最后一个Generation应该内存被合理释放,否则就代表了在这个视图或操作中有泄漏或不合理的地方。

Zombies僵尸对象(EXC_BAD_ACCESS)

用于查找那些被过度释放的僵尸对象(overreleased objects)。

  1. 启动Instruments,选择Zombies。
  2. 对之前产生EXC_BAD_ACCESS的测试用例重新运行,直到程序崩溃,如果发生EXC_BAD_ACCESS错误,会出现对应界面。
  3. 通过滑动箭头来查看错误细节,例如可以看到该对象的内存操作过程,如malloc、autorelease、retain、release等操作。
  4. 查看底部的详细历史,选择相应的行可以定位到相应的代码,找出产生错误的代码

参考文章

一次TableView性能优化经历

iOS性能调优系列

自动化部署

deliver用于上传应用的二进制代码、应用截屏和原数据到应用商店

snapshot可以自动化iOS应用在每个设备上的本地化截屏过程

frameit用于在应用截屏外添加设备框架

PEM可以自动化地生成和更新应用推送通知描述文件

sigh可以生成并下载开发者的应用商店配置文件

Fastlane为iOS带来持续部署

马建成系列

stackoverflow

stackoverflow在中国被墙了,不能登陆进行答题和收藏,所以在此记录工作中有所帮助的问题!根据链家基本上就能看出大概的问题了,以此来纪念那些被解决的问题!

http://stackoverflow.com/questions/26865132/strings-in-switch-statements-string-does-not-conform-to-protocol-intervaltyp#

http://stackoverflow.com/questions/25951195/swift-print-vs-println-vs-nslog#

http://stackoverflow.com/questions/28427458/ios-webrtc-library-supporting-both-armv7-arm64

其他

如何利用搜索框

Xcode各版本官方下载及百度云盘下载, Mac和IOS及Xcode版本历史.

HTTP通信过程

请求

HTTP协议规定,一个完整的由 客户端 发给 服务器 的HTTP请求中包含以下内容。

请求行

包含了 请求方法请求资源路径HTTP协议版本,如下所示:

1
GET /ZLServer/resources/images/1.jpg HTTP/1.1

请求头

包含了对客户端的环境描述客户端请求的主机地址等信息,如下所示:

1
2
3
4
5
6
Host: 192.168.1.105:8080 	// 客户端想访问的服务器主机地址
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9) Firefox/30.0
// 客户端的类型,客户端的软件环境
Accept: text/html, */* // 客户端所能接收的数据类型
Accept-Language: zh-cn // 客户端的语言环境
Accept-Encoding: gzip // 客户端支持的数据压缩格式

请求体

客户端发给服务器的具体数据,比如文件数据。

创建GET请求

1
2
3
NSString *urlStr = [@"http://192.168.1.102:8080/MJServer/login?username=123&pwd=123" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

创建POST请求

1
2
3
4
5
6
7
NSString *urlStr = @"http://192.168.1.102:8080/MJServer/login";
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// 请求体
NSString *bodyStr = @"username=123&pwd=123";
request.HTTPBody = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];

响应

客户端服务器 发送请求,服务器 应当做出响应,即返回数据给 客户端

HTTP协议规定,一个完整的HTTP响应中包含以下内容:

状态行

包含了HTTP协议版本状态码状态英文名称,如下所示:

1
HTTP/1.1 200 OK

响应头

包含了对服务器的描述对返回数据的描述,如下所示:

1
2
3
4
Server: Apache-Coyote/1.1 		    // 服务器的类型
Content-Type: image/jpeg // 返回数据的类型
Content-Length: 56811 // 返回数据的长度
Date: Mon, 23 Jun 2014 12:54:52 GMT // 响应的时间

实体内容

服务器 返回给 客户端 的具体数据,比如文件数据。

HTTP请求方案

在iOS中,常见的发送HTTP请求方案有苹果原生和第三方框架。

苹果原生(自带)

NSURLConnection

用法简单,最古老最经典最直接的一种方案。

负责发送请求,建立客户端和服务器的连接。发送NSURLRequest到数据给服务器,并收集来自服务器的响应数据。

NSURL

请求地址。

NSURLRequest

一个NSURLRequest对象就代表一个请求,它包含的信息有:

一个NSURL对象
请求方法、请求头、请求体
请求超时
... ...

NSMutableURLRequest

NSURLRequest的子类。

常用方法

1
2
3
4
5
6
7
8
9
10
11
设置请求超时等待时间(超过这个时间就算超时,请求失败)
- (void)setTimeoutInterval:(NSTimeInterval)seconds;

设置请求方法(比如GETPOST
- (void)setHTTPMethod:(NSString *)method;

设置请求体
- (void)setHTTPBody:(NSData *)data;

设置请求头
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;

NSURLConnection的使用步骤

使用NSURLConnection发送请求到步骤很简单,创建一个NSURL对象,设置请求路径;传入NSURL创建一个NSURLRequest对象,设置请求头和请求体;使用NSURLConnection发送NSURLRequest。

NSURLConnection常见的发送请求方法有以下几种:

同步请求
1
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;
异步请求

根据对服务器返回数据的处理方式掉不同,又可以分为2种:

block回调

1
+ (void)sendAsynchronousRequest:(NSURLRequest*) request queue:(NSOperationQueue*) queue completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler;

代理

1
2
3
4
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate;
+ (NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately;//在startImmediately = NO的情况下,需要调用start方法开始发送请求
- (void)start;
成为NSURLConnection的代理,最好遵守NSURLConnectionDataDelegate协议
NSURLConnectionDataDelegate协议中的代理方法
1
2
3
4
5
6
7
8
9
10
11
开始接收到服务器的响应时调用
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

接收到服务器返回的数据时调用(服务器返回的数据比较大时会调用多次)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

服务器返回的数据完全接收完毕后调用
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

请求出错时调用(比如请求超时)
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

NSURLSession

iOS7新出的技术,功能比NSURLConnection更加强大。

CFNetwork

NSURLConnection和NSURLSession的底层,纯C语言。

第三方框架

ASIHttpRequest

外号“HTTP终结者”,功能极其强大,可惜早已停止更新。

AFNetworking

简单易用,提供了基本够用的常用功能,维护和使用者多。

MKNetworkKit

简单易用,维护和使用者少。

JSON

JSON是一种轻量级的数据格式,一般用于数据交互。服务器返回给客户端的数据,一般都是JSON格式或者XML格式(文件下载除外)。

JSON解析方案

在iOS中,JSON的常见解析方案有4种

第三方框架:JSONKitSBJsonTouchJSON(性能从左到右越差)

苹果原生(自带):NSJSONSerialization(性能最好)

NSJSONSerialization的常见方法

JSON数据 –> OC对象

1
+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;

OC对象 –> JSON数据

1
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;

XML

全称是Extensible Markup Language,译作“可扩展标记语言”,跟JSON一样,也是常用的一种用于交互的数据格式,一般也叫XML文档(XML Document)。

XML语法

一个常见的XML文档一般由文档声明元素(Element)属性(Attribute)三部分组成。

文档声明

在XML文档的最前面,必须编写一个文档声明,用来声明XML文档的类型。

最简单的声明

1
<?xml version="1.0" ?>

用encoding属性说明文档的字符编码

1
<?xml version="1.0" encoding="UTF-8" ?>

元素(Element)

一个元素包括了开始标签和结束标签,规范的XML文档最多只有1个根元素,其他元素都是根元素的子孙元素。

XML中的所有空格和换行,都会当做具体内容处理。

属性(Attribute)

一个元素可以拥有多个属性,属性值必须用 双引号”” 或者 单引号’’ 括住。

实际上,属性表示的信息也可以用子元素来表示。

XML解析

XML的解析方式有2种:

DOM:一次性将整个XML文档加载进内存,比较适合解析小文件
SAX:从根元素开始,按顺序一个元素一个元素往下解析,比较适合解析大文件

在iOS中,解析XML的手段有很多种。

苹果原生

NSXMLParse:SAX方式解析,使用简单。

NSXMLParser采取的是SAX方式解析,特点是事件驱动,下面情况都会通知代理

当扫描到文档(Document)的开始与结束
当扫描到元素(Element)的开始与结束
1
2
3
4
5
6
7
使用步骤
// 传入XML数据,创建解析器
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
// 设置代理,监听解析过程
parser.delegate = self;
// 开始解析
[parser parse];

NSXMLParserDelegate

1
2
3
4
5
6
7
8
9
10
11
当扫描到文档的开始时调用(开始解析)
- (void)parserDidStartDocument:(NSXMLParser *)parser

当扫描到文档的结束时调用(解析完毕)
- (void)parserDidEndDocument:(NSXMLParser *)parser

当扫描到元素的开始时调用(attributeDict存放着元素的属性)
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict

当扫描到元素的结束时调用
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName

第三方框架

libxml2:纯C语言,默认包含在iOS SDK中,同时支持DOM和SAX方式解析

GDataXML:DOM方式解析,由Google开发,基于libxml2

GDataXML中常用的类

GDataXMLDocument:代表整个XML文档
GDataXMLElement:代表文档中的每个元素
使用attributeForName:方法可以获得属性值

XML解析方式的选择建议

大文件:NSXMLParser、libxml2
小文件:GDataXML

推荐阅读

从 NSURLConnection 到 NSURLSession

使用AFNetworking, SDWebimage和OHHTTPStubs

开始前请先看源码Demo

概述

在软件设计领域,设计模式是对通用问题的可复用的解决方案。设计模式是一系列帮你写出更可理解和复用代码的模板,设计模式帮你创建松耦合的代码以便你不需要费多大力就可以改变或者替换代码中的组件。

创建型:单利(单态)和 抽象工厂

结构型:模型-视图-控制器、装饰器、适配器、外观(门面)和组合模式

行为型:观察者、备忘录、责任链和命令模式

MVC(模型-视图-控制器)

模型-视图-控制器(MVC) 是Cocoa的构建块之一,毫无疑问它是使用最频繁的设计模式。它根据通用的角色去划分类,这样就使得类的职责可以根据角色清晰的划分开来。

Model:模型保存应用程序的数据,定义了怎么去操作它。例如在本应用中模型就是Album类。

View: 视图是模型的可视化表示以及用户交互的控件;基本上来说,所有的UIView对象以及它的子类都属于视图。在本应用中AlbumView代表了视图。

Controller:控制器是一个协调所有工作的中介者(Mediator)。它访问模型中的数据并在视图中展示它们,同时它们还监听事件和根据需要操作数据。例如在本应用中ViewController。

模型会把任何数据的变更通知控制器,然后控制器更新视图数据。视图对象通知控制器用户的操作,控制器要么根据需要来更新模型,要么检索任何被请求的数据。

你可能在想为什么不能仅仅使用控制器,在一个类中实现视图和模型,这样貌似更加容易?

所有的这些都归结于代码关注点分离以及复用。在理想的状态下,视图应该和模型完全的分离。如果视图不依赖某个实际的模型,那么视图就可以被复用来展示不同模型的数据。

举个例子来说,如果将来你打算加入电影或者书籍到你的资料库中,你仍然可以使用同样的AlbumView去显示电影和书籍数据。更进一步来说,如果你想创建一个新的与专辑有关联的工程,你可以很简单的复用Album类,因为它不依赖任何视图。这就是MVC的强大之处。

单例模式

单例设计模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。它通常采用懒加载的方式在第一次用到实例的时候再去创建它。

苹果大量使用了此模式。例如:

1
2
3
4
[NSUserDefaults standardUserDefaults]
[UIApplication sharedApplication]
[UIScreen mainScreen]
[NSFileManager defaultManager]

有一些情况下,只有一个实例显得非常合理。举例来说,你不需要有多个Logger的实例,除非你想去写多个日志文件。或者一个全局的配置处理类:实现线程安全的方式访问共享实例是容易的,比如一个配置文件,有好多个类同时修改这个文件。

外观模式

外观模式针对复杂的子系统提供了单一的接口,不需要暴漏一系列的类和API给用户,你仅仅暴漏一个简单统一的API。这个API的使用者完全不需要关心背后的复杂性。这个模式非常适合有一大堆很难使用或者理解的类的情况。外观模式解耦了使用系统的代码和需要隐藏的接口和实现类。它也降低了外部代码对内部子系统的依赖性。当隐藏在外观之后的类很容易发生变化的时候,此模式就很有用了,因为当背后的类发生变化的时候,外观类始终保持了同样的API。

本应用中用PersistencyManager本地保存专辑数据,使用HTTPClient处理远程连接,工程中的其它类暂时与本次实现的逻辑无关。

为了实现这个模式,只有LibraryAPI应该保存PersistencyManager和HTTPClient的实例,然后LibraryAPI将暴漏一个简单的API去访问这些服务。LibraryAPI将暴漏给其它代码,但是它隐藏了HTTPClient和PersistencyManager的复杂性。

装饰器(Decorator)模式

装饰器模式在不修改原来代码的情况下动态的给对象增加新的行为和职责,它通过一个对象包装被装饰对象的方法来修改类的行为,这种方法可以做为子类化的一种替代方法。
在Objective-C中,存在两种非常常见的实现:Category(类别)和Delegation(委托)。

Category(类别)

Category(类别)是一种不需要子类化就可以让你能动态的给已经存在的类增加方法的强有力的机制。新增的方法是在编译期增加的,这些方法执行的时候和被扩展的类的其它方法是一样的。它可能与装饰器设计模式的定义稍微有点不同,因为Category(类别)不会保存被扩展类的引用。

除了可以扩展你自己的类以外,还可以给Cocoa自己的类增加方法。

本应用中需要让Album(专辑)对象显示在一个表格视图(TableView)中:专辑的标题从何而来?因为专辑是模型对象,它本身不需要关心你如何显示它的数据。你需要增加一些代码去扩展专辑类的行为,但是不需要直接修改专辑类。创建一个专辑类扩展的类别,定义一个新的方法,这个方法会返回能很容易和UITableViews使用的数据结构。

Delegation(委托)

当你使用UITableView的时候,你必须要实现tableView:numberOfRowsInSection:方法。
你不可能让UITableView知道它需要在每个区域显示多少行,因为这些是应用特定的数据。因此计算每个区域需要显示多少行的职责就给了UITableView的委托。这就让UITableView类独立于它要显示的数据。

UITableView的职责就是显示一个表格视图。然而最终它需要一些它自身没有的信息。那么它就求助于它的委托,通过发送消息给委托来获取信息。在Objective-C实现委托模式的时候,一个类可以通过协议(Protocol)来声明可选以及必要的方法。

这个是一个重要的模式。苹果在UIKit类中大量使用了它:UITableView, UITextView, UITextField, UIWebView, UIAlert, UIActionSheet, UICollectionView, UIPickerView,UIGestureRecognizer, UIScrollView等等等。

适配器(Adapter)模式

适配器可以让一些接口不兼容的类一起工作。它包装一个对象然后暴漏一个标准的交互接口。

苹果通过一个稍微不同的方式来实现它-苹果使用了协议的方式来实现。你可能已经熟悉UITableViewDelegate, UIScrollViewDelegate, NSCoding 和 NSCopying协议。举个例子,使用NSCopying协议,任何类都可以提供一个标准的copy方法。

观察者(Observer)模式

在观察者模式中,一个对象任何状态的变更都会通知另外的对改变感兴趣的对象。这些对象之间不需要知道彼此的存在,这其实是一种松耦合的设计。当某个属性变化的时候,我们通常使用这个模式去通知其它对象。

此模式的通用实现中,观察者注册自己感兴趣的其它对象的状态变更事件。当状态发生变化的时候,所有的观察者都会得到通知。苹果的推送通知(Push Notification)就是一个此模式的例子。

如果你要遵从MVC模式的概念,你需要让模型对象和视图对象在不相互直接引用的情况下通信。这正是观察者模式的用武之地。

Cocoa通过通知(Notifications)和Key-Value Observing(KVO)来实现观察者模式。

通知(Notifications)

不要和远程推送以及本地通知所混淆,通知是一种基于订阅-发布模式的模型,它让发布者可以给订阅者发送消息,并且发布者不需要对订阅者有任何的了解。

通知在苹果官方被大量的使用。举例来说,当键盘弹出或者隐藏的时候,系统会独立发送UIKeyboardWillShowNotification/UIKeyboardWillHideNotification通知。当你的应用进入后台运行的时候,系统会发送一个UIApplicationDidEnterBackgroundNotification通知。

Key-Value Observing(KVO)模式

在KVO中,一个对象可以要求在它自身或者其它对象的属性发送变化的时候得到通知。

KVO机制让对象可以感知到属性的变化。在本例中,你可以使用KVO去观察UIImageView的image属性的变化。

备忘录(Memento)模式

备忘录模式快照对象的内部状态并将其保存到外部。换句话说,它将状态保存到某处,过会你可以不破坏封装的情况下恢复对象的状态,也就是说原来对象中的私有数据仍然是私有的。

归档(Archiving)

归档是苹果对于备忘录模式的特定实现之一。这种机制可以转换一个对象到一个可保存的数据流中,过会可以在不暴漏私有属性给外部的情况下重建它们。

命令模式

命令模式将一个请求封装为一个对象。封装以后的请求会比原生的请求更加灵活,因为这些封装后的请求可以在多个对象之间传递,存储以便以后使用,还可以动态的修改,或者放进一个队列中。苹果通过Target-Action机制和Invocation实现命令模式。

你可以通过苹果的官方在线文档阅读更多关于Target-Action的内容,至于Invocation,它采用了NSInvocation类,这个类包含了一个目标对象,方法选择器,以及一些参数。这个对象可以动态的修改并且可以按需执行。实践中它是一个命令模式很好的例子。它解耦了发送对象和接受对象,并且可以保存一个或者多个请求。

参考文章

IOS设计模式之一(MVC模式,单例模式)

概述

简单的说Shell就是一个包含若干行Shell或者Linux命令的文件。对于一次编写,多次使用的大量命令,就可以使用单独的文件保存下来,以便日后使用。

通常Shell脚本以.sh为后缀。如果要执行该脚本,必须先使其可执行

1
chmod +x filename

此后在该脚本所在目录下,输入./filename即可执行该脚本。

还有一种更简单的方法就是直接在终端用 sh 指令来执行。

1
sh filename

最近打包webRTC库时重复的工作和容易出错的机会让我想到了Shell脚本。基本需求就是把C++工程师那边的arm64、armv7、i386三个架构的zip包(webRTC静态库包的压缩文件)打成一个.a静态库。

下面就是这个过程中解决的两个版本。

第一个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/sh
# arm64包的名称
arm64=Release-iphoneos
# armv7包的名称
armv7=Release-iphoneos
# i386包的名称
i386=Debug-iphonesimulator

# 解压缩该目录下的$arm64.zip文件到该目录下的$arm64文件夹下,不提示的情况下覆盖文件
unzip -o -d arm64/$arm64 arm64/$arm64
# 解压缩该目录下的$armv7.zip文件到该目录下的$armv7文件夹下,不提示的情况下覆盖文件
unzip -o -d armv7/$armv7 armv7/$armv7
# 解压缩该目录下的$i386.zip文件到该目录下的$i386文件夹下,不提示的情况下覆盖文件
unzip -o -d i386/$i386 i386/$i386
echo "解压缩成功"

# 将上面的两个静态库移到其上一个目录
mv arm64/$arm64/$arm64/$capture arm64/$arm64/$arm64/$render arm64/$arm64/
mv armv7/$armv7/$armv7/$capture armv7/$armv7/$armv7/$render armv7/$armv7/
mv i386/$i386/$i386/$capture i386/$i386/$i386/$render i386/$i386/
echo "成功移动两个特殊的静态库"

# 合并arm64/$arm64/$arm64/下所有*.a构建arm64为libWebRTC-arm64.a
libtool -static -o arm64/$arm64/libWebRTC-arm64.a arm64/$arm64/$arm64/*.a
# 合并armv7/$armv7/$armv7/下所有*.a构建armv7为libWebRTC-armv7.a
libtool -static -o armv7/$armv7/libWebRTC-armv7.a armv7/$armv7/$armv7/*.a
# 合并i386/$i386/$i386/下所有*.a构建i386为libWebRTC-i386.a
libtool -static -o i386/$i386/libWebRTC-i386.a i386/$i386/$i386/*.a
echo "成功构建libWebRTC-arm64.a、libWebRTC-armv7.a、libWebRTC-i386.a"

echo "正在执行最后的合成操作,请稍后..."

# 创建支持arm64、armv7、i386的libWebRTC
lipo -create arm64/$arm64/$capture armv7/$armv7/$capture i386/$i386/$capture -output libVideoCapture.a
lipo -create arm64/$arm64/$render armv7/$armv7/$render i386/$i386/$render -output libVideoRender.a
lipo -create arm64/$arm64/libWebRTC-arm64.a armv7/$armv7/libWebRTC-armv7.a i386/$i386/libWebRTC-i386.a -output libWebRTC.a
echo "成功创建支持arm64、armv7、i386的libWebRTC"

# 清理中间垃圾文件(把子目录及子目录中所有档案删除,并且不用一一确认)
rm -rf arm64/$arm64
rm -rf armv7/$armv7
rm -rf i386/$i386

第二个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# arm64包的名称
arm64=Release-iphoneos
# armv7包的名称
armv7=Release-iphoneos
# i386包的名称
i386=Debug-iphonesimulator

tempArm64Path=arm64/$arm64
tempArmv7Path=armv7/$armv7
tempI386Path=i386/$i386
# 定义需要移动的两个静态库的别名
capture=libvideo_capture_module_internal_impl.a
render=libvideo_render_module_internal_impl.a

# 解压缩第一个参数下的zip文件到该目录下,不提示的情况覆盖文件
funUnzip(){
unzip -o -d ${1} ${1}
echo "解压缩${1}成功"
}

# 将上面的两个静态库移到其上一个目录
funMoveStatic(){
funUnzip ${1}

mv ${1}/${2}/$capture ${1}/${2}/$render ${1}
echo "成功移动${1}/${2}中两个特殊的静态库"
}

# 合并arm64/$arm64/$arm64/下所有*.a构建arm64为libWebRTC-arm64.a这种情况
funMergeStatic(){
funMoveStatic ${1} ${2}

libtool -static -o ${1}/libWebRTC-${3}.a ${1}/${2}/*.a
echo "成功构建libWebRTC-${3}.a"
}

funBuildWebRTC(){
funMergeStatic ${tempArm64Path} ${arm64} "arm64"
funMergeStatic ${tempArmv7Path} ${armv7} "armv7"
funMergeStatic ${tempI386Path} ${i386} "i386"

echo "正在执行最后的合成操作,请稍后..."

# 创建支持arm64、armv7、i386的libWebRTC
lipo -create ${tempArm64Path}/$capture ${tempArmv7Path}/$capture ${tempI386Path}/$capture -output libVideoCapture.a
lipo -create ${tempArm64Path}/$render ${tempArmv7Path}/$render ${tempI386Path}/$render -output libVideoRender.a
lipo -create ${tempArm64Path}/libWebRTC-arm64.a ${tempArmv7Path}/libWebRTC-armv7.a ${tempI386Path}/libWebRTC-i386.a -output libWebRTC.a
echo "成功创建支持arm64、armv7、i386的libWebRTC"

# 清理中间垃圾文件(把子目录及子目录中所有档案删除,并且不用一一确认)
rm -rf ${tempArm64Path}
rm -rf ${tempArmv7Path}
rm -rf ${tempI386Path}
}
funBuildWebRTC

参考文章

shell 命令

Linux Shell脚本教程:30分钟玩转Shell脚本编程

Shell常用招式大全之入门篇

Objective-C是基于C语言加入了 面向对象特性消息转发机制 的动态语言,这意味着它不仅需要一个编译器,还需要 Runtime系统 来动态创建类和对象,进行消息发送和转发。

Runtime即运行时,是系统在运行的时候的一些机制,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据 函数的名称 找到对应的函数来调用。

事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言在这种情况下,编译阶段就会报错。对于C语言,函数的调用在编译的时候就会决定调用哪个函数,编译完成之后直接顺序执行,无任何二议性。

RunTime源码,它是一套比较底层的纯C语言API,属于一个C语言库,包含了很多底层的C语言API。平时编写的OC代码,在程序运行过程中,其实最终都是转成了Runtime的C语言代码,Runtime算是OC的幕后工作者。

数据结构

SEL

1
typedef struct objc_selector *SEL;

SEL其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个Int类型的一个地址,地址中存放着方法的名字。对于一个类中每一个方法对应着一个SEL,所以iOS类中不能存在2个名称相同的方法,即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。

可以通过Objc编译器命令@selector()或者Runtime系统的sel_registerName函数来获取一个SEL类型的方法选择器。如果你知道selector对应的方法名是什么,可以通过NSString* NSStringFromSelector(SEL aSelector)方法将SEL转化为字符串,再用NSLog打印。

id

1
typedef struct objc_object *id;

id是通用类型指针,能够表示任何对象。id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类。

1
struct objc_object { Class isa; };

isa指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用class方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 isa-swizzling 的技术,详见官方文档

Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct objc_class *Class;
struct objc_class {
Class isa; // 指向metaclass

Class super_class ; // 指向其父类
const char *name ; // 类名
long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols; // 存储该类遵守的协议
}

可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。

在objc_class结构体中:ivars是objc_ivar_list指针;methodLists是指向objc_method_list指针的指针。也就是说可以动态修改*methodLists的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。关于二级指针,可以参考这篇文章

Method

1
2
3
4
5
6
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

Method表示类中的某个方法,是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。

IMP

1
typedef id (*IMP)(id, SEL, ...);

IMP指向的方法与objc_msgSend函数类型相同,参数都包含id和SEL类型。每个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的SEL对应的方法实现肯定是唯一的,通过一组id和SEL参数就能确定唯一的方法实现地址;反之亦然。它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。

Ivar

1
2
3
4
5
6
7
8
9
10
11
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}

Ivar表示类中的实例变量,是一个指向objc_ivar结构体指针,它包含了变量名(ivar_name)、变量类型(ivar_type)等信息。

##Property

1
2
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//这个更常用

Property代表类中的属性,它是一个指向objc_property结构体的指针。可以通过class_copyPropertyList 和 protocol_copyPropertyList方法来获取类和协议中的属性:

1
2
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

返回类型为指向指针的指针,因为属性列表是个数组,每个元素内容都是一个objc_property_t指针,而这两个函数返回的值是指向这个数组的指针。

Cache

1
typedef struct objc_cache *Cache

Cache为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在isa指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了,而是优先在Cache中查找。Runtime 系统会把被调用的方法存到Cache中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。这根计算机组成原理中学过的 CPU 绕过主存先访问Cache的道理挺像。

消息

Objc 中发送消息是用中括号([])把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。下面详细叙述下消息发送步骤:

  1. 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain, release 这些函数了。
  2. 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
  3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
  4. 如果 cache 找不到就找Class中的方法列表。
  5. 如果Class中的方法列表找不到就到超类的Class中的方法列表去找,一直找,直到找到NSObject类为止。
  6. 如果还找不到就要开始进入动态方法解析了。

应用

Runtime是属于OC的底层,可以进行一下非常底层的操作:

  1. 在程序运行过程中动态地创建一个类(比如KVO底层的实现)。
  2. 在程序运行过程中动态地为某个类添加属性、方法,修改属性值和方法。
  3. 遍历一个类的所有成员变量、属性、方法。如:字典转模型利用Runtime遍历模型对象的所有属性,根据属性名从字典中取出对应的值设置到模型的属性上。

相关的头文件:

1
2
<objc/runtime.h>
<objc/message.h>

参考资料

刨根问底Objective-C Runtime

Objective-C特性:Runtime