iOS 的沙盒目录结构是怎样的? App Bundle 里面都有什么?
iOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等。
沙盒结构
- Application:存放程序源文件,上架前经过数字签名,上架后不可修改
- Documents:常用目录,iCloud备份目录,存放数据,这里不能存缓存文件,否则上架不被通过
- Library
- Caches:存放体积大又不需要备份的数据,SDWebImage缓存路径就是这个
- Preference:设置目录,iCloud会备份设置信息
- tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能
App Bundle
- Info.plist:此文件包含了应用程序的配置信息.系统依赖此文件以获取应用程序的相关信息
- 可执行文件:此文件包含应用程序的入口和通过静态连接到应用程序target的代码
- 资源文件:图片,声音文件一类的
- 其他:可以嵌入定制的数据资源
iOS 的签名机制大概是怎样的?
假设,我们有一个APP需要发布,为了防止中途篡改APP内容,保证APP的完整性,以及APP是由指定的私钥发的。首先,先将APP内容通过摘要算法,得到摘要,再用私钥对摘要进行加密得到密文,将源文本、密文、和私钥对应的公钥一并发布即可。
那么如何验证呢?验证方首先查看公钥是否是私钥方的,然后用公钥对密文进行解密得到摘要,将APP用同样的摘要算法得到摘要,两个摘要进行比对,如果相等那么一切正常。这个过程只要有一步出问题就视为无效。
+load 和 +initialize 的区别是什么?
load对于加入运行期系统中的每个类及分类都会调用且仅调用一次。通常app启动时就会执行,调用顺序父类->子类->分类。iniitialize程序首次调用该类前调用,只调用一次,只有在用到时才调用,而load是程序会执行所有类的load,无论是否用到。initialize遵从普通方法的覆写如果本类没有,会调用其父类的,而load不会自动覆写。
strong / weak / unsafe_unretained 的区别?
- weak只能修饰OC对象,使用weak不会使计数器加1,对象销毁时修饰的对象会指向nil
- strong等价与retain,能使计数器加1,且不能用来修饰数据类型
- unsafe_unretained等价与assign,可以用来修饰数据类型和OC对象,但是不会使计数器加1,且对象销毁时也不会将对象指向nil,容易造成野指针错误
如何为 Class 定义一个对外只读对内可读写的属性?
在头文件中将属性定义为readonly,在.m文件中将属性重新定义为readwrite
Objective-C 中,meta-class 指的是什么?
meta-class 是 Class 对象的类,为这个Class类存储类方法,当一个类发送消息时,就去那个类对应的meta-class中查找那个消息,每个Class都有不同的meta-class,所有的meta-class都使用基类的meta-class(假如类继承NSObject,那么他所对应的meta-class也是NSObject)作为他们的类
UIView 和 CALayer 之间的关系?
- UIView显示在屏幕上归功于CALayer,通过调用drawRect方法来渲染自身的内容,调节CALayer属性可以调整UIView的外观,UIView继承自UIResponder,CALayer不可以响应用户事件
- UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它内部是由Core Animation来实现的,它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的根绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性
- UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示
什么时候会发生「隐式动画」?
当改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来.相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作,这就是隐式动画
Push Notification 是如何工作的?
推送通知分为两种,一个是本地推送,一个是远程推送
- 本地推送:不需要联网也可以推送,是开发人员在APP内设定特定的时间来提醒用户干什么
- 远程推送:需要联网,用户的设备会于苹果APNS服务器形成一个长连接,用户设备会发送手机UDID 和应用Bundle idenidentifier给苹果服务器,苹果服务器会加密生成一个deviceToken给用户设备,然后设备会将deviceToken发送给APP的服务器,APP服务器会将deviceToken存进他们的数据库,这时候如果有人发送消息给我,APP服务器端就会去查询我的deviceToken,然后将deviceToken和要发送的信息发送给苹果服务器,苹果服务器通过deviceToken找到我的设备并将消息推送到我的设备上,这里还有个情况是如果APP在线,那么APP服务器会于APP产生一个长连接,这时候APP服务器会直接通过deviceToken将消息推送到设备上。
什么是 Runloop?
可以把Runloop理解为一个循环,在这个循环里面等待事件然后处理事件。而这个循环是基于线程的,在Cocoa中每个线程都有它的Runloop,通过这样的机制,线程可以在没有事件要处理的时候休息,有事件运行,减轻CPU压力。只有主线程的Runloop是默认在运行中的,用户启动的线程,需要指定让其运行起来。线程可以通过以下方式挂载事件源:(1)创建nstimer,挂载timer事件源;(2)关联port:这个还没深入了解;(3)performselector:挂载需要发送的消息。
当系统出现内存警告时会发生什么?
- 会将不在当前窗口上的view暂时移除
- 如果放任内存警告,最终会导致软件强制被系统关闭
autorelease 对象在什么情况下会被释放?
分两种情况:手动干预释放和系统自动释放
- 手动干预释放就是指定autoreleasepool,当前作用域大括号结束就立即释放
- 系统自动去释放:不手动指定autoreleasepool,Autorelease对象会在当前的 runloop 迭代结束时释放
为什么 NotificationCenter 要 removeObserver? 如何实现自动 remove?
- 如果不移除的话,万一注册通知的类被销毁以后又发了通知,程序会崩溃.因为向野指针发送了消息
- 实现自动remove:通过自释放机制,通过动态属性将remove转移给第三者,解除耦合,达到自动实现remove
最常用的版本控制工具是什么?
git的工具是SourceTree,svn的工具是Cornetstore
你一般是怎么用 Instruments 的?
1.Time Profiler:性能分析
2.Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能
3.Allocations:用来检查内存,写算法的那批人也用这个来检查
4.Leaks:检查内存,看是否有内存泄露
谈谈iOS开发中Debug和Release的区别和使用
**Debug **: 调试版本,主要是让程序员使用,在调试的过程中调用 Debug 会启动更多的服务来监控错误,运行速度相对较慢,而且比较耗能.只有Debug版的程序才能设置断点、单步执行、使用TRACE/ASSERT等调试输出语句
Release : 发行版本,主要是让用户使用, 在使用的过程中会去掉那些繁琐的监控服务,运行速度相对较快,而且比较节约内存.
在程序开发完成以后,应该在测试的时候 把 Debug 和
Release 两个版本都测试一下,在提测的时候以 Release 测试的情况为准. 因为 Release 状态下测试的情况,是用户使用的真是情景.
在程序调试的过程中, Xcode 默认的情况是 Debug ,如果想修改成 Release 情景下测试.
需要选择菜单栏-Product-Scheme-Edit Scheme 弹出界面如下:
有哪些常见的 Crash 场景?
- 访问了僵尸对象
- 访问野指针
- 访问了不存在的方法
- 数组越界
- 在定时器下一次回调前将定时器释放
请谈一谈UIViewController的完整生命周期
-[ViewController initWithNibName:bundle:];
-[ViewController init];
-[ViewController loadView];
-[ViewController viewDidLoad];
-[ViewController viewWillAppear:];
-[ViewController viewDidAppear:];
-[ViewController viewWillDisappear:];
-[ViewController viewDidDisappear:];
谈谈控制器View的加载过程?
当程序访问了控制器的View属性时会先判断控制器的View是否存在,
- 如果存在就直接返回已经存在的View;
- 如果不存在,就会先调用loadView这个方法;
- 如果控制器的loadView方法实现了,就会按照loadView方法加载自定义的View;
- 如果控制器的loadView方法没有实现就会判断storyboard是否存在;
- 如果storyboard存在就会按照storyboard加载控制器的View;
- 如果storyboard不存在,就会创建一个空视图返回。
请描述一下Storyboard和标准NIB文件的差别。
-
xib是一个基于xml的描述文件,可以实现可视化编程。一个工程中可以有多个xib文件,一个xib文件对应着一个视图控制器和多个视图。
-
storyboard是多个xib文件集合的描述文件,也采用xml格式,也可以实现可视化编程,而且在多个视图控制器的情况下,storyboard可以描述个视图控制器之间的导航关系。
什么时候会发生 EXC BAD ACCESS 异常?
访问一个僵尸对象,访问僵尸对象的成员变量或者向其发消息死循环
NSNotification 和 KVO 的使用场景?
KVO使用场景:当一个对象的特定属性改变的时候,需要被通知一个或者多个对象的时候
NSNotification使用场景:跨层级传递值,多个对象通知多个对象
使用 Block 时需要注意哪些问题?
- 在block内部使用外部指针且会造成循环引用情况下,需要用__weak修饰外部指针
__weak typeof(self) weakSelf = self; - 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下__strong typeof(self) strongSelf = weakSelf;
- 如果需要在block内部改变外部变量的话,需要在用__block修饰外部变量
为什么 UIScrollView 的滚动会导致 NSTimer 失效?
定时器里面有个runloop mode,一般定时器是运行在defaultmode上但是如果滑动了这个页面,主线程runloop会转到UITrackingRunLoopMode中,这时候就不能处理定时器了,造成定时器失效,原因就是runloop mode选错了,解决办法有2个,一个是更改mode为NSRunLoopCommonModes(无论runloop运行在哪个mode都能运行),还有种办法是切换到主线程来更新UI界面的刷新
为什么当 Core Animation 完成时,layer 又会恢复到原先的状态?
因为这些产生的动画只是假象,并没有对layer进行改变.图层树里的呈现树实际上是模型图层的复制,但是它的属性值表示了当前外观效果,动画的过程实际上只是修改了呈现树,并没有对图层的属性进行改变,所以在动画结束以后图层会恢复到原先状态
为什么Category只能为对象添加方法,却不能添加成员变量?
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
typedef struct objc_class *Class;
objc_class结构体的定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
在上面的objc_class结构体中,ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。
其实属性是可以添的但不能直接用(类别不会自动帮属性生成get/set方法,声明属性的时候没有生成_开头的成员变量。实际上,点语法是可以写的,只不过在运行时调用到这个方法时候会报方法找不到的错误)。只是说现在Xcode自动会给属性生成成员变量让大家对这个概念有点混淆。Property是Property,Ivar是Ivar。分类本身并不是一个真正的类,它并没有自己的ISA,分类只会将自己的method attach到主类,并不会影响到主类的IvarList。这就是为什么分类里面不能增加成员变量的原因。
什么是extension
extension被开发者称之为扩展、延展、匿名分类。extension看起来很像一个匿名的category,但是extension和category几乎完全是两个东西。和category不同的是extension不但可以声明方法,还可以声明属性、成员变量。extension一般用于声明私有方法,私有属性,私有成员变量。
category是拥有.h文件和.m文件的东西。但是extension不然。extension只存在于一个.h文件中,或者extension只能寄生于一个类的.m文件中。比如,viewController.m文件中通常寄生这么个东西,其实这就是一个extension:
@interface ViewController ()
@end
category和extension的区别
-
extension在编译期决议,它就是类的一部分,但是category则完全不一样,它是在运行期决议的。extension在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它、extension伴随类的产生而产生,亦随之一起消亡。
-
extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension,除非创建子类再添加extension。而category不需要有类的源码,我们可以给系统提供的类添加category。
-
extension可以添加实例变量,而category不可以。
-
extension和category都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现。
如何访问并修改一个类的私有属性?
- 有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性
- 创建一个Father类,声明一个私有属性name,并重写description打印name的值,在另外一个类中通过runtime来获取并修改Father中的属性
@interface Father ()
@property (nonatomic, copy) NSString *name;
@end
@implementation Father
- (NSString *)description
{
return [NSString stringWithFormat:@"name:%@",_name];
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Father *father = [Father new];
// count记录变量的数量IVar是runtime声明的一个宏
unsigned int count = 0;
// 获取类的所有属性变量
Ivar *menbers = class_copyIvarList([Father class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = menbers[i];
// 将IVar变量转化为字符串,这里获得了属性名
const char *memberName = ivar_getName(ivar);
NSLog(@"%s",memberName);
Ivar m_name = menbers[0];
// 修改属性值
object_setIvar(father, m_name, @"zhangsan");
// 打印后发现Father中name的值变为zhangsan
NSLog(@"%@",[father description]);
}
}
Objective-C 如何对已有的方法,添加自己的功能代码以实现类似记录日志这样的功能?
思路:runtime交换方法
先在分类中添加一个方法,注意不能重写系统方法,会覆盖
+ (NSString *)myLog
{
// 这里写打印行号,什么方法,哪个类调用等等
}
然后交换方法
// 加载分类到内存的时候调用
+ (void)load
{
// 获取imageWithName方法地址
Method description = class_getClassMethod(self, @selector(description));
// 获取imageWithName方法地址
Method myLog = class_getClassMethod(self, @selector(myLog));
// 交换方法地址,相当于交换实现方式
method_exchangeImplementations(description, myLog);
}
如何让 Category 支持属性?
- 使用runtime可以实现
@interface NSObject (test)
@property (nonatomic, copy) NSString *name;
@end
@implementation NSObject (test)
// 定义关联的key
static const char *key = "name";
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
如何把一个包含自定义对象的数组序列化到磁盘?
- 自定义对象只需要实现NSCoding协议即可
- (void)viewDidLoad
{
[super viewDidLoad];
User *user = [User new];
Account *account = [Account new];
NSArray *userArray = @[user, account];
// 存到磁盘
NSData * tempArchive = [NSKeyedArchiver archivedDataWithRootObject: userArray];
}
// 代理方法
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
self.user = [aDecoder decodeObjectForKey:@"user"];
self.account = [aDecoder decodeObjectForKey:@"account"];
}
return self;
}
// 代理方法
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.user forKey:@"user"];
[aCoder encodeObject:self.account forKey:@"account"];
}
UIScrollView 大概是如何实现的,它是如何捕捉、响应手势的?
- 我对UIScrollView的理解是frame就是他的contentSize,bounds就是他的可视范围,通过改变bounds从而达到让用户误以为在滚动,以下是一个简单的UIScrollView实现
@interface MyScrollView : UIView
@property (nonatomic) CGSize contentSize;
@end
@implementation MyScrollView
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self == nil) {
return nil;
}
// 添加一个滑动手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGesture:)];
[self addGestureRecognizer:pan];
return self;
}
- (void)panGesture:(UIPanGestureRecognizer *)gestureRecognizer{
// 改变bounds
CGPoint translation = [gestureRecognizer translationInView:self];
CGRect bounds = self.bounds;
CGFloat newBoundsOriginX = bounds.origin.x - translation.x;
CGFloat minBoundsOriginX = 0.0;
CGFloat maxBoundsOriginX = self.contentSize.width - bounds.size.width;
bounds.origin.x = fmax(minBoundsOriginX, fmin(newBoundsOriginX, maxBoundsOriginX));
CGFloat newBoundsOriginY = bounds.origin.y - translation.y;
CGFloat minBoundsOriginY = 0.0;
CGFloat maxBoundsOriginY = self.contentSize.height - bounds.size.height;
bounds.origin.y = fmax(minBoundsOriginY, fmin(newBoundsOriginY, maxBoundsOriginY));
self.bounds = bounds;
[gestureRecognizer setTranslation:CGPointZero inView:self];
}
- 解决手势冲突,对自己添加的手势进行捕获和响应
// 让UIScrollView遵守UIGestureRecognizerDelegate协议,实现这个方法,在这里方法里对添加的手势进行处理就可以解决冲突
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
当 TableView 的 Cell 改变时,如何让这些改变以动画的形式呈现?
这里举个例子,点击cell以后以动画形式改变cell高度
@interface ViewController ()
@property (nonatomic, strong) NSIndexPath *index;
@end
@implementation ViewController
static NSString *ID = @"cell";
- (void)viewDidLoad {
[super viewDidLoad];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
cell.textLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 20;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if(self.index == indexPath){
return 120;
}
return 60;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
self.index = indexPath;
[tableView deselectRowAtIndexPath:indexPath animated:TRUE];
// 重点是这2句代码实现的功能
[tableView beginUpdates];
[tableView endUpdates];
}
你会如何存储用户的一些敏感信息,如登录的 token
- 使用keychain来存储,也就是钥匙串,使用keychain需要导入Security框架
自定义一个keychain的类
#import <Security/Security.h>
@implementation YCKKeyChain
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
service, (__bridge_transfer id)kSecAttrService,
service, (__bridge_transfer id)kSecAttrAccount,
(__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible,
nil];
}
+ (void)save:(NSString *)service data:(id)data {
// 获得搜索字典
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
// 删除旧的
SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
// 添加新的对象到字符串
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData];
// 查询钥匙串
SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL);
}
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
// 配置搜索设置
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
[keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
return ret;
}
+ (void)delete:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
}
在别的类实现存储,加载,删除敏感信息方法
// 用来标识这个钥匙串
static NSString * const KEY_IN_KEYCHAIN = @"com.yck.app.allinfo";
// 用来标识密码
static NSString * const KEY_PASSWORD = @"com.yck.app.password";
+ (void)savePassWord:(NSString *)password
{
NSMutableDictionary *passwordDict = [NSMutableDictionary dictionary];
[passwordDict setObject:password forKey:KEY_PASSWORD];
[YCKKeyChain save:KEY_IN_KEYCHAIN data:passwordDict];
}
+ (id)readPassWord
{
NSMutableDictionary *passwordDict = (NSMutableDictionary *)[YCKKeyChain load:KEY_IN_KEYCHAIN];
return [passwordDict objectForKey:KEY_PASSWORD];
}
+ (void)deletePassWord
{
[YCKKeyChain delete:KEY_IN_KEYCHAIN];
}
猜想runloop内部是如何实现的?
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
或使用伪代码来展示下:
int main(int argc, char * argv[]) {
//程序一直运行状态
while (AppIsRunning) {
//睡眠状态,等待唤醒事件
id whoWakesMe = SleepForWakingUp();
//得到唤醒事件
id event = GetEvent(whoWakesMe);
//开始处理事件
HandleEvent(event);
}
return 0;
}
请将字符串“2015-04-10”格式化日期转为NSDate类型
NSString *timeStr = @"2015-04-10";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
formatter.timeZone = [NSTimeZone defaultTimeZone];
NSDate *date = [formatter dateFromString:timeStr]; // 2015-04-09 16:00:00 +0000 NSLog(@"%@", date);
如何让自己所写的对象具有拷贝功能?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying与 NSMutableCopying协议。
具体步骤:
需声明该类遵从 NSCopying 协议
实现 NSCopying 协议。该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
什么是谓词?
谓词是通过NSPredicate,是通过给定的逻辑条件作为约束条件,完成对数据的筛选。
predicate = [NSPredicate predicateWithFormat:@"customerID == %d",n];
a = [customers filteredArrayUsingPredicate:predicate];
AirPlay是如何运行的?
AirPlay 是苹果开发的一种无线技术,可以通过WiFi将iPhone 、iPad、iPod touch 等iOS 设备上的包括图片、音频、视频通过无线的方式传输到支持AirPlay 设备。
Apple Pay 是什么?它的大概工作流程是怎样的?
Apple Pay是一种移动支付技术,它能够让用户以一种便捷安全的方式为现实世界中购买的商品和服务付款。
大概的流程:
- 在Xcode和苹果开发者会员中心中配置Apple Pay
- 用户授权支付请求
- 服务器处理支付请求
网友评论