作者: xiao, yanzi

iOS高级编程 runtime

代码示例:

1 #import <objc/runtime.h>
2 #import <objc/message.h>
3 #import <stdio.h>
4
5 extern int MyUIApplicationMain(int argc, char *argv[], void *principalClassName, void *delegateClassName);
6
7 struct MyRect {
8     float x;
9     float y;
10     float width;
11     float height;
12 };
13 typedef struct MyRect MyRect;
14
15 void *navController;
16 static int numberOfRows = 100;
17
18 int tableView_numberOfRowInSection(void *receiver, struct objc_selector *selector, void *tblView, int section) {
19     return numberOfRows;
20 }
21
22 void *tableView_cellForRowAtIndexPath(void *receiver, struct objc_selector *selector, void *tblView, void *indexPath) {
23     Class TableViewCell = (Class)objc_getClass(“UITableViewCell”);
24     void *cell = class_createInstance(TableViewCell, 0);
25     objc_msgSend(cell, sel_registerName(“init”));
26     char buffer[7];
27
28     int row = (int)objc_msgSend(indexPath, sel_registerName(“row”));
29     sprintf(buffer, “Row %d”, row);
30     void *label = objc_msgSend(objc_getClass(“NSString”), sel_registerName(“stringWithUTF8String:”), buffer);
31     objc_msgSend(cell, sel_registerName(“setText:”), label);
32
33     return cell;
34 }
35
36 void tableView_didSelectRowAtIndexPath(void *receiver, struct objc_selector *seletor, void *tblView, void *indexPath) {
37     Class ViewController = (Class)objc_getClass(“UIViewController”);
38     void *vc = class_createInstance(ViewController, 0);
39     objc_msgSend(vc, sel_registerName(“init”));
40     char buffer[8];
41     int row = (int)objc_msgSend(indexPath, sel_registerName(“row”));
42     sprintf(buffer, “Item %d”, row);
43     void *label = objc_msgSend(objc_getClass(“NSString”), sel_registerName(“stringWithUTF8String”), buffer);
44     objc_msgSend(vc, sel_registerName(“setTitle”), label);
45
46     objc_msgSend(navController, sel_registerName(“pushViewController:animated:”), vc, 1);
47 }
48
49 void *createDataSource() {
50     Class superclass = (Class)objc_getClass(“NSObject”);
51     Class DataSource = objc_allocateClassPair(superclass, “DataSource”, 0);
52     class_addMethod(DataSource, sel_registerName(“tableView:numberOfRowsInSection:”), (void (*))tableView_numberOfRowInSection, nil);
53     class_addMethod(DataSource, sel_registerName(“tableView:cellForRowAtIndexPath:”), (void (*))tableView_cellForRowAtIndexPath, nil);
54
55     objc_registerClassPair(DataSource);
56     return class_createInstance(DataSource, 0);
57 }
58
59 void *createDelegate() {
60     Class superClass = (Class)object_getClass(@”NSObject”);
61     Class DataSource = objc_allocateClassPair(superClass, “Delegate”, 0);
62     class_addMethod(DataSource, sel_registerName(“tableView:didSelectRowAtIndexPath:”), (void (*))tableView_didSelectRowAtIndexPath, nil);
63
64     objc_registerClassPair(DataSource);
65     return class_createInstance(DataSource, 0);
66 }
67
68 //自定义主函数
69 void applicationdidFinishLaunching(void *receiver, struct objc_selector *selector, void *application) {
70     Class windowClass = (Class)object_getClass(@”UIWindow”);
71     void *windowInstance = class_createInstance(windowClass, 0);
72
73     objc_msgSend(windowClass, sel_registerName(“initWithFrame:”), (MyRect){0, 0, 320, 480});
74
75     //make key and visiable
76     objc_msgSend(windowInstance, sel_registerName(“makeKeyAndVisible”));
77
78     //创建表
79     Class TableViewController = (Class)object_getClass(@”UITableViewController”);
80     void *tableViewController = class_createInstance(TableViewController, 0);
81     objc_msgSend(TableViewController, sel_registerName(“init”));
82     void *tableView = objc_msgSend(TableViewController, sel_registerName(“tableView”));
83     objc_msgSend(tableView, sel_registerName(“setDataSource”), createDataSource());
84     objc_msgSend(tableView, sel_registerName(“setDelegate”), createDelegate());
85
86     Class NavController = (Class)object_getClass(@”UINavigationController”);
87     navController = class_createInstance(NavController, 0);
88     objc_msgSend(navController, sel_registerName(“initWithRootViewCOntroller:”), tableViewController);
89     void *view = objc_msgSend(navController, sel_registerName(“view”));
90
91     //add TableView to window
92     objc_msgSend(windowInstance, sel_registerName(“addSubview:”), view);
93
94 }
95
96 //create an class named “AppDelegate”, and return its name as an instance of class NSString
97 void *createAppDelegate() {
98     Class mySubclass = objc_allocateClassPair((Class)object_getClass(@”NSObject”), “AppDelegate”, 0);
99     struct objc_selector *selName = sel_registerName(“application:didFinishLaunchingWithOptions:”);
100     class_addMethod(mySubclass, selName, (void (*))applicationdidFinishLaunching, nil);
101     objc_registerClassPair(mySubclass);
102     return objc_msgSend(object_getClass(@”NNString”), sel_registerName(“stringWithUTF8String:”), “AppDelegate”);
103 }
104
105 int main(int argc, char *argv[]) {
106     return MyUIApplicationMain(argc, argv, 0, createAppDelegate());

iOS 实时录音和播放

需求:*近公司需要做一个楼宇对讲的功能:门口机(连接WIFI)拨号对室内机(对应的WIFI)的设备进行呼叫,室内机收到呼叫之后将对收到的数据进行UDP广播的转发,手机(连接对应的WIFI)收到视频流之后,实时的展示视频数据(手机可以接听,挂断,手机接听之后,室内机不展示视频,只是进行转发。)

简单点说就是手机客户端需要做一个类似于直播平台的软件,可以实时的展示视频,实时的播放接收到的声音数据,并且实时将手机麦克风收到的声音回传给室内机,室内机负责转发给门口机。

 

这篇文章介绍iOS怎么进行实时的录音和播放收到的声音数据

 

想要使用系统的框架实时播放声音和录音数据,就得知道音频队列服务,

在AudioToolbox框架中的音频队列服务,它完全可以做到音频播放和录制,

一个音频服务队列有三个部分组成:

1.三个缓冲器Buffers:每个缓冲器都是一个存储音频数据的临时仓库。
2.一个缓冲队列Buffer Queue:一个包含音频缓冲器的有序队列。
3.一个回调CallBack:一个自定义的队列回调函数。

具体怎么运转的还是百度吧!

我的简单理解:

对于播放:系统会自动从缓冲队列中循环取出每个缓冲器中的数据进行播放,我们需要做的就是将接收到的数据循环的放到缓冲器中,剩下的就交给系统去实现了。

对于录音:  系统会自动将录的声音放入队列中的每个缓冲器中,我们需要做的就是从回调函数中将数据转化我们自己的数据就OK了。

 

#pragma mark–实时播放

1. 导入系统框架AudioToolbox.framework  AVFoundation.framework

2. 获取麦克风权限,在工程的Info.plist文件中加入Privacy – Microphone Usage Description 这个key 描述:App想要访问您的麦克风

3. 创建播放声音的类 EYAudio

 

EYAudio.h

#import <Foundation/Foundation.h>

@interface EYAudio : NSObject

// 播放的数据流数据
– (void)playWithData:(NSData *)data;

// 声音播放出现问题的时候可以重置一下
– (void)resetPlay;

// 停止播放
– (void)stop;

@end

 

EYAudio.m

 

#import “EYAudio.h”
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

#define MIN_SIZE_PER_FRAME 1920 //每个包的大小,室内机要求为960,具体看下面的配置信息
#define QUEUE_BUFFER_SIZE 3 //缓冲器个数
#define SAMPLE_RATE 16000 //采样频率

@interface EYAudio(){
AudioQueueRef audioQueue; //音频播放队列
AudioStreamBasicDescription _audioDescription;
AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE]; //音频缓存
BOOL audioQueueBufferUsed[QUEUE_BUFFER_SIZE]; //判断音频缓存是否在使用
NSLock *sysnLock;
NSMutableData *tempData;
OSStatus osState;
}
@end

@implementation EYAudio

#pragma mark – 提前设置AVAudioSessionCategoryMultiRoute 播放和录音
+ (void)initialize
{
NSError *error = nil;
//只想要播放:AVAudioSessionCategoryPlayback
//只想要录音:AVAudioSessionCategoryRecord
//想要”播放和录音”同时进行 必须设置为:AVAudioSessionCategoryMultiRoute 而不是AVAudioSessionCategoryPlayAndRecord(设置这个不好使)
BOOL ret = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryMultiRoute error:&error];
if (!ret) {
NSLog(@”设置声音环境失败”);
return;
}
//启用audio session
ret = [[AVAudioSession sharedInstance] setActive:YES error:&error];
if (!ret)
{
NSLog(@”启动失败”);
return;
}
}

– (void)resetPlay
{
if (audioQueue != nil) {
AudioQueueReset(audioQueue);
}
}

– (void)stop
{
if (audioQueue != nil) {
AudioQueueStop(audioQueue,true);
}

audioQueue = nil;
sysnLock = nil;
}

– (instancetype)init
{
self = [super init];
if (self) {
sysnLock = [[NSLock alloc]init];

//设置音频参数 具体的信息需要问后台
_audioDescription.mSampleRate = SAMPLE_RATE;
_audioDescription.mFormatID = kAudioFormatLinearPCM;
_audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
//1单声道
_audioDescription.mChannelsPerFrame = 1;
//每一个packet一侦数据,每个数据包下的桢数,即每个数据包里面有多少桢
_audioDescription.mFramesPerPacket = 1;
//每个采样点16bit量化 语音每采样点占用位数
_audioDescription.mBitsPerChannel = 16;
_audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame;
//每个数据包的bytes总数,每桢的bytes数*每个数据包的桢数
_audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;

// 使用player的内部线程播放 新建输出
AudioQueueNewOutput(&_audioDescription, AudioPlayerAQInputCallback, (__bridge void * _Nullable)(self), nil, 0, 0, &audioQueue);

// 设置音量
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);

// 初始化需要的缓冲区
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
audioQueueBufferUsed[i] = false;
osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
}

osState = AudioQueueStart(audioQueue, NULL);
if (osState != noErr) {
NSLog(@”AudioQueueStart Error”);
}
}
return self;
}

// 播放数据
-(void)playWithData:(NSData *)data
{
[sysnLock lock];

tempData = [NSMutableData new];
[tempData appendData: data];
NSUInteger len = tempData.length;
Byte *bytes = (Byte*)malloc(len);
[tempData getBytes:bytes length: len];

int i = 0;
while (true) {
if (!audioQueueBufferUsed[i]) {
audioQueueBufferUsed[i] = true;
break;
}else {
i++;
if (i >= QUEUE_BUFFER_SIZE) {
i = 0;
}
}
}

audioQueueBuffers[i] -> mAudioDataByteSize = (unsigned int)len;
// 把bytes的头地址开始的len字节给mAudioData,向第i个缓冲器
memcpy(audioQueueBuffers[i] -> mAudioData, bytes, len);

// 释放对象
free(bytes);

//将第i个缓冲器放到队列中,剩下的都交给系统了
AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);

[sysnLock unlock];
}

// ************************** 回调 **********************************
// 回调回来把buffer状态设为未使用
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) {

EYAudio* audio = (__bridge EYAudio*)inUserData;

;
}

– (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
// 防止空数据让audioqueue后续都不播放,为了安全防护一下
if (tempData.length == 0) {
audioQueueBufferRef->mAudioDataByteSize = 1;
Byte* byte = audioQueueBufferRef->mAudioData;
byte = 0;
AudioQueueEnqueueBuffer(audioQueueRef, audioQueueBufferRef, 0, NULL);
}

for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
// 将这个buffer设为未使用
if (audioQueueBufferRef == audioQueueBuffers[i]) {
audioQueueBufferUsed[i] = false;
}
}
}

@end

 

 

外界使用: 不断调用下面的方法将NSData传递进来

– (void)playWithData:(NSData *)data;

 

#pragma mark–实时录音

 

1. 导入系统框架AudioToolbox.framework  AVFoundation.framework

2. 创建录音的类 EYRecord

 

EYRecord.h

 

#import <Foundation/Foundation.h>

@interface ESARecord : NSObject

//开始录音
– (void)startRecording;

//停止录音
– (void)stopRecording;

@end

 

EYRecord.m

 

#import “ESARecord.h”
#import <AudioToolbox/AudioToolbox.h>

#define QUEUE_BUFFER_SIZE 3 // 输出音频队列缓冲个数
#define kDefaultBufferDurationSeconds 0.03//调整这个值使得录音的缓冲区大小为960,实际会小于或等于960,需要处理小于960的情况
#define kDefaultSampleRate 16000 //定义采样率为16000

extern NSString * const ESAIntercomNotifationRecordString;

static BOOL isRecording = NO;

@interface ESARecord(){
AudioQueueRef _audioQueue; //输出音频播放队列
AudioStreamBasicDescription _recordFormat;
AudioQueueBufferRef _audioBuffers[QUEUE_BUFFER_SIZE]; //输出音频缓存
}
@property (nonatomic, assign) BOOL isRecording;

@end

@implementation ESARecord

– (instancetype)init
{
self = [super init];
if (self) {
//重置下
memset(&_recordFormat, 0, sizeof(_recordFormat));
_recordFormat.mSampleRate = kDefaultSampleRate;
_recordFormat.mChannelsPerFrame = 1;
_recordFormat.mFormatID = kAudioFormatLinearPCM;

_recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
_recordFormat.mBitsPerChannel = 16;
_recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
_recordFormat.mFramesPerPacket = 1;

//初始化音频输入队列
AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);

//计算估算的缓存区大小
int frames = (int)ceil(kDefaultBufferDurationSeconds * _recordFormat.mSampleRate);
int bufferByteSize = frames * _recordFormat.mBytesPerFrame;

NSLog(@”缓存区大小%d”,bufferByteSize);

//创建缓冲器
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++){
AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioBuffers[i]);
AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[i], 0, NULL);
}
}
return self;
}

-(void)startRecording
{
// 开始录音
AudioQueueStart(_audioQueue, NULL);
isRecording = YES;
}

void inputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
{
if (inNumPackets > 0) {
ESARecord *recorder = (__bridge ESARecord*)inUserData;
[recorder processAudioBuffer:inBuffer withQueue:inAQ];
}

if (isRecording) {
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
}

– (void)processAudioBuffer:(AudioQueueBufferRef )audioQueueBufferRef withQueue:(AudioQueueRef )audioQueueRef
{
NSMutableData * dataM = [NSMutableData dataWithBytes:audioQueueBufferRef->mAudioData length:audioQueueBufferRef->mAudioDataByteSize];

if (dataM.length < 960) { //处理长度小于960的情况,此处是补00
Byte byte[] = {0x00};
NSData * zeroData = [[NSData alloc] initWithBytes:byte length:1];
for (NSUInteger i = dataM.length; i < 960; i++) {
[dataM appendData:zeroData];
}
}

// NSLog(@”实时录音的数据–%@”, dataM);
//此处是发通知将dataM 传递出去
[[NSNotificationCenter defaultCenter] postNotificationName:@”EYRecordNotifacation” object:@{@”data” : dataM}];
}

-(void)stopRecording
{
if (isRecording)
{
isRecording = NO;

//停止录音队列和移除缓冲区,以及关闭session,这里无需考虑成功与否
AudioQueueStop(_audioQueue, true);
//移除缓冲区,true代表立即结束录制,false代表将缓冲区处理完再结束
AudioQueueDispose(_audioQueue, true);
}
NSLog(@”停止录音”);
}

@end

基于iOS的网络音视频实时传输系统(一)- 前言

演示图

(由于GIF大小限制,图做得比较模糊和不太流畅,实际的效果是不错的,可以自己下下来实际跑一下)

client端:

%title插图%num

%title插图%num
server端:

%title插图%num

%title插图%num

下载

GitHub:

client 端:https://github.com/AmoAmoAmo/Smart_Device_Client

server端:https://github.com/AmoAmoAmo/Smart_Device_Server

另还写了一份macOS版的server,但是目前还有一些问题,有兴趣的去看看吧:https://github.com/AmoAmoAmo/Server_Mac

简介

之前在做类似的网络协议的时候,突发奇想,想写一个网络视频监控,基于局域网的情况下,将MacBook摄像头捕获到的视频,在手机端显示,但是由于对macOS不是很熟悉,*终导致该计划流产。所以后来干脆使用手机捕获视频数据。

为了简化项目工作量,socket协议也只用到了一些必要的功能,其他细节如client端退出监控视频时,server端会crash,各位有需要可以自行去添加一些如设置select()函数,或者设置signal()函数忽略这个断开的信号。等等

项目中没有写录制设备视频的功能,所以没有用到MP4封装
更多其他的细节已经搭建过程,有兴趣的可以去我的GitHub上回退到各个版本看循序渐进的过程。关于音视频我也是初学者,欢迎各位斧正。

主要功能

client端:

1. udp局域网搜索设备(模拟设备的代码在这里),或者手动添加其他设备(并没有功能) 到plist
2. 点击已添加的监控设备,开始TCP音视频数据传输
3. 接收到音视频数据,进行解码,并用OpenGL es渲染显示到界面上 或openAL播放音频
4. 横竖屏功能

server端(摄像头):

1. 点击“reset”,进入“配对模式”,即开始UDP监听AP
2. 连接成功后,将自己的设备信息发送给client
3. 开始捕获音视频,并进行硬编码,发送给client

使用方法

*次使用时,先点击“server”端的“reset”按钮,表示开始处于配对模式,

“client”端点击“+”按钮,开始去局域网里搜索(UDP)设备,就会得到设备的IP地址等信息,后面音视频传输就是用这个IP地址来建立TCP通信的。这个过程模拟实际应用中

这里音视频传输用TCP还是UDP没有什么讲究,主要是根据嵌入式工程师给的音视频协议文档里用到的是哪个。

这里就是用“server”端来模拟一种型号的监控摄像头。

找到设备后,信息会自动被填写到textField上,点击“添加”,设备就被写入plist文件里。

点击已添加的监控视频,即可开始音视频传输

iOS开发——APP回退到历史版本

1.软件准备

  • 【必备】Charles

2.正式开始

2.1打开Charles青花瓷
%title插图%num

2.2安装证书以便能够拦截解析HTTPS请求数据
%title插图%num

2.2.1如果是选择本地安装证书的话,记住保存格式选择.cer
%title插图%num

2.2.2双击下载好的证书
%title插图%num

2.2.3右键点击证书
%title插图%num

2.2.4信任证书
%title插图%num

2.2.5 ssl代理证书配置完成
%title插图%num

2.3打开iTunes
%title插图%num

2.4单击编辑左上角以便能够出现应用这个选项
%title插图%num

2.5在下拉选项中选择应用
%title插图%num

2.6单击正上方的App Store
%title插图%num

2.7搜索需要下载历史版本的App,这里我以IT之家为例
%title插图%num

2.8点击获取按钮,就会变成正在下载
%title插图%num

2.9这时候查看Charles能够看到一大堆请求,看关键字p28-buy,这个是Itunes的下载请求,而且是HTTPS加密的,链接旁边有一把小锁,不能查看里面的内容
%title插图%num

2.10打开ssl代理并打断点拦截该请求
%title插图%num

2.11进入iTunes删除刚才下载的App,因为刚才只为为了获取下载请求
%title插图%num

2.12删除好App后刷新一下,重启Charles,再次点击iTunes的获取按钮
%title插图%num

2.13可以看到这时候已经能够查看HTTPS请求里面的内容了,点击图中相应位置
%title插图%num

2.14往下翻,可以查看到一大串数字,这些全都是该应用的历史版本编号,我们只需要把想下载的历史版本的编号复制下来就好,后面会用到,这里我复制*个11577427,为该应用程序的*个版本
%title插图%num

2.15再次点击获取按钮,下载请求会被拦截,跳转到Charles
%title插图%num

2.16这时候把刚才我们复制好的历史版本号
%title插图%num

2.17点击Excute执行按钮,可能会来断点2次,除了*次,后面的都直接点击Excute执行按钮
%title插图%num

2.18大功告成!!!
%title插图%num

2.19查看下载好的App的版本号
%title插图%num

%title插图%num

%title插图%num

3.安装旧版本应用程序

旧版本的应用程序下载好了,接下来我们要安装到手机上才是王道,或者进行其他用途,这里介绍下2种安装到iPhone上的方法。

  • 1.利用iTunes的安装
    手机链接电脑,iTunes会自动弹出,然后会进行同步,刚才电脑上下载好的应用会自动安装到iPhone上。
  • 2.利用iTools的安装
%title插图%num

iOS自定义Log(OC方法与Swift方法)

新项目开始之时,自定义Log
在debug环境下打印内容,但是在release环境下不显示打印

动态获取打印所在文件

OC方法:

在pch文件里面添加:

#ifdef DEBUG
#define DLog(fmt, …) NSLog((@”%s [Line %d] ” fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define DLog(…)
#endif

printLog(@”test”);

打印输出:

Swift方法:

func printLog<T>(message:T,
file: String = #file,
method: String = #function,
line: Int = #line) {

#if DEBUG
guard System.isEnablePrint == true else {
return
}
print(“路径:\((file as NSString).lastPathComponent)[\(line)],方法:\(method),信息:\(message)”)
#endif

}

swift等效OC方法makeObjectsPerformSelector

在OC我使用这段代码删除子视图:

[self.view.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
1
在swift中使用该方法

self.view.subviews.forEach { subview in
subview.removeFromSuperview()
}
1
2
3
或者

view.subviews.forEach { $0.removeFromSuperview() }

iOS内存管理方案

不同场景下的内存管理方案:
1 小对象采用TaggedPointer
2 Arm64架构下的iOS应用程序采用的是NONPOINTER_ISA(本身占64bit位,实际上32位就够用了,剩余的苹果为了提高内存的利用率,剩余位存储了内存管理相关的内容)
*位如果是0代表isa只是存isa指针的,如果是1代表是优化过的,第2位表示当前对象是否有关联对象has_assoc,第三位has_cxx_dtor表示当前变量是否用到c++代码或者c++一些内容,第4位到第32位(也就是33位)是当前对象的类对象的指针地址, 接下来有weakly_referenced是否有相应的弱引用指针,还有deallocating标志当前对象是否再进行dealloc操作,还有当前的引用技术到达上限的话就外挂一个has_sidetable_rc散列表,接下来是extra_rc额外的引用计数如果引用计数在一个很小的范围内就会存到isa指针当中
3 散列表(引用计数表和弱引用表)SideTables

%title插图%num

为什么不是一个sideTable而是多个sideTable组成一个sideTables这么一个数据结构
假如把所有对象的弱引用计数都放在一个大表中,如果我要操作某一个对象值进行修改,由于对象可能是在不同的线程当中进行分配创建的,那么需要加锁处理才能保证访问安全,存在效率的问题,如果有成千上百个对象,每个存储的对象引用计数改变的时候都锁住,就存在效率的问题,为了解决效率问题提供了分离锁的技术方案比如非嵌入式原来是64个SideTable那么现在就用8个(N = 64),对8个锁分别加锁,如果两个对象不在一个表中就可以同时进行加锁.
如何实现快速分流:(SideTables的本质是一张hash表)

%title插图%num

%title插图%num
Spinlock_t 是”忙等”的锁,如果当前锁已被其他线程获取, 那么当前线程就不断的判断这个锁是否有被释放,如果释放掉自己*时间获取这个锁, 自旋锁适用于轻量访问
比如其他锁,信号量,看到当前锁已经被其他线程获取,那么当前线程就会阻塞休眠,等到其他线程释放锁后来唤醒当前线程.

RefcountMap:引用计数表是哈希表,提供查找效率避免for循环遍历

%title插图%num

size_t中每个位的意思

%title插图%num
weak_table_t 弱引用表

%title插图%num

ARC:是LLVM和Runtime协作的共同结果
引用计数管理:dealloc实现:

%title插图%num

object_dispose()实现

%title插图%num
objc_destructInstance()实现

%title插图%num

clearDeallocting()实现

%title插图%num

弱引用管理:

%title插图%num

添加weak变量

%title插图%num
自动释放池:以栈为节点通过双向链表(ParentPtr, ChildPtr)的形式组合而成.是和线程一一对应的
AutoreleasePool的实现原理是怎么样的?为何可以嵌套使用
编译器会将@autoreleasepool{}改写为
void *ctx = objc_autoreleasePoolPush(); -> void * AutoreleasePoopPage::push(void)
{}中的代码
objc_autoreleasePoolPop(ctx); -> AutoreleasePoopPage::pop(void * ctx)
一次pop实际上相等于一次批量的pop操作

%title插图%num

如果再进行一次push入栈操作就可以添加一个autorelease对象到了next这里,把当前next位置置为Nil称为哨兵对象,再将Next指针指向下一个可以入栈的位置, 实际上每次进行一个autoreleasePoolPage代码块的创建相等于不断的在栈当中插入哨兵对象. 下面的图中*下面是autoreleasepoolpage自身所占用的内存,上面是存储花括号当中所填充的autorelease对象,同样的栈是从高地址到低地址,

%title插图%num

[objc autorelease]

%title插图%num
autoreleasePoolPage::Pop,
根据传入的哨兵对象找到对应位置 (记AutoreleasePoolPage::push函数返回值的哨兵对象),
2 给上次push操作之后添加的对象一次发送release消息,
3 回退next指针到正确的位置

%title插图%num

%title插图%num

在viewDidLoad中创建的对象是在什么时候释放的?
实际上在每一次runloop循环过程当中都会在他将要结束的时候将前一次创建的autoreleasePoolPage进行pop操作,再一个新的autoreleasePool
在当次runloop将要结束的时候调用autoreleasePoolPage::pop()
多层嵌套就是多次插入哨兵对象
比如每次进行一个autoreleasepool代码块的创建时,系统会进行一个哨兵对象的插入
完成新的autoreleasepoolPage的创建,如果当前page没有满就不用创建page

什么时候手动创建autoreleasePool:
在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool, 降低内存峰值

iOS arm64汇编中寄存器和基本指令

iOS汇编(汇编语言依赖于机器)
真机,arm64汇编, GNU汇编
模拟器:x86汇编(Mac电脑) , AT&T汇编

真机:arm64汇编
寄存器:
通用寄存器
64bit的:x0~x28,29个寄存器每个寄存器都是64位的,64位就是8 个字节,一个寄存器*多能放8个字节的数据
32bit的:w0~w28(属于x0~x28的低32bit) 4个字节
x0~x7通常拿来存放函数的参数,更多的参数使用堆栈来传递
x0通常拿来存放函数的返回值
程序状态寄存器
cpsr(current program status register)
spsr(saved program status register) ,异常状态下使用
指令
堆栈
在xcode中建立汇编文件

%title插图%num

%title插图%num
在汇编文件中如何写函数
.text // 放代码段的 .data//数据段,.stack// 栈段
test: // 函数名称
在外面如何调用汇编文件就要建一个叫arm.h的头文件

%title插图%num

%title插图%num

汇编指令
ret 返回指令
mov 目的寄存器,源操作数 // 将右边的源操作数复制到左边的目的寄存器中

汇编文件写函数

%title插图%num

%title插图%num
指令还有next,nexti 表示如果遇到函数直接逃过这个函数,如果遇到代码是一行一行执行,step,遇到代码也是一行一行执行,如果遇到函数直接进去,stepi-> step instruction一条一条汇编指令跳过.si,执行这一句汇编指令,敲c这个函数过掉了.

%title插图%num

%title插图%num
cmp:将2个寄存器相减,相减的结果会影响cpsr寄存器的标志位
b : 跳转指令, jamp

%title插图%num

%title插图%num

%title插图%num

条件域:

%title插图%num
EQ: equal,相等
NE:not equal,不相等
GT:great than,大于
GE great or equal,大于等于
LT:less than,小于
LE:less equal, 小于等于

bl:带返回的跳转指令,跳转完返回到跳转的下一行

内存操作指令
load, 从内存中装载数据,从内存中读数据到寄存器
ldr,ldur,去内存数据放到寄存器中,区别是如果右边立即数为负数则用的是ldur,如果右边立即数为正数则用ldr.立即数为偏移值

%title插图%num

ldp:(p是pair的简称,一对的意思)从内存中读取数据放到一对寄存器中
ldp w0, w1, [x2, #0x10],取[x2,#0x10]的前4个字节给w0,接着下面的4个字节给w1

%title插图%num

store指令往内存中存储数据
str, stur指令将寄存器里的值往内存中写入数据

stp指令 w0, w1, [x1, #-0x5] ,将w0四个字节的数据放入[x1, #-0x5],再讲w1的四个字节的数据放入[x1, #-0x5]中
先写寄存器w0,w1,再写内存寻址[x1, #-0x5]
寻址方式:

%title插图%num

%title插图%num

%title插图%num

零寄存器:里面存储的值为0
wzr(32bit, Word Zero Register) 4个字节,
xzr(64bit) 8个字节

%title插图%num
将4个字节的0赋值给了a,将4个字节的0赋值给了b

程序计数器
pc (Program Counter) 记录CPU当前指令的是哪一条指令,存储着当前CPU正在执行的指令的地址,类似于8086汇编的ip寄存器.
si
register read
链接寄存器
lr (Link Register) ,也就是x30,存储着函数的返回地址

%title插图%num
bl与b指令的本质区别
bl指令(带返回值的跳转指令):
将下一条指令的地址存储到lr(x30)寄存器中
跳转到标记处开始执行代码

ret指令函数返回, 将将lr(x30)的值赋值给pc寄存器,pc里面存储的地址值,则cpu马上就执行这个地址对应的指令
而b指令只是跳转

iOS实现App之间的分享

我们在iOS平台上想要实现不同App之间的内容分享一般有几种常用方式:
*种是通过AirDrop实现不同设备的App之间文档和数据的分享;
第二种是给每个App定义一个URL Scheme,通过访问指定了URL Scheme的一个URL,实现直接访问一个APP;
第三种是通过UIDocumentInteractionController或者是UIActivityViewController这俩个iOS SDK中封装好的类在App之间发送数据、分享数据和操作数据;
第四种是通过App Extension,在iOS 8的SDK中提供的扩展新特性实现跨App的数据操作和分享;
还有一种集成第三方SDK实现的有限个App的数据分享,比如社交平台(QQ,微信,新浪微博等)给我们提供的官方SDK,或者是集成了多个社交平台的ShareSDK组件和友盟分享组件等。
谈谈苹果原生提供的基于iOS SDK的分享技术,同时推荐俩篇苹果开发者中心的文档:Inter-App Communication和Document Interaction Programming Topics for iOS。
谈一下如何通过UTI让我们的App支持分享。
原理
参考详解苹果提供的UTI(统一类型标识符)这篇文章中,详细地讲解了一下UTI(Uniform Type Identifier),一套苹果给我们提供用来在基于Cocoa和Cocoa Touch应用程序中识别实体内容类型的规范,而关于实现内容关联的技术也正是基于这套规范。在iOS和Mac OS开发中,苹果给我们提供了注册文档类型的接口,而这种注册的文档类型是全局的,系统中所有的应用程序和服务都可以侦测到。因此我们通过这个底层侦测,可以使用其他可选的第三方App来预览我们的App中不支持的文档,而且我们还可以通过这个接口在我们的App中打开并处理第三方App的文档。

如果我们的App可以处理某些类型的实体内容,那么我们就可以在我们项目中的Info.plist文件中进行注册。关于使用哪种类型和UTI,就要参考我在“详解苹果提供的UTI(统一类型标识符)“这篇文章中的讲解。当一个第三方App通过苹果的底层侦测技术检查有哪些App可以处理它所指定的内容类型时,如果我们的App已经注册了这种类型,那么我们的App图标就会显示在其中,并且作为我们自己的App的一个入口。
主要技术
主要应用到这种底层侦测的技术有iOS SDK中给我们提供的UIDocumentInteractionController、UIActivityViewController 和Quick Look 框架。此外,在iOS 8中,苹果又给开发者提供了App Extension,一种更高大上的方式在App之间的实现分享内容。关于UIDocumentInteractionController、UIActivityViewController、Quick Look 框架以及App Extension的细节,我计划在后面的文章中详细讲解。这篇文章,我们主要是来谈谈如何注册我们App可用的文档类型以及简单使用我们的App来处理第三方App分享的内容。

注册可用类型
我们需要在info.plist文件中,添加一个新的属性CFBundleDocumentTypes(实际上输入的是”Document types”),这是一个数组类型的属性,意思就是我们可以同时注册多个类型。而针对数组中的每一个元素,都有许多属性可以指定,详细的属性列表我们可以从官方文档上找到: Core Foundation Keys —- CFBundleDocumentTypes。这里列举我们在做iOS开发时常用的属性
%title插图%num
– CFBundleTypeName(“Icon File Name”)
字符串类型,指定某种类型的别名,也就是用来指代我们规定的类型的别称,一般为了保持唯一性,我们使用UTI来标识。
– CFBundleTypeIconFiles
数组类型,包含指定的png图标的文件名,指定代表某种类型的图标,而图标有具体的尺寸标识:

|Device |Sizes |
|:------|:---------|
|iPad |64 x 64 pixels, 320 x 320 pixels|
|iPhone and iPod touch|22 x 29 pixels, 44 x 58 pixels (high resolution)|

 

  • LSItemContentTypes(“Document Content Type UTIs”)
    数组类型,包含UTI字符串,指定我们的应用程序所有可以识别的类型集合
  • LSHandlerRank(“Handler rank”)
    字符串类型,包含Owner,Default,Alternate,None四个可选值,指定对于某种类型的优先权级别,而Launcher Service会根据这个优先级别来排列显示的App的顺序。优先级别从高到低依次是Owner,Alternate,Default。None表示不接受这种类型。
    而当我们添加完所有属性后,开始运行我们的程序,然后再回到我们的Info界面,就会看到Document types这个列表已经发生了变化,这就证明我们成功的注册好了App可用的类型。
    %title插图%num

打开第三方应用
我们在上面的步骤中注册好了我们的App可以识别的类型,现在我们可以打开一个使用UIDocumentInteractionController或者是Quick Look框架来展示内容的第三方App,这里以iPhone 上的QQ程序为例。

我们在上面的注册步骤中,注册的LSItemContentTypes仅包含了public.data这个UTI。所以我们先从QQ应用程序的我的文件中,打开不同类型的文件进行对比,大家可以看下图我的文件列表中包含俩种类型的文件,一种是.jpg扩展名的图片文件,一种是.pdf扩展名的文档文件。
%title插图%num
%title插图%num
当我打开一个图片文件进行预览时,点击其他应用打开,就可以在App列表中看到我们的App图标。简单介绍一下这个页面,*行是苹果在iOS 7之后给我们提供的使用AirDrop在iPhone、iPad或iPod Touch设备之间通过iCloud共享内容的一种方式。第二行是通过文档类型关联技术识别的App的列表。第三行是通过文档关联技术识别的Action的列表,Action表示对文档可进行的操作,如复制,打印等。
程序回调
当我们通过上面步骤,成功地显示了我的app图标之后,点击图标,我们就可以跳转到我的应用中,而苹果在iOS SDK中给我们提供的接收回调的方法在iOS 9之后做出了改变,因此我们需要针对不同的设备版本做出改变:
%title插图%num

%title插图%num

%title插图%num

分享一下自己的手机从ios14降级回ios13.5.1,可以保存资料哦

关于ios14降级后点击图片和网页分享页面出现空白的情况
需要还原系统设置,这样子会重置当前的所有设置(不会清除数据)。

通用—还原—还原所有设置(会重启)

%title插图%num

下面是修复问题后的样子

%title插图%num
升级ios14测试版
6月22号凌晨ios14测试版推出后,上午立马给我的小7装上尝尝鲜。
想要尝试的请下载描述文件,iphone和ipad通用。

https://www.firedev.xyz/14/iOS_iPadOS_14_AppleSeed_Profile.mobileconfig

注意:使用Safari浏览器(iphone自带的)

ios14主要的不同点是:
App资源库,可以智能分类
小组件,主屏幕和负一屏
画中画,这个实测仅限apple TV可以使用
接下来的一段时间让我深刻体会到了什么是测试版,噩梦开始了
手机发热(堪比某米)
掉电巨快
有时莫名的卡顿
系统占用内存的变大
*让我受不了的还是前面两点
ios14降回ios13.5.1
昨天晚上实在是受不了,决定倒退系统

准备工作
itunes(用来备份手机和还原)
ios13.5.1固件包
1、备份
建议啊,icloud基本备份一下!!!!!!
微信和QQ的聊天记录备份,可以先备份到电脑上,双重保险。
由于 iOS 系统限制,跨版本降级会导致设备数据丢失,理论上从 iOS 14 备份的数据也无法恢复至 iOS 13 系统,你现在用爱思助手或者 iTunes 备份的数据,降回 iOS 13 是无法恢复的。

1.1、首先,在 iOS 14 系统下通过 iTunes 完整备份你的 iPhone 资料。

%title插图%num

如果找不到这个页面,请点击这个手机小图标

%title插图%num
1.2、备份完成后,打开刚刚备份的文件
windows:C:Users/用户名/AppData/Roaming/Apple Computer/MobileSync/Backup

打开*新的备份文件夹,用记事本/文本编辑工具打开info.plist
(文件被删除了,就不上图了)

Ctrl+F 查找Product Version

在这个字段的下一行就是关于IOS备份的版本号了,因为是在ios14下备份的嘛,这个就是14了

<kev>Product Version</kev>
<string>14.0</string>
<key>Serial Number</key>

把中间的14.0修改为13.5.1,也就是要降级的系统版本。注意啊,这个版本号千万不要写错!!!!!
2、固件包
2.1、在爱思助手官网上下载相应的固件包
链接地址

%title插图%num
3、使用itunes进行降级
打开 iTunes,按住 shift(windows)键,点恢复 iPhone,在弹出窗口中选择下载好的 iOS 13.5.1 系统固件等待刷机完成即可

%title插图%num
实测
除了邮件账户需要重新添加,别的数据都在。(也可能是icloud有基本备份)
成功了的小伙伴帮我点一下赞噢

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速