朝元的博客


  • 首页

  • 归档

iOS 蓝牙技术分享

发表于 2019-07-12

0x01 - 概览

蓝牙技术最初由爱立信创制。技术始于爱立信公司的1994方案,它是研究在移动电话和其他配件间进行低功耗、低成本无线通信连接的方法。发明者希望为设备间的通讯创造一组统一规则(标准化协议),以解决用户间互不兼容的移动电子设备。从早期的1.0到目前最新的5.0版本,中间经过了十几年的发展。我们这里着重关注的是蓝牙4.0以及4.0之后的规范,蓝牙4.0于2010年7月7号推出,4.0之前的蓝牙统称为经典蓝牙,4.0之后又加入了低功耗功能并且有效的传输距离拓展到了最大的60米。

阅读全文 »

iOS开发之玩转蓝牙CoreBluetooth

发表于 2019-07-01

关键概念

谈到蓝牙,很容易让人联想到蓝牙穿戴设备,好像听起来更靠近硬件层一些。苹果其实对iOS和OSX上的蓝牙已做了一层很好的封装,看过CoreBluetooth Framework的大致API之后,基本上就将其流程明白个大概。难点在于理解其工作模式和理清一些关键概念,比如Peripehral, Central, Service, characteristics等等,不要被这些陌生的单词吓到,网络协议的应用大多脱不了CS的架构模型,这里和大家一起对照传统的Client/Server架构来梳理下iOS和OSX上CoreBluetooth的重要知识点。我画了一张图,方便大家一目了然的明白CoreBluetooth的工作原理。

阅读全文 »

Core Bluetooth - Background Procesing

发表于 2019-06-27

0x00 - 前言

对于蓝牙来说,如果不设置相关的后台运行许可,那么在程序退到后台的时候是系统是不允许做蓝牙的相关操作的。

阅读全文 »

Core-Bluetooth(2)

发表于 2019-06-27

这一节讲讲外接设备(Peripheral),主要是关于CBPeripheralManager这个类的使用。

阅读全文 »

Core Bluetooth

发表于 2019-06-27

0x00 - overview

常见名称和缩写

MFI: make for ipad ,iphone, itouch 专们为苹果设备制作的设备

BLE: buletouch low energy,蓝牙4.0设备因为低耗电,所以也叫做BLE

阅读全文 »

iOS 插入数据到 TableView 顶部

发表于 2018-08-26

困扰了几天的问题终于得到解决了,感谢 github,感谢开源~

在这里记录下遇到的问题,算是记录也避免他人少走一些弯路。

阅读全文 »

设置UITableViewCell之间的间距(推荐第四种)

发表于 2018-05-24

1.设置假的间距,我们在tableviewcell的contentView上添加一个view,比如让其距离上下左右的距离都是10;这个方法是最容易想到的;

阅读全文 »

面试知识点整理

发表于 2018-04-17

Part 0

看以下习题前可以先看看下面的别人整理好的面试题:

iOSInterviewQuestions【强烈推荐看】


Part 1

习题来自:iOS-InterviewQuestion-collection

0x01 内存管理

@autoreleasrPool 的释放时机?

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

参考:

幕背后的Autorelease

Autorelease对象的释放时机

arc 内存管理要点


自动引用计(ARC)数应该遵循的原则?

不能使用 retain/release/retainCount/autorelease

不能使用 NSAllocateObject/NSDeallocateObject

须遵守内存管理的方法命名规则

不要显示调用dealloc

使用 @autorelease 块代替NSAutoreleasePool

不能使用区域(NSZone)

对象型变量不能作为C语言结固体的成员

显示转换“id”和”void *”

参考:

OC内存管理教程之ARC(二)——自动引用计数规则


访问__weak修饰的变量,是否已经被注册在了 @autoreleasePool 中?为什么?

在访问 __weak 修饰的变量时,实际上必定会访问注册到 Autorelease Pool 的对象。如下来年两段代码是相同的效果:

1
2
3
4
5
6
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);

为什么会这样呢?因为 __weak 修饰符只持有对象的弱引用,而在访问对象的过程中,该对象有可能被废弃,如果把被访问的对象注册到 Autorelease Pool 中,就能保证 Autorelease Pool 被销毁前对象是存在的。

参考:

arc 内存管理要点


ARC 的 retainCount 怎么存储的?

the reference counts for most objects are stored in hash tables. Have a look at the _objc_rootRetain function in runtime/objc-arr.mm

参考:

Where is the retain count stored for NSObjects in Objective C


简要说一下 @autoreleasePool 的数据结构

1
2
3
4
5
6
7
8
9
AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}

magic用来校验AutoreleasePoolPage结构是否完整;

next指向第一个可用的地址;

thread指向当前的线程;

parent指向父类

child指向子类

参考:

深入理解@autoreleasepool


__weak 和 _Unsafe_Unretain 的区别

weak与unsafe_unretained的区别在于,weak会将被释放指针赋值为nil,而unsafe_unretained则会成为野指针。


为什么已经有了 ARC ,但还是需要 @AutoreleasePool 的存在?

ARC 并不是舍弃了 @autoreleasepool,而是在编译阶段帮你插入必要的 retain/release/autorelease 的代码调用。

所以,跟你想象的不一样,ARC 之下依然是延时释放的,依然是依赖于 NSAutoreleasePool,跟非 ARC 模式下手动调用那些函数本质上毫无差别,只是编译器来做会保证引用计数的正确性。


__weak 属性修饰的变量,如何实现在变量没有强引用后自动置为 nil

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。


说一下对 retain,copy,assign,weak,_Unsafe_Unretain 关键字的理解

assign表明 setter 仅仅是一个简单的赋值操作,通常用于基本的数值类型,例如CGFloat和NSInteger。

weak表明属性定义了一个非拥有者关系。当给属性设定一个新值的时候,这个值不会进行 retain,旧值也不会进行 release, 而是进行类似 assign 的操作。不过当属性指向的对象被销毁时,该属性会被置为nil。

strong表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行 retain ,旧值进行 release ,然后进行赋值操作。

copy类似于 strong,不过在赋值时进行 copy 操作而不是 retain 操作。通常在需要保留某个不可变对象(NSString最常见),并且防止它被意外改变时使用。

unsafe_unretained的语义和 assign 类似,不过是用于对象类型的,表示一个非拥有(unretained)的,同时也不会在对象被销毁时置为nil的(unsafe)关系。

参考:

Objective-C 内存管理——你需要知道的一切

NSString属性什么时候用copy,什么时候用strong?


ARC 在编译时做了哪些工作

根据代码执行的上下文语境,在适当的位置插入 retain,release


ARC 在运行时做了哪些工作

待查~


函数返回一个对象时,会对对象 autorelease 么?为什么?

参考 arc 内存管理要点 这篇。


说一下什么是 悬垂指针?什么是 野指针?

悬垂指针(dangling pointer)一般是说指向已经被释放的自由区内存(free store)的指针,野指针(wild pointer)则一般是未经初始化的指针。前者曾经有效过,后者从未有效过。

参考:

悬垂指针(Dangling pointer)和野指针(Wild pointer)


内存管理默认的关键字是什么?

strong


内存中的5大区分别是什么?

内存5大区:堆,栈,方法区,全局区,常量区
栈:不需要手动管理内存,会自动清理栈中的内存
堆: 需要手动管理内存
静态区:又称全局区
常量区: 储存常量的地方
方法区: 存放函数体的二进制代码


是否了解 深拷贝 和 浅拷贝 的概念,集合类深拷贝如何实现?

深拷贝是内存拷贝,指向的是不同的内存
浅拷贝是指针拷贝,指向的是同一块内存

集合类深拷贝是通过归档、解档实现。


BAD_ACCESS 在什么情况下出现?

  • 访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。
  • 死循环

0x02 Runtime

类对象的数据结构?

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:

1
typedef struct objc_class *Class;

查看objc/runtime.h中objc_class结构体的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;

在这个定义中,下面几个字段是我们感兴趣的

  1. isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),我们会在后面介绍它。
  2. super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。
  3. cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
  4. version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。

参考:

Objective-C Runtime 运行时之一:类与对象


实例对象的数据结构?

实例对象都是一个id类型的对象,查看objc.h中对id的描述:

1
2
3
4
5
6
7
8
9
10
11
12
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

查看源文件,可以看出id其实就是一个指向objc_object结构体指针,它包含一个Class isa 成员。所以实例对象的数据结构实际上就是一个Class的数据结构。


元类对象的数据结构?

meta-class 是 Class 对象的类。每个 Class 都有个不同的自己的 meta-class(因此每个 Class 都可以有一个自己不同的方法列表)。也就是说每个类的 Class 不完全相同。

参考:

Objective-C 中的 Meta-class 是什么?


Category 的实现原理?以及如何实现添加一个属性?

参考:

从源码解读Category实现原理

深入理解Objective-C:Category


如何运用 Runtime 字典转模型?

参考:

利用运行时(runtime)字典转模型

简单的版本如上,其他还有一些关键字的不能作为key值得还要映射一下的。还有嵌套的等等其他的要考虑。


Category 有哪些用途?

1)无需创建继承类,实现对已有类扩展。并且可以被扩展的类的所有子类自动继承。

2)可以用来修复没有源码类的bug。

3)对于一个类多个开发人员维护的情况,可以根据不同用途创建不同分类。

注意点:

1)不能在分类中重写系统方法,因为会把系统的功能给覆盖掉,而且分类中不能调用super。但是,这种情况可以用来修复,没有源码的类中方法有Bug的情况。


Category 和 Extension 有什么区别?

1)Category 的加载在运行时,Extension 的加载在编译时。

2) Extension 不能给没有源码的类添加方法。

3) Extension 是一个匿名的 Category 。


说一下 Method Swizzling? 说一下在实际开发中你在什么场景下使用过?

参考:

Method Swizzling

关于Method Swizzling踩过的坑,

参考这里

Method Swizzling


如何实现动态添加方法和属性?

1
2
3
4
5
6
7
8
9
10
11
//动态创建类
Class person = objc_allocateClassPair([NSObject class], "Person", 0);
//添加变量
class_addIvar(person, "name", sizeof(NSString *), 0, "@");
//添加函数 sayHi
class_addMethod(person, @selector(sayHi:), (IMP)sayHi, "v@:");
objc_registerClassPair(person);
//初始化一个person对象
id Tom = [[person alloc] init];
[Tom setValue:@"Tom" forKey:@"name"];
[Tom sayHi:@"Jeck"];

上述代码,我们通过objc_allocateClassPair()创建了一个继承NSObject的person的子类,然后通过objc_registerClassPair()这个函数注册了person类,下面我们就可以使用这个类了,使用之前我们在给person类中添加一个name属性和sayHi:方法,分别通过class_addIvar()和class_addMethod()来添加,接下来我们要实现我们添加的sayHi:方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//这个函数必须写,要不然xcode会报错,实际运行的时候,这个函数是不会调用的
-(void)sayHi:(NSString *)name
{

}

//运行的时候,会调用这个方法
static void sayHi(id self, SEL _cmd, NSString *name)
{
Ivar n = class_getInstanceVariable([self class], "name");
id a = object_getIvar(self, n);
NSLog(@"hello %@,my name is %@",name,a);
}

static void sayHi(id self, SEL _cmd, NSString *name)这个就是我们运行时候调用的方法,其中self使我们使用person创建的对象Tom,_cmd是调用的方法名,name就是传过来的参数,如果有多个参数,可以写成static void sayHi(id self, SEL _cmd, NSString *name,...)省略号可以填写你愿意添加的参数。

这样我们就利用runtime动态的创建了一个person类,包括了name属性和sayHi:方法,运行如结果如下:

1
RunTimeDemo[46329:7242266] hello Jeck,my name is Tom

参考:

runtime常用方法


说一下对 isa 指针的理解, 对象的isa 指针指向哪里?(注意区分不同对象)

网上有2种说法:

1)isa 是指向元类的指针,不了解元类的可以看 Objective-C 中的 Meta-class 是什么?

2) 任何直接或间接继承了NSObject的类,它的实例对象(instacne objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。

参考:

Objective-C内存布局

Objective-C 中的 Meta-class 是什么?


Obj-C 中的类信息存放在哪里?

类方法存储在元类。

当前类的信息都在 data 里。看看源码就知道了。


一个 NSObject 对象占用多少内存空间?

通过 size_t class_getInstanceSize ( Class cls )接口可以获取NSObject 对象占用内存空间大小。

1
2
3
4
5
6
7
size_t z = class_getInstanceSize([NSObject class]);

//输出
(lldb) p z
(size_t) $0 = 8

单位是 byte

说一下对 class_rw_t 的理解?

rw代表可读可写。

ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct class_rw_t {  
uint32_t flags;
uint32_t version;

const class_ro_t *ro;

method_array_t methods;
property_array_t properties;
protocol_array_t protocols;

Class firstSubclass;
Class nextSiblingClass;
};

其中还有一个指向常量的指针 ro,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议

参考:

深入解析 Objective-C 中方法的结构


说一下对 class_ro_t 的理解?

储了当前类在编译期就已经确定的属性、方法以及遵循的协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct class_ro_t {  
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;

const uint8_t * ivarLayout;

const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;

const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};

参考:

深入解析 Objective-C 中方法的结构


说一下 Runtime 消息解析和转发

先看一下流程图:

阅读全文 »

工作笔记

发表于 2018-03-28

1、根据 text 和字体大小返回 size

1
CGSize topTilteSize = [KGCommonTools sizeForString:topTilte.text font:topTilte.font];

2、ceil(xx)函数

函数名: ceil

用 法: double ceil(double x);

功 能: 返回大于或者等于指定表达式的最小整数

头文件:math.h


3、UIButton 的一个分类,扩大 button 本身的点击响应范围

1
2
3
4
5
6
7
8
9
10
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@interface UIButton (EnlargeEdge)

- (void)setEnlargeEdge:(CGFloat)size;

- (void)setEnlargeEdgeWithTop:(CGFloat)top bottom:(CGFloat)bottom left:(CGFloat)left right:(CGFloat)right;

@end
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
56
57
58
59
60
61
62
63
64
65
#import "UIButton+EnlargeEdge.h"

@implementation UIButton (EnlargeEdge)

static char topNameKey;
static char leftNameKey;
static char rightNameKey;
static char bottomNameKey;

- (void)setEnlargeEdge:(CGFloat)size
{
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setEnlargeEdgeWithTop:(CGFloat)top bottom:(CGFloat)bottom left:(CGFloat)left right:(CGFloat)right
{
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGRect)enlargedRect
{
NSNumber *topEdge = objc_getAssociatedObject(self, &topNameKey);
NSNumber *leftEdge = objc_getAssociatedObject(self, &leftNameKey);
NSNumber *rightEdge = objc_getAssociatedObject(self, &rightNameKey);
NSNumber *bottomEdge = objc_getAssociatedObject(self, &bottomNameKey);

if (topEdge && bottomEdge && leftEdge && rightEdge) {
return CGRectMake(self.bounds.origin.x - leftEdge.floatValue,
self.bounds.origin.y - topEdge.floatValue,
self.bounds.size.width + leftEdge.floatValue + rightEdge.floatValue,
self.bounds.size.height + topEdge.floatValue + bottomEdge.floatValue);
} else {
return self.bounds;
}
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect rect = [self enlargedRect];
if (CGRectEqualToRect(rect, self.bounds)) {
return [super pointInside:point withEvent:event];
}
return CGRectContainsPoint(rect, point) ? YES : NO;
}

//Should Not Override hitTest: directly!

//- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
//{
// CGRect rect = [self enlargedRect];
// if (CGRectEqualToRect(rect, self.bounds))
// {
// return [super hitTest:point withEvent:event];
// }
// return CGRectContainsPoint(rect, point) ? self : nil;
//}


@end

使用:

1
[cell.downloadBtn setEnlargeEdgeWithTop:15 bottom:15 left:7.5 right:15]; // 扩大点击范围

参考链接:

iOS事件响应链中Hit-Test View的应用

4、解决阴影和圆角不能同时共存的问题

参考:

如何让阴影和圆角同时实现

5.

Method Swizzing

发表于 2017-08-30

关于 Method Swizzling 其实已经算是挺熟悉了的,但今天在用的时候遇到了一个坑,觉得还是有必要记录一下的。

阅读全文 »
12…5
朝元

朝元

42 日志
10 分类
24 标签
© 2019 朝元
由 Hexo 强力驱动
主题 - NexT.Mist