工具

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 內置模擬器的新功能與技巧

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

@property

在普通的OC对象中,@property就是编译器自动帮我们生成一个私有的成员变量和getter、setter方法的声明及实现。为了研究编译器是如何实现@property的,我们需要使用clang。clang提供一个命令,可以将OC的源码改写成c++的,借此可以研究@property具体的源码实现方式。该命令是:

1
clang -rewrite-objc xxx.m

除此之外我们还可以通过苹果开源的runtime进行研究,源码地址。源码

线程安全

atomic、nonatomic

atomic为原子性,会对set方法的实现进行加锁,多线程下可以一个线程写多个线程读,主要用在mac开发;nonatomic为非原子性,set方法的实现不加锁(比atomic性能高)。因为mac开发早于iOS开发,所以默认值为atomic。(A joke!)在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备nonatomic特质,则不使用同步锁。

一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读到不同的属性值。因此,在iOS开发中,你会发现,几乎所有属性都声明为nonatomic;但是在开发Mac OS X程序时,使用 atomic属性通常都不会有性能瓶颈。

问题来了:

@property (strong) NSMutableArray *array; 有什么问题?

线程安全特性默认为atomic,该属性使用了同步锁,会在创建属性时生成一些额外的代码用于帮助写多线程程序,这会带来性能问题,通过显示声明nonatomic可以节省这些虽然很小但是不必要的额外开销。

##访问权限

readonly、readwrite

readonly只生成get方法的声明和实现; readwrite同时生成get方法和set方法的声明和实现。

内存管理

每个对象都有一个引用计数器,每个新对象的计数器默认是1,当对象的计数器减为0时就会被销毁;通过retain可以让对象的计数器加1、release可以让对象的计数器减1,还可以通过 autorelease pool 管理内存;只要调用了alloc、copy、new、retain方法产生的新对象,都必须在最后调用一次release或者autorelease;如果使用ARC,编译器会自动生成管理内存的代码。

assign

set方法的实现是直接赋值,用于基本数据类型的简单赋值操作,用于非OC对象。

weak(ARC)

weak表明该属性定义了一种“非拥有关系(nonowning relationship)”,set方法的实现既不release旧也不retain新值,同assign类似,然而当对象销毁的时候,指针会被自动设置为nil,weak必须用于OC对象。

使用场合:

*在ARC中,出现循环引用的时候,必须要有一端使用weak,比如:delegate。

*自身已经对它进行一次强引用,没有必要在强引用一次,此时也会使用weak,比如@IBOutlet。

copy

set方法的实现是release旧值,copy新值,用于NSString、block等类型。
栗子来了:

1
2
@property(nonatomic, copy) NSString *name;
- (void)setName:(NSString *)name { if (_name != name) { [_name release]; _name = [name copy]; } }

字符串使用copy是为了外部把字符串内容改了不影响该属性;block使用copy是在MRC遗留下来的,在MRC中方法内部的block是在栈区的,使用copy可以把它放到堆区。在ARC中对于block使用copy还是strong效果是一样的。

Copy扩展

前提

实现NSCopying或NSMutableCoping。

作用

  1. 改变原对象不影响拷贝对象。
  2. 改变拷贝对象不影响源对象。

浅复制和深复制

Copy产生的是不可变副本,MutableCopy产生的是可变副本。

只要源对象是不可变类型且产生的对象也是不可变类型的时的情况是浅复制,其他情况都是深复制。浅复制是源对象和拷贝对象指向同一对象,深复制是产生了不同的对象。

@property和copy

在set方法中release旧值copy新值,用于NSString和block,可以保证属性不会被外部变量(例如NSMutableString类型变量)的改变所影响。

问题来了:

@property (nonatomic,copy) NSMutableArray *array;这个写法会出什么问题?

copy产生的是不可变副本,在set方法中release旧值copy新值,所以运行时类型为NSArray,编译时类型为NSMutableArray,所以对array执行添加、删除、修改数组内的元素的时候程序会因为找不到对应的方法而崩溃。

栗子来了:

当一个使用 initWithArray: 初始化方法创建的NSMutable对象赋值给array属性,那么之后array执行可变数组的方法,比如:removeObjectAtIndex: 时会出现”-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x100206cd0”的崩溃。原因在于array属性在被赋值(setter)的时候默认执行了copy方法后变为了不可变NSArray对象。

自定义对象

  1. 遵守NSCopying或NSMutableCopying协议。其实也可以在代码中不写协议类的遵守,只实现里面的方法,协议只是用来方便生成快捷方法的。
  2. 实现copyWithZone或mutableCopyWithZone。即用self实例化对象([[[self class] allocWithZone: zone]init];)、属性赋值、返回对象。

特别注意

编译时类型和运行时类型,应以运行时类型为准。

strong(ARC)、retain(MRC)

set方法的实现是release旧值,retain新值,用于OC对象类型。

栗子来了:

1
2
@property(nonatomic, copy) NSString *name;
- (void)setName:(NSString *)name { if (_name != name) { [_name release]; _name = [name retain]; } }

##指定方法名称
setter=

getter=

@synthesize和@dynamic

@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是:

1
@synthesize var=_var;

@synthesize

@synthesize的语义就是,如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,如果这个成员变量已经存在了就不再生成。

如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

在@property被增强之后其实已经很少使用@synthesize

@dynamic

@dynamic告诉编译器,属性的setter方法和getter方法由我自己实现不用自动生成(对于readonly的属性只需提供getter方法即可),如果你木有提供setter方法和getter方法,编译时是没问题,但是当程序运行时调用会由于缺少setter方法或gettr方法导致程序崩溃。

编译时是靠Xcode把关,运行时靠runtiime机制来执行相应的方法,这就是所谓的动态绑定。

参考链接

iOS面试题

iOS面试题集锦

《招聘一个靠谱的iOS》面试题参考答案(上)

子曰:工欲善其事,必先利其器。居是邦也。事其大夫之贤者,友其士之仁者。

有志者事竟成,破釜沉舟,百二秦关终属楚; 苦心人天不负,卧薪尝胆,三千越甲可吞吴。

由此产生此文!

Mac常用快捷键

窗口操作

1
Command+Q:退出程序
1
Dock右键App+Alt:退出程序-->强制退出
1
Command+W:关闭程序的窗口,并没有真正退出程序
1
Command(长按)+Tab:切换程序窗口
1
Command(长按)+空格:切换输入法
1
Ctrl+⬆️:管理多个桌面

文件操作

1
点击一下文件、文件夹+空格:快速预览内容,无需双击打开。

Xcode常用快捷键

##Xcode导航快捷键

1
Command+1:工程导航器

1
Command+0:显示、隐藏导航器面板
1
Command+Option+0:显示、隐藏实用工具面板
1
在项目导航器中选中文件,执行Option+左键点击操作:在辅助编辑器中打开文件

Xcode搜索快捷键

1
Command+Shift+F:搜索导航器(Find Navigator,也就是搜索)
1
Control+6(键入方法/变量名+Enter跳转):文件跳转栏
1
Command + Shift + O:快速打开
1
Control + Command + ⬆️:程序中(Objective-CC++编写).h and .m文件间的快速切换
1
Command+L:输入行号进行快速查找

应用程序编译和清理

1
Command + R:运行app
1
Command + Shift + K:清除工程
1
Command + B:构建应用程序

调试相关

1
Command + . :方便地暂停运行iOS模拟器
1
Command+K:删除调试面板里的信息,相当于Debug Workflow->Clear Console
1
Ctrl+Command+Y:继续执行
1
F6:跳过方法
1
F7:跳入方法
1
F8:跳出方法

文档和帮助

1
Command + Shift + 0 (Zero):文档和参考
1
在类或者方法名上执行Option + 左键点击操作:快速帮助

其他快捷键

1
Command + Shift + J:可展示当前你在工程导航器中打开的文件
1
Ctrl + 1:可打开"Show Related Items’弹出菜单
1
Command + Option + Shift + 左键点击操作:该组合键可展示一个小尺寸的弹出视图,你可以查看你想要打开它的地方,比如辅助编辑器、标签或者窗口等。

VIM编辑器相关

VIM的运行模式

编辑模式:等待编辑命令输入

插入模式:编辑模式下,输入 “i” 进行插入模式,插入文本信息

命令模式:在编辑模式下按Esc键,输入 “:” 进行命令模式

VIM使用的命令

1
:q:直接退出
1
:wq:保存后退出
1
:q!:强制退出
1
Shift+v:选中光标行
1
y:复制选中行
1
p:粘贴选中行
1
x:删除光标前的一个字符

常见Unix指令

因为Mac系统是基于UNIX系统的,因此可以在“终端”中输入一些UNIX指令来操作Mac系统。比如:新建文件(夹)、打开文件(夹)等。

文件目录操作指令

1
ls:列出当前目录下的所有内容(文件、文件夹)

-l:列出文件的详细信息

-a:列出当前目录所有文件,包括隐藏文件

1
mkdir:新建一个目录

-p:父目录不存在的情况下先生成父目录

1
cd:改变当前操作的目录
1
touch:新建一个文件(文件不存在才会新建)
1
cat(tac):显示文本文件内容
1
cp:复制文件或目录
1
rm:删除文件

-r:同时(递归)删除该目录下的所有文件

-f:强制删除文件或目录

mv:移动文件或目录
1
find:在文件系统中查找指定的文件
1
pwd:显示当前目录的名称
1
open:打开一个文件(夹)

注:按一下Tab键可以自动补齐指令名称、文件名、文件夹名等。

Unix指令中的特殊路径

Mac系统采用的是UNIX文件系统,所有的文件都放在根目录下(即“\”),因此没有Windows中的C盘、D盘的概念,文件路径就不再有盘符。例如:

Windows中:c://Users/你的用户名/Desktop

Mac中:/Users/你的用户名/Desktop

在使用Unix指令过程中,经常会涉及到目录(路径)操作,下面列出几个有特殊含义的路径:

/:根路径
./:当前路径
../:上一级路径
/:根目录,以斜杠表示,其他所有文件和目录在根目录下展开。
/bin:“binary”的缩写,存放提供用户级基础功能的二进制文件,如ls、ps等。
/boot:存放能使系统成功启动的所有文件,这些文件一般在内核用户程序开始执行前得到调用。在iOS中此目录为空。
/dev:“device”的简写,存放BSD设备文件。每个文件代表系统的一个块设备或字符设备,一般来说,“块设备”以块为单位传输数据,如硬盘;而“字符设备”以字符为单位传输数据,如调整解调器。
/sbin:“system binaries”的简写,存放提供系统级基础功能的二进制文件,如netstat、reboot等。
/etc:“et cetera”的简写,存放系统脚本及配置文件,如passwd、hosts等。在iOS中,/etc是一个符号链接,实际指向/private/etc。
/lib:存放系统库文件、内核模块及设备驱动等。iOS中此目录为空。
/mnt:“mount”的简写,存放临时的文件系统挂载点。iOS中此目录为空。
/private:存放两个目录,分别是/private/etc和/private/var。
/tmp:临时目录。在iOS中,/tmp是一个符号链接,实际指向/private/tmp。
/usr:包含了大多数用户工具和程序。/usr/bin包含那些/bin和/sbin中未出现的基础功能,如nm、killall等;/usr/include包含所有的标准C头文件;/usr/lib存放库文件。
/var:“variable”的简写,存放一些经常更改的文件,如日志、用户数据、临时文件等。其中/var/mobile/Applications下存放了所有App Store App,是要重点关注的目录之一。

系统管理命令

1
who(w):显示在线登陆用户
1
whoami:显示用户自己的身份
1
hostname:显示主机名称
1
uname:显示系统信息
1
top:显示当前系统中耗费资源最多的进程
1
ps:显示瞬间的进程状态
ifconfig en0:显示网络接口信息
1
clear:清屏
1
man:命令帮助信息查询

其他

1
2
3
4
5
export

设置或显示环境变量,但是只在本次登陆中有效。在shell中执行程序时,shell会提供一组环境变量,export可新增、修改或删除环境变量,供后续执行的程序使用,效力仅及于此登陆操作。export设置环境变量是暂时的,只在本次登陆中有效。=前的PATH变量不加$符号,再增加的路径用:追加。

export PATH=$PATH:/Library/Developer/IceTouch-1.3/SDKs/Objc/bin

iphone的一些操作

双击Shift开启大写

摇一摇撤销

计算器输入的时候手指右划可以删除输入的数字

按住键盘上的小地球切换输入法

拍照可以按声音键

点击状态栏回到顶部

导航侧滑

写在最后

iOS开发和Mac使用中你还有哪些能提高开发效率的小技巧和快捷键以及黑科技,在评论中一起分享吧。