ios 笔记

1.单例的写法

  • (instancetype)sharedInstance {
    static id sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
    });

    return sharedInstance;
    }
    2.规范写法:
    @property (weak, nonatomic) id<SGOAnalyticsDelegate> analyticsDelegate;

@property (assign, nonatomic) NSInteger statusCode;
@synthesize statusCode = _statusCode;

3.GCD 延时操作
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
dispatch_after(timer, dispatch_get_main_queue(), ^{

});

4.设置父视图的透明度,添加的子视图不显示解决方法 设置父视图的颜色:
_bottomView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5];

5.计算时间
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[finance.buildDate longLongValue] / 1000];

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString *strDate = [dateFormatter stringFromDate:date];

6、NSString为何要用copy?而不是strong?

我这样说你就明白了 A->B A中的一个MutableString给B中的一个Property(NSString类型)赋值 首先是能接受的,父类可以接受子类,如果是retain,仅仅是生成一个指针,计数器加一,然后指向那个MutableString。如果MString改变,B中那个跟着改变,因为是同一块内存区域。而选择Copy相当于又生成了一个NSString,与A中的MutableString独立。

NSArray 和 NADictionary同理

7、对象回收时Weak指针自动被置为nil的实现原理
Runtime维护了一个Weak表,用于存储指向某个对象的所有Weak指针。Weak表其实是一个哈希表,Key是所指对象的地址,Value是Weak指针的地址(这个地址的值是所指对象的地址)的数组。
在对象被回收的时候,经过层层调用,会最终触发下面的方法将所有Weak指针的值设为nil。

8、响应者链
事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。
通过hitTest:withEvent:层层找到最佳响应的视图,然后通过touchesBegan:withEvent:响应事件,如果不能响应再找父视图然后从下到上 直到UIApplication如果不能响应则废弃该事件。
http://www.jianshu.com/p/c294d1bd963d
传递过程详解:
keyWindow会在它的内容视图上调用hitTest:withEvent:(该方法返回的就是处理此触摸事件的最合适view)来完成这个找寻过程。
hitTest:withEvent:在内部首先会判断该视图是否能响应触摸事件,如果不能响应,返回nil,表示该视图不响应此触摸事件。然后再调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内)。如果pointInside:withEvent:返回NO,那么hiteTest:withEvent:也直接返回nil。
如果pointInside:withEvent:返回YES,则向当前视图的所有子视图发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历。直到有子视图返回非空对象或者全部子视图遍历完毕;若第一次有子视图返回非空对象,则 hitTest:withEvent:方法返回此对象,处理结束;如所有子视图都返回非,则hitTest:withEvent:方法返回该视图自身。
hitTest:底层实现
// point是该视图的坐标系上的点

  • (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
    // 1.判断自己能否接收触摸事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2.判断触摸点在不在自己范围内
    if (![self pointInside:point withEvent:event]) return nil;
    // 3.从后往前遍历自己的子控件,看是否有子控件更适合响应此事件
    int count = self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
    UIView *childView = self.subviews[i];
    CGPoint childPoint = [self convertPoint:point toView:childView];
    UIView *fitView = [childView hitTest:childPoint withEvent:event];
    if (fitView) {
    return fitView;
    }
    }
    // 没有找到比自己更合适的view
    return self;
    }
    http://www.jianshu.com/p/2f664e71c527
    http://www.cocoachina.com/ios/20160630/16868.html

9、结合响应者链扩大button的点击范围 重写pointInside: withEvent:方法 给button添加一个范围

import <UIKit/UIKit.h>

@interface UIButton (test)
@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
@end

import "UIButton+test.h"

import <objc/runtime.h>

@implementation UIButton (test)
@dynamic hitTestEdgeInsets;

static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
if(value) {
UIEdgeInsets edgeInsets;
[value getValue:&edgeInsets];
return edgeInsets;
}else {
return UIEdgeInsetsZero;
}
}

  • (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
    return [super pointInside:point withEvent:event];
    }
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    //判断这个点是否在这个范围内
    return CGRectContainsPoint(hitFrame, point);
    }
    @end

10、面试题
http://www.cocoachina.com/programmer/20151019/13746.html

11、观察者模式 ()
http://www.jb51.net/article/76122.htm
http://www.cnblogs.com/goodboy-heyang/p/5265675.html
kvo: 一对多, 观察者模式, 是键值监听,键值观察机制, KVO的本质是当观察者为一个对象的属性进行了注册,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。所以 isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名
当你观察一个对象时,会动态的创建该对象类的子类,这个子类重写了被观察属性的 setter 方法,同时将该对象的 isa 指针指向了新创建的子类。在 Objective-C 中对象是通过 isa 指针来查找对应类中的方法列表的,所以这里可以把该对象看为新子类的实例对象。重写的 setter 方法会在调用原 setter 方法之后,通知观察者对象属性值的更改。
addObserver:forKeyPath:options:context:
observeValueForKeyPath:ofObject:change:context

当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
派生类在被重写的 setter 方法中实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
//自定义kvo
@implementation NSObject (WSG)

  • (void)wsg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{

    //1、自定义子类
    //2、给子类重写setName方法 调用父类的setName方法,通知观察者
    //3、修改当前对象的isa指针,指向自定义的子类
    //Person类
    NSString *oldClass = NSStringFromClass([self class]);
    //新类
    NSString *newClass = [@"wsgKVO_" stringByAppendingString:oldClass];
    //创建的子类对象
    Class myClass = objc_allocateClassPair([self class], [newClass UTF8String], 0);
    //注册类
    objc_registerClassPair(myClass);

    //给类添加setName方法
    class_addMethod(myClass, @selector(setName:), (IMP)setName, "v@:@");

    //修改isa指针 指向新建的子类
    object_setClass(self, myClass);

    //保存观察者对象
    objc_setAssociatedObject(self, @"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //保存keypath
    objc_setAssociatedObject(self, @"wsgkeypath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

//调用父类的setName方法 通知观察者 observer
void setName(id self,SEL _cmd,NSString *newName){
//调用父类的set方法 变量是一个结构体 比较负责
//objc_msgSendSuper()

//改用 修改self的isa指针指向父类 调用setName方法  然后通知观察者 值发生了改变  然后再改回self的isa指针指向 子类
id class = [self class];
//修改self的isa指向 父类
object_setClass(self, class_getSuperclass(class));
//调用父类的setName方法 /* id self, SEL op, ... */ 要编译通过  要去build-setting 搜索objc_msg 设置enable为NO
objc_msgSend(self,@selector(setName:),newName);

//通知observer 值发生了改变
id objc = objc_getAssociatedObject(self, @"objc");
id keypath = objc_getAssociatedObject(self, @"wsgkeypath");
objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),self,keypath,nil,nil);

//把self的isa指针改为指向子类类型
object_setClass(self, class);

}
@end

KVC(Key-Value-Coding)内部的实现:是键值编码, 一个对象在调用setValue的时候,(1)首先根据方法名找到运行方法的时候所需要的环 境参数。(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。(3)再直接查找得来的具体的方法实现。

Delegate: 通常发送者和接收者的关系是直接的一对一的关系。代理的目的是改变或传递控制链。允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以减少框架复杂度。消息的发送者(sender)告知接收者(receiver)某个事件将要发生,delegate同意然然后发送者响应事件,delegate机制使得接收者可以改变发送者的行为。

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

1). 效率肯定是delegate比NSNotification高。

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

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

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

12、delegate为什么用weak
@protocol LYSunDelegate <NSObject>
@end

@interface LYSun : NSObject
@property (nonatomic, weak) id<LYSunDelegate>delegate;
@end

import "LYPerson.h"

import "LYSun.h"

@interface LYPerson()<LYSunDelegate>
/** 强引用dog*/
@property (nonatomic, strong) LYSun *sun;
@end

@implementation LYPerson

  • (instancetype)init
    {
    self = [super init];
    if (self) {
    // 实例化dog
    self.sun = [[LYSun alloc] init];
    // sun的delegate引用self,self的retainCount,取决于delegate修饰,weak:retainCount不变,strong:retainCount + 1
    self.sun.delegate = self;
    }
    return self;
    }
  • (void)dealloc
    {
    NSLog(@"LYPerson----销毁");
    }
    @end

// 实例化person, self对person弱引用,person的retainCount不变
LYPerson *person = [[LYPerson alloc] init];
当delegate 用strong时 :当viewController不对person引用后想释放person的时候,发现这时sun.delegate对person还强引用着呢,person的retainCount为1,所以person不会释放,sun固然也不会释放,这就是造成循环引用导致的内存泄漏的原因
当delegate用weak时:当viewController 不对person引用之后,person的retainCount 为 0 ,当然可以释放啦,那么person就被释放了,sun也就被释放啦

13、retain、copy、assign对于get、set方法的影响
//assain修饰的属性 生成的set get方法
-(void)setAge:(NSInteger)age
{
_age =age;
}
-(NSInteger )age
{
return _age;
}
//retain 修饰的属性
-(void)setArray:(NSArray *)array
{
if (_array !=array)
{
[_array release];
_array =[array retain];
}
}

-(NSArray*)array{
return [[_array retain]autorelease];
}
//copy修饰的属性
-(void)setName:(NSString *)name
{
if (_name !=name) {
[_name release];
_name = [name copy];
}
}
-(NSString *)name
{
return [[_name retain]autorelease];
}
14:SEL
http://www.imlifengfeng.com/blog/?p=398
又叫选择器,是表示一个方法的selector的指针
本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度
IMP 是方法的实现 SEL 和IMP是一一对应的

15:tableview计算cell高度
1:专门的layout 来计算各个控件的位置:例如YYKit里https://github.com/ibireme/YYKit
在子线程里计算好之后 在主线程刷新 提高刷新效率。
2、autolayout自动适应高度;
3、全代码在cell里的layoutSubviews里代码计算高度。
http://www.imlifengfeng.com/blog/?p=501
tableview优化:

16、coretext绘制文本


coretext_arch.png

core_text_arch_2x.png

CTFramesetter是由CFAttributedString(NSAttributedString)初始化而来,可以认为它是CTFrame的一个Factory,通过传入CGPath生成相应的CTFrame并使用它进行渲染:直接以CTFrame为参数使用CTFrameDraw绘制或者从CTFrame中获取CTLine进行微调后使用CTLineDraw进行绘制。

一个CTFrame是由一行一行的CLine组成,每个CTLine又会包含若干个CTRun(既字形绘制的最小单元),通过相应的方法可以获取到不同位置的CTRun和CTLine,以实现对不同位置touch事件的响应。

http://blog.devtang.com/2015/06/27/using-coretext-1/
http://blog.devtang.com/2015/06/27/using-coretext-2/
http://blog.csdn.net/hopedark/article/details/50174157

17、runloop
runloop只能在一个mode下 执行完一个mode 会休眠 等待下一个。
NSRunLoopCommonModes:占位模式 UI&默认
UITrackingRunLoopMode:UI模式
NSDefaultRunLoopMode:默认模式
每个mode都对应着 source、timer、observer
source:
source0:非系统内核
source1:系统内核

1202010-b85e86926d49b75b.png

https://blog.ibireme.com/2015/05/18/runloop/
https://mp.weixin.qq.com/s/O00rVlHgJZ62Nf3QXtp5uA
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(lickedButtonAtIndex) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
runloop:在APP的应用:
1、NSTimer、CADisplayLink、NSObject(NSDelaydPerforming)
三个都是对CFRunLoopTimer的封装
2、UIEvent
3、autorelease
5、NSObject(NSThreadPerformAddtion)
7、CATransition
8、CAAnimation
9、dispatch_get_main_queue()
10、AFNetworking(NSURLConnection)

18、GCD 多线程
队列:
并行队列(Concurrent Dispatch Queue):可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务)并行功能只有在异步(dispatch_async)函数下才有效
串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
任务:
同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力

//并行队列
dispatch_queue_t queue = dispatch_queue_create("com.sunny.test", DISPATCH_QUEUE_CONCURRENT);
//串行队列
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_SERIAL);
//全局并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//全局队列的优先级
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND

创建任务:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});


屏幕快照 2017-11-02 下午3.30.39.png

主队列+同步执行 会阻塞主线程 导致死锁

// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2-------%@",[NSThread currentThread]);
});

//延时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
NSLog(@"run-----");
});

//只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});

dispatch_barrier_async
需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
});

执行完上面两个任务之后 再执行下面两个
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
});

队列组
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});

//信号量
dispatch_semaphore
//创建信号量
dispatch_semaphore_create
//发送信号量
dispatch_semaphore_signal
//等待信号量
dispatch_semaphore_wait

//创建一个并行队列
dispatch_queue_t queque = dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
//异步执行
dispatch_async(queque, ^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self getToken:semaphore];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self request];
});
收到信号量之后 才执行接下来的任务
在getToken方法里有发送信号量方法:dispatch_semaphore_signal(semaphore);
然后会往下执行request方法
//计时 比NSTImer更精准
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1000000000, 0);
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"---------%@",[NSThread currentThread]);
});
dispatch_resume(self.timer);

19、NSString内存分配
方法1.直接赋值: NSString *testStr1 = @"a";
方法2.类函数初始化生成:
NSString *testStr2 = [NSString stringWithString:@"b"];
NSString *testStr3 = [NSString stringWithFormat:@"c"];
方法3.实例方法初始化生成: 
NSString *testStr4 = [[NSString alloc] initWithString:@"d"];
NSString *testStr5 = [[NSString alloc] initWithFormat:@"e"];
test1,test2,test4都是在一个内存区域,也就是上文所说的常量内存区。test3,test5在一个内存区,也就是堆区
test1,test2,test4这三种的话建议用=@“字符串”来使用,因为本来就是一样的。test3,test5这两种的话,建议用texst3这种

20、dsYM文件 分析
xcode 打包 archive 之后 会生成文件. xcarchive 路径(~/Library/Developer/Xcode/Archives)
每一个xx.app 和 xx.app.dSYM都有对应的UUID,crash文件也有自己的UUID,只要这三个文件的UUID一致,通过命令行解析出错误的函数地址,
查看xx.app.dSYM 的UUID
dwarfdump --uuid xx.app.dSYM
查看crash地址对应的函数
atos -arch arm64 -o Zeus -l 0x10063000 0x00000001009795d4

21、HMAC加密 替代MD5加密 MD5加密的加盐加密 在APP中存在危险 如果盐泄露 将导致整个加密流程收到威胁 修改盐 影响所有用户。
// HMACMD5加密方法 每个用户有一个key 从服务端获取 可以更新key

  • (NSString *)hmacMD5StringWithKey:(NSString *)key {
    const char *keyData = key.UTF8String;
    const char *strData = self.UTF8String;
    uint8_t buffer[CC_MD5_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer);
    return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
    }
    HMAC加密之后 再把密码+201711302229 md5加密 到服务器校验 服务器也是把HMAC密码加上时间(到分) 跟APP传过来的进行匹配 。

22、成员变量 属性
@interface Person : NSObject{
@public
NSString *_name;//成员变量
}
@property (nonatomic,copy) NSString *age;//属性
@end
Person *p = [[Person alloc] init];
p->_name = @"sunny";//实例变量赋值采用箭头 kvo观察不到值得变化
p.age = @"18";//实例变量赋值采用set方法 kvo可以观察到值得变化

23、递归没有return 会栈溢出 调用函数 就会分配栈空间 没有return 会一直分配栈空间 。

24、2、在iOS 11上运行tableView向下偏移64px或者20px,因为iOS 11废弃了automaticallyAdjustsScrollViewInsets,而是给UIScrollView增加了contentInsetAdjustmentBehavior属性。避免这个坑的方法是要判断
if (@available(iOS 11.0, *)) {
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}else {
self.automaticallyAdjustsScrollViewInsets = NO;
}

25、设置导航栏为透明
self.navigationController.navigationBar.translucent = YES;
self.navigationController.navigationBar.barTintColor = clear_color;
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setShadowImage:[UIImage new]];

26、设置导航栏为不透明
self.navigationController.navigationBar.translucent = NO;
self.navigationController.navigationBar.barTintColor = RGBCOLOR(64, 98, 153);
[self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setShadowImage:nil];

27、CFBundleURLType:当前APP的scheme记录, 通过schema可在其它App中打开当前App
LSApplicationQueriesSchemes是从iOS9时被引入的。
用意:当前APP允许访问的APP有哪些,即白名单,需要通信双方 均设置为对方的scheme,否则当调用对方App时,系统会告诉你This app is not allowed to query for scheme。
调用者和被调用者均需要设置白名单,一方想调用,另一方需要也知道将被你调用 ,更为安全

28、Block https://halfrost.com/ios_block/?utm_source=tuicool
带有自动变量(局部变量)的匿名函数
Block捕获外部变量
静态全局变量、全局变量:作用域在全局可以访问
静态变量:静态变量传递给Block是内存地址值,所以能在Block里面直接改变值
局部变量:在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中,Block仅仅捕获了值,并没有捕获内存地址

Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)

带有 __block的变量也被转化成了一个结构体__Block_byref_i_0
ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。只不过带有__block修饰符的变量会被捕获到Block内部持有

1.手动调用copy
2.Block是函数的返回值
3.Block被强引用,Block被赋值给__strong或者id类型
4.调用系统API入参中含有usingBlcok的方法
系统都会默认调用copy方法把Block赋复制

29、+load 在把类加载到内存的时候运行的方法
load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。

30、消息转发机制
1、方法调用[receiver message]
2、编译之后objc_msgSend(receiver, selector)
3、通过receiver的isa指针找到类对象,首先从类对象的缓存中去找方法找到则执行,如果没有找到则去方法列表查找,如果没有找到则去父类的方法缓存列表查找

方法的结构体实现:
struct objc_method {
SEL method_name 方法名 OBJC2_UNAVAILABLE;
char *method_types 方法的参数 OBJC2_UNAVAILABLE;
IMP method_imp 方法的实现 OBJC2_UNAVAILABLE;
}
IMP的定义

if !OBJC_OLD_DISPATCH_PROTOTYPES

typedef void (IMP)(void / id, SEL, ... */ );

else

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

endif

类方法:保存在元类里,类对象的isa指向元类

如果找不到实例方法:在Objective中,对一个对象发送一个它没有实现的Selector是完全合法的,这样做可以隐藏某一个消息背后实现,也可以模拟多继承(OC不支持多继承)。这个机制就是动态转发机制

+resolveInstanceMethod:

  • forwardingTargetForSelector:
  • forwardInvocation:

动态方法的机制第一步,类自己处理
使用resolveInstanceMethod
动态为实例方法提供一个实现
这个方法在Objective C消息转发机制之前被调用。如果 respondsToSelector或者instancesRespondToSelector: 被调用,可以为改Selector提供动态的实现者。

第二步(第一步不能处理的情况下),调用forwardingTargetForSelector来简单的把执行任务转发给另一个对象,到这里,还是廉价调用
-(id)forwardingTargetForSelector:(SEL)aSelector{

pragma clang diagnostic push

pragma clang diagnostic ignored "-Wundeclared-selector"

if (aSelector == @selector(dynamicSelector) && [对象 respondsToSelector:@selector(dynamicSelector)]) {
    return self.myObj;
}else{
    return [super forwardingTargetForSelector:aSelector];
}

pragma clang diagnostic pop

}
第三步,当前两步都不能处理的时候,调用forwardInvocation转发给别人,返回值仍然返回给最初的Selector
-(void)forwardInvocation:(NSInvocation *)anInvocation{
if ([self.myObj respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:对象];
}else{
[super forwardInvocation:anInvocation];
}
}

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [CustomObject instanceMethodSignatureForSelector:aSelector];
    }

31、weak、assign 都不会使引用计数+1
assign通常修饰基本数据类型,weak修饰对象,会在对象释放后置为nil,但assign不会,

32、有关于对于AFN的二次封装
可以取消单个任务(存储每个任务的taskIdentifier,然后根据这个删除任务)
首先做一个单例做各种请求,单例有一个AFURLSessionManager或者AFHTTPSessionManager,来控制任务的执行,每执行一个任务 把任务放到一个字典里 任务的taskIdentifier作为key,任务为value
然后做一个请求类:配置封装请求参数 调用单例请求数据

33、静态库、动态库
静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存
iOS里静态库形式
.a和.framework
iOS里动态库形式
.dylib和.framework

系统的.framework是动态库,我们自己建立的.framework是静态库

为什么要使用静态库?
1 方便共享代码,便于合理使用。
2 实现iOS程序的模块化。可以把固定的业务模块化成静态库。
3 和别人分享你的代码库,但不想让别人看到你代码的实现。
4 开发第三方sdk的需要。

34、iOS 防止 Charles 抓取数据
1、通过 HTTP/1.1 及以上版本的 CONNECT 请求方式
2、使用自签名证书的应用和双向验证的应用:其一,客户端通过指定的方式只信任某一个证书;其二,一般做法只有客户端验证服务端公钥证书是不是合法,但是某些 app,比如支付宝,采用双向验证的方式,在通信过程中,服务器会验证 app 的公钥证书,这时候,就没办法使用 Charles(中间人攻击的方式)进行抓包。
3、对返回数据进行加密(RAS保密 + token验证 & 效率更高的AES) 。
4、判断客户端当前是否设置了代理。这也是本人通过 NSURLProtocol 拦截请求后,判断是否设置了代理,实现了防止 Charles 抓取 APP 的数据

  • (BOOL)getProxyStatus {
    NSDictionary *proxySettings = (__bridge NSDictionary *)(CFNetworkCopySystemProxySettings());
    NSArray *proxies = (__bridge NSArray *)(CFNetworkCopyProxiesForURL((__bridge CFURLRef _Nonnull)([NSURL URLWithString:@"http://www.baidu.com"]), (__bridge CFDictionaryRef _Nonnull)(proxySettings)));
    NSDictionary *settings = [proxies objectAtIndex:0];

    NSLog(@"host=%@", [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
    NSLog(@"port=%@", [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
    NSLog(@"type=%@", [settings objectForKey:(NSString *)kCFProxyTypeKey]);

    if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"]){
    //没有设置代理
    return NO;
    }else{
    //设置代理了
    return YES;
    }
    }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270