前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >第13章 Kotlin 集成 SpringBoot 服务端开发(2)

第13章 Kotlin 集成 SpringBoot 服务端开发(2)

作者头像
一个会写诗的程序员
发布于 2018-08-17 06:19:49
发布于 2018-08-17 06:19:49
58200
代码可运行
举报
运行总次数:0
代码可运行

13.2.10 搜索关键字管理

本节我们开发爬虫爬取的关键字管理的功能。

数据库实体类

首先,新建实体类SearchKeyWord 如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.easy.kotlin.picturecrawler.entity

import java.util.*
import javax.persistence.*


@Entity
@Table(indexes = arrayOf(Index(name = "idx_key_word", columnList = "keyWord", unique = true)))
class SearchKeyWord {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = -1
    @Column(name = "keyWord", length = 50, nullable = false, unique = true)
    var keyWord: String = ""
    @Column(nullable = true)
    var totalImage: Int? = 0
    var gmtCreated: Date = Date()
    var gmtModified: Date = Date()
    var isDeleted: Int = 0  //1 Yes 0 No
    var deletedDate: Date = Date()
}

其中,keyWord 是搜索关键字,有唯一性约束,同时我们给它建立了索引。

dao 层接口

我们来实现插入数据的 dao 层接口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Modifying
    @Transactional
    @Query(value = "INSERT INTO `search_key_word` (`deleted_date`, `gmt_created`, `gmt_modified`, `is_deleted`, `key_word`) VALUES (now(), now(), now(), '0', :keyWord) ON DUPLICATE KEY UPDATE `gmt_modified` = now()", nativeQuery = true)
    fun saveOnNoDuplicateKey(@Param("keyWord") keyWord: String): Int

其中,ON DUPLICATE KEY UPDATE 这句表明当遇到重复的键值的时候,执行更新 gmt_modified = now() 的操作。这里nativeQuery = true ,表示使用的是原生 SQL 查询。

系统启动初始化动作

我们在应用启动类PictureCrawlerApplication 中添加初始化动作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.easy.kotlin.picturecrawler

import com.easy.kotlin.picturecrawler.dao.SearchKeyWordRepository
import com.easy.kotlin.picturecrawler.entity.SearchKeyWord
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.stereotype.Component
import java.io.File


@SpringBootApplication
@EnableScheduling
class PictureCrawlerApplication

fun main(args: Array<String>) {
    SpringApplication.run(PictureCrawlerApplication::class.java, *args)
}


@Component
@Order(value = Ordered.LOWEST_PRECEDENCE)
class initSearchKeyWordRunner : CommandLineRunner {
    @Autowired lateinit var searchKeyWordRepository: SearchKeyWordRepository

    override fun run(vararg args: String) {
        var keyWords = File("搜索关键词列表.data").readLines()
        keyWords.forEach {
            val SearchKeyWord = SearchKeyWord()
            SearchKeyWord.keyWord = it
            searchKeyWordRepository.saveOnNoDuplicateKey(it)
        }
    }
}

Spring Boot应用程序在启动后会去遍历 CommandLineRunner 接口的实例并运行它们的run方法。使用@Order注解来指定 CommandLineRunner 实例的运行顺序。

搜索查询接口

查询所有关键字记录接口如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Query("SELECT a from #{#entityName} a where a.isDeleted=0 order by a.id desc")
override fun findAll(pageable: Pageable): Page<SearchKeyWord>

模糊搜索关键字接口如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Query("SELECT a from #{#entityName} a where a.isDeleted=0 and a.keyWord like %:searchText% order by a.id desc")
fun search(@Param("searchText") searchText: String, pageable: Pageable): Page<SearchKeyWord>
模糊搜索 http 接口实现

跟搜索图片分类的逻辑类似,模糊搜索关键字的接口如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @RequestMapping(value = "searchKeyWordJson", method = arrayOf(RequestMethod.GET))
    @ResponseBody
    fun sotuSearchJson(@RequestParam(value = "page", defaultValue = "0") page: Int, @RequestParam(value = "size", defaultValue = "10") size: Int, @RequestParam(value = "searchText", defaultValue = "") searchText: String): Page<SearchKeyWord> {
        return getPageResult(page, size, searchText)
    }

    private fun getPageResult(page: Int, size: Int, searchText: String): Page<SearchKeyWord> {
        val sort = Sort(Sort.Direction.DESC, "id")
        // 注意:PageRequest.of(page,size,sort) page 默认是从0开始
        val pageable = PageRequest.of(page, size, sort)
        if (searchText == "") {
            return searchKeyWordRepository.findAll(pageable)
        } else {
            return searchKeyWordRepository.search(searchText, pageable)
        }
    }
前端列表页面代码

search_keyword_view.ftl 模板页面代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<#include 'common/head.ftl'>
<#include 'common/nav.ftl'>

<form id="add_key_word_form">
    <div class="col-lg-3">
        <div class="input-group">
            <input name="keyWord"
                   id="add_key_word_form_keyWord"
                   type="text"
                   class="form-control"
                   placeholder="输入爬虫抓取关键字">
            <span class="input-group-btn">
                        <button id="add_key_word_form_save_button"
                                class="btn btn-default"
                                type="button">
                             保存
                        </button>
            </span>
        </div><!-- /input-group -->
    </div><!-- /.col-lg-3 -->
</form>
<table id="search_keyword_table"></table>
<#include 'common/foot.ftl'>
<script src="search_keyword_table.js"></script>

search_keyword_table.js 代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$(function () {
    $.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['zh-CN'])
    var searchText = $('.search').find('input').val()

    var columns = []

    columns.push(
        {
            title: 'ID',
            field: 'id',
            align: 'center',
            valign: 'middle',
            width: '10%',
            formatter: function (value, row, index) {
                return value
            }
        },
        {
            title: '关键字',
            field: 'keyWord',
            align: 'center',
            valign: 'middle',
            formatter: function (value, row, index) {
                var html = "<a href='sotu_view?keyWord=" + value + "' target='_blank'>" + value + "</a>"
                return html
            }
        },
        {
            title: '图片总数',
            field: 'totalImage',
            align: 'center',
            valign: 'middle',
            formatter: function (value, row, index) {
                var html = "<a href='sotu_view?keyWord=" + row.keyWord + "' target='_blank'>" + row.totalImage + "</a>"
                return html
            }
        })

    $('#search_keyword_table').bootstrapTable({
        url: 'searchKeyWordJson',
        sidePagination: "server",
        queryParamsType: 'page,size',
        contentType: "application/x-www-form-urlencoded",
        method: 'get',
        striped: false,     //是否显示行间隔色
        cache: false,      //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
        pagination: true,  //是否显示分页(*)
        paginationLoop: true,
        paginationHAlign: 'right', //right, left
        paginationVAlign: 'bottom', //bottom, top, both
        paginationDetailHAlign: 'left', //right, left
        paginationPreText: ' 上一页',
        paginationNextText: '下一页',
        search: true,
        searchText: searchText,
        searchTimeOut: 500,
        searchAlign: 'right',
        searchOnEnterKey: false,
        trimOnSearch: true,
        sortable: true,    //是否启用排序
        sortOrder: "desc",   //排序方式
        sortName: "id",
        pageNumber: 1,     //初始化加载第一页,默认第一页
        pageSize: 10,      //每页的记录行数(*)
        pageList: [8, 16, 32, 64, 128], // 可选的每页数据
        totalField: 'totalElements', // 所有记录 count
        dataField: 'content', //后端 json 对应的表格List数据的 key
        columns: columns,
        queryParams: function (params) {
            return {
                size: params.pageSize,
                page: params.pageNumber - 1,
                sortName: params.sortName,
                sortOrder: params.sortOrder,
                searchText: params.searchText
            }
        },
        classes: 'table table-responsive full-width',
    })


    $(document).on('keydown', function (event) {
        // 键盘翻页事件
        var e = event || window.event || arguments.callee.caller.arguments[0];
        if (e && e.keyCode == 38 || e && e.keyCode == 37) {//上,左
            // 上一页
            $('.page-pre').click()
        }
        if (e && e.keyCode == 40 || e && e.keyCode == 39) {//下,右
            // 下一页
            $('.page-next').click()
        }

    })

    $('#add_key_word_form_save_button').on('click', function () {
        var keyWord = $('#add_key_word_form_keyWord').val()
        $.ajax({
            url: 'save_keyword',
            type: 'get',
            data: {keyWord: keyWord},
            success: function (response) {
                if (response == "1") {
                    alert("保存成功")
                } else {
                    alert("保存失败")
                }

            },
            error: function (error) {
                alert(JSON.stringify(error))
            }
        })
    })

})
添加爬取关键字

添加爬取关键字 http 接口代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RequestMapping(value = "save_keyword", method = arrayOf(RequestMethod.GET,RequestMethod.POST))
@ResponseBody
fun save(@RequestParam(value = "keyWord")keyWord:String): String {
    if(keyWord==""){
        return "0"
    }else{
        searchKeyWordRepository.saveOnNoDuplicateKey(keyWord)
        return "1"
    }
}

前端输入框表单代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<form id="add_key_word_form">
    <div class="col-lg-3">
        <div class="input-group">
            <input name="keyWord"
                   id="add_key_word_form_keyWord"
                   type="text"
                   class="form-control"
                   placeholder="输入爬虫抓取关键字">
            <span class="input-group-btn">
                        <button id="add_key_word_form_save_button"
                                class="btn btn-default"
                                type="button">
                             保存
                        </button>
            </span>
        </div><!-- /input-group -->
    </div><!-- /.col-lg-3 -->
</form>

对应的 js 代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$('#add_key_word_form_save_button').on('click', function () {
    var keyWord = $('#add_key_word_form_keyWord').val()
    $.ajax({
        url: 'save_keyword',
        type: 'get',
        data: {keyWord: keyWord},
        success: function (response) {
            if (response == "1") {
                alert("保存成功")
                $('#search_keyword_table').bootstrapTable('refresh')
            } else {
                alert("数据不能为空")
            }

        },
        error: function (error) {
            alert(JSON.stringify(error))
        }
    })
})

其中, $('#search_keyword_table').bootstrapTable('refresh') 是当保存成功后,刷新表格内容。

定时更新该关键字的图片总数任务

最终的效果如下

爬取关键字管理页面

模糊搜索“秋”

更新 search_key_word 表 total_image 字段的 SQL 逻辑如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Modifying
@Transactional
@Query("update search_key_word a set a.total_image = (select count(*) from image i where i.is_deleted=0 and i.category like concat('%',a.key_word,'%'))", nativeQuery = true)
fun batchUpdateTotalImage()

表示该对应关键字包含的图片总数。

然后,我们用一个定时任务去执行它

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.easy.kotlin.picturecrawler.job

import com.easy.kotlin.picturecrawler.dao.SearchKeyWordRepository
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.util.*

@Component
class BatchUpdateJob {

    @Autowired lateinit var searchKeyWordRepository: SearchKeyWordRepository

    @Scheduled(cron = "0 */5 * * * ?")
    fun job() {
        println("开始执行定时任务 batchUpdateTotalImage: ${Date()}")
        searchKeyWordRepository.batchUpdateTotalImage()
    }
}

13.2.11 使用协程实现异步爬虫任务

上面我们的定时任务都是同步的。当我们想用 http 接口去触发任务执行的时候,可能并不想一直等待,这个时候可以使用异步的方式。这里我们使用 Kotlin 提供的轻量级线程——协程来实现。在常用的并发模型中,多进程、多线程、分布式是最普遍的,不过近些年来逐渐有一些语言以first-class或者library的形式提供对基于协程的并发模型的支持。其中比较典型的有Scheme、Lua、PythonPerl、Go等以first-class的方式提供对协程的支持。同样地,Kotlin也支持协程。(关于协程的更多介绍,可参考《Kotlin 极简教程》第9章 轻量级线程:协程 )

我们在 build.gradle 中添加kotlinx-coroutines-core 依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '0.19.2'

然后把我们的定时任务代码改写为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
class BatchUpdateJob {

    @Autowired lateinit var searchKeyWordRepository: SearchKeyWordRepository

    @Scheduled(cron = "0 */5 * * * ?")
    fun job() {
        doBatchUpdate()
    }

    fun doBatchUpdate() = runBlocking {
        launch(CommonPool) {
            println("开始执行定时任务 batchUpdateTotalImage: ${Date()}")
            searchKeyWordRepository.batchUpdateTotalImage()
        }
    }
}

同样的爬虫抓取图片的任务也可以改写成

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fun doCrawJob() = runBlocking {
    val list = searchKeyWordRepository.findAll()
    for (i in 1..1000) {
        list.forEach {
            launch(CommonPool) {
                saveImage(it.keyWord, i)
            }
        }
    }
}

其中,launch函数会以非阻塞(non-blocking)当前线程的方式,启动一个新的协程后台任务,并返回一个Job类型的对象作为当前协程的引用。我们把真正要执行的代码逻辑放到 launch(CommonPool) { } 中。这样我们就可以手动启动任务异步执行了。

13.2.12 图片存入数据库并在前端展现

数据库实体类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.easy.kotlin.picturecrawler.entity

import java.util.*
import javax.persistence.*

@Entity
@Table(indexes = arrayOf(
        Index(name = "idx_url", unique = true, columnList = "url"),
        Index(name = "idx_category", unique = false, columnList = "category")))
class Image {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = -1
    @Version
    var version: Int = 0

    @Column(length = 255, unique = true, nullable = false)
    var category: String = ""
    var isFavorite: Int = 0

    @Column(length = 255, unique = true, nullable = false)
    var url: String = ""

    var gmtCreated: Date = Date()
    var gmtModified: Date = Date()
    var isDeleted: Int = 0  //1 Yes 0 No
    var deletedDate: Date = Date()

    @Lob
    var imageBlob: ByteArray = byteArrayOf()
    /* 0-Baidu  1-Gank */
    var sourceType: Int = 0

    override fun toString(): String {
        return "Image(id=$id, version=$version, category='$category', isFavorite=$isFavorite, url='$url', gmtCreated=$gmtCreated, gmtModified=$gmtModified, isDeleted=$isDeleted, deletedDate=$deletedDate)"
    }
}

其中 @Lob var imageBlob: ByteArray = byteArrayOf() 这个字段存储图片的 Base64内容。

图片比特流数组存入数据库代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
val image = Image()
image.category = "干货集中营福利"
image.url = url
image.sourceType = 1
image.imageBlob = getByteArray(url)
logger.info("Image = ${Image}")
imageRepository.save(Image)

其中的getByteArray(url) 函数实现代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    private fun getByteArray(url: String): ByteArray {
        val urlObj = URL(url)
        return urlObj.readBytes()
    }

前端 html 展示图片代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
        title: '图片',
        field: 'imageBlob',
        align: 'center',
        valign: 'middle',
        formatter: function (value, row, index) {
            // var html = "<img onclick=downloadImage('" + value + "') width='100%' src='" + value + "'>"
            var html = '<img onclick="downBase64Image(this.src)" width="100%" src="data:image/jpg;base64,' + value + '"/>'
            return html
        }
    }

点击下载 js :

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function downloadImage(src) {
    var $a = $("<a></a>").attr("href", src).attr("download", "sotu.png");
    $a[0].click();
}


function downBase64Image(url) {
    var blob = base64Img2Blob(url);
    url = window.URL.createObjectURL(blob);
    var $a = $("<a></a>").attr("href", url).attr("download", "sotu.png");
    $a[0].click();
}


function base64Img2Blob(code) {
    var parts = code.split(';base64,');
    var contentType = parts[0].split(':')[1];
    var raw = window.atob(parts[1]);
    var rawLength = raw.length;

    var uInt8Array = new Uint8Array(rawLength);

    for (var i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
    }

    return new Blob([uInt8Array], {type: contentType});
}

13.2.13 IDEA 的数据库客户端工具

IDEA 的数据库客户端工具

本节完整的项目源码:https://github.com/EasySpringBoot/picture-crawler

本章小结

Spring Framework 5.0中已经添加了对 Kotlin 的支持。使用 Kotlin 集成 SpringBoot 开发非常流畅自然,几乎不需要任何迁移成本。所以,Kotlin 在未来的 Java 服务端领域也必将受到越来越多的程序员的关注。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017.10.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Method-Swizzling 方法交换
所谓的一次性就是:mehod-swizzling写在load方法中,而load方法会主动调用多次,这样会导致方法的重复交换,使方法sel的指向又恢复成原来的imp的问题
PHP开发工程师
2022/03/08
6890
Method-Swizzling 方法交换
iOS 开发:『Runtime』详解(二)Method Swizzling
文中示例代码在: bujige / YSC-Runtime-MethodSwizzling
程序员充电站
2019/07/15
2K1
iOS 开发:『Runtime』详解(二)Method Swizzling
iOS Runtime Method IMP指针详解
OC是消息转发机制,代码在编译的时候会生产Runtime中间代码,运行的时候执行Runtime代码,我们也可以动态的添加Runtime代码。
星宇大前端
2019/01/15
1.6K0
RunTime 之Method Swizzling
有关Runtime的知识总结,我本来想集中写成一篇文章的,但是最后发现实在是太长,而且不利于阅读,最后分成了如下几篇:
進无尽
2018/09/12
1.5K0
RunTime 之Method Swizzling
iOS类簇代码保护引发的Crash原因排查全记录
全民 K 歌在一个业务需求中,接入了一个第三方的 SDK,在接入 SDK 后启动 APP 就出现 crash,在后续的定位排查中,发现这是由一段关于对系统类簇添加保护代码引发的。本文记录了 crash 的原因排查过程及需要关注的一些细节。
QQ音乐技术团队
2023/12/23
3740
iOS类簇代码保护引发的Crash原因排查全记录
iOS运行时(4)——常用函数
1 类 1.1 创建对象 id class_createInstance(Class cls, size_t extraBytes) eg: size_t size = class_getInstanceSize([Person class]); Person *person = class_createInstance([Person class], size); 1.2 获取类名 const char *class_getName(Class cls) eg const char *name = c
羊羽shine
2019/05/29
5950
iOS开发·runtime原理与实践: 方法交换篇(Method Swizzling)(iOS“黑魔法”,埋点统计,禁止UI控件连续点击,防奔溃处理)
Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。
陈满iOS
2018/09/10
2.9K0
iOS开发·runtime原理与实践: 方法交换篇(Method Swizzling)(iOS“黑魔法”,埋点统计,禁止UI控件连续点击,防奔溃处理)
iOS小技能:富文本编辑器
使用[_webView loadHTMLString:html baseURL:baseURL]; 进行代码加载
公众号iOS逆向
2022/08/22
2.4K0
iOS小技能:富文本编辑器
ios开发Runtime详解part3(Method swizzling)
  在 ios开发 Runtime 详解part1和 ios开发 Runtime 详解part2(动态方法解析)中我大致介绍了runtime的基本功能,在这篇文章里,重点介绍一下runtime的一个重要的功能---method swizzling。   说到method swizzling,不得不介绍一下AOP(Aspect Oriented Programming),即面向切面编程。 AOP在java开发中因为有着一个牛逼的框架spring的存在使得AOP能够得以发扬光大,那么在ios开发中,AOP有哪些作用呢?下面我来大致列举一下: 1、记录日志,这也是用的最多的一种。 2、事务管理,如数据库的提交。 3、处理缓存。 4、安全检查,如权限管理。   由于汉字的博大精深,切面两个字已经将这一思想做了很好的诠释,但是如果没有深入的体会还是很难理解的。我们知道,OOP(面向对象)是把一切操作都针对对象进行操作,而面向切面则是对切面进行的操作,也就是对业务的某一个层面进行的操作。   好比我们要对所有的网络请求做一个日志功能,大家首先想到的办法肯定是在网络请求的代码里面加上日志请求的代码,但是假设这个网络请求的代码是被封装起来的,我们没有办法去改变这个请求的源代码,这时候就可以用method swizzling来用我们自定义的方法来替换原有的网络请求的方法,在里面加上日志请求的代码,同时也能够执行网络请求代码。也就是在既有的业务层面中插入新的切面,来处理通用的功能。
Leacode
2018/08/22
5310
iOS 如何实现Aspect Oriented Programming (下)
从调用栈可以看出,Aspects hook过程主要分4个阶段,hookClass,ASPECTS_ARE_BEING_CALLED,prepareClassAndHookSelector,remove。
一缕殇流化隐半边冰霜
2018/08/29
2.5K0
iOS Runtime 简单介绍,以及不同类的 Method Swizzling
runtime 顾名思义就是运行时,其实我们的 App 从你按下 command+R 开始一直到 App 运行起来经历了大致两个阶段,1:编译时,2:运行时。还记得一道很经典的面试题
molier
2022/11/03
6510
iOS Runtime 简单介绍,以及不同类的 Method Swizzling
iOS开发--runtime常用API
1.objc_xxx 系列函数 objc_系列函数关注于宏观使用,如类与协议的空间分配,注册,注销等操作 objc_getClass 获取Class对象 objc_getMetaClass 获取MetaClass对象 objc_allocateClassPair 分配空间,创建类(仅在 创建之后,注册之前 能够添加成员变量) objc_registerClassPair 注册一个类(注册后方可使用该类创建对象) objc_disposeClassPair 注销某个类 objc_allocateProtoco
mukekeheart
2021/07/16
7470
AFNetworking源码探究(十七) —— _AFURLSessionTaskSwizzling实现方法交换
上一篇主要讲述了AFPropertyListRequestSerializer AFJSONRequestSerializer和 AFHTTPRequestSerializer中请求序列化的协议方法的实现。这一篇主要就是转载一篇关于_AFURLSessionTaskSwizzling实现方法交换的文章。
conanma
2021/09/03
7460
iOS-埋点2021-Aspect的改变
originalInvocation:返回被hooked方法的原始invocation
Wilbur-L
2021/04/05
2.7K0
iOS-埋点2021-Aspect的改变
iOS_selector、SEL、IMP、Method都是什么,以及之间的关系
​ 在 Objective-C中使用发送消息的形式来调用方法,其中涉及到 Runtime库中定义的 SEL、 IMP、 Method,它们分别表示什么,以及它们之间的关系。
mikimo
2022/07/20
8910
深入浅出 Runtime(一):初识
对于 NSString *string = [[NSMutableArray alloc]init];
师大小海腾
2020/04/16
1.1K0
iOS runtime探究(四): 从runtiem开始实践Category添加属性与黑魔法method swizzling你要知道的runtime都在这里
你要知道的runtime都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本文主要讲解runtime相关知识,从原理到实践,由于包含内容过多分为以下五篇文章详细讲解,可自行选择需要了解的方向: 从runtime开始: 理解面向对象的类到面向过程的结构体 从runtime开始: 深入理解OC消息转发机制 从runtime开始: 理解OC的属性property 从runtime开始: 实践Category添加属性与黑魔法method sw
WWWWDotPNG
2018/04/10
8440
iOS runtime探究(四): 从runtiem开始实践Category添加属性与黑魔法method swizzling你要知道的runtime都在这里
Objc Runtime 总结
Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。Runtime是C和汇编编写的,这里http://www.opensource.apple.com/source/objc4/可以下到苹果维护的开源代码,GNU也有一个开源的runtime版本,他们都努力的保持一致。苹果官方的Runtime编程指南
用户7451029
2020/06/16
8350
iOS runtime通过selector获取IMP地址
使用class_getMethodImplementation分别获取实例方法、类方法的IMP。打印出来有两个相同的地址0x0000000105f4da00,这是在调用class_getMethodImplementation时无法找到对应的实现方法。(你可以执行多次都会发现这两个地址虽然会变但都会相同)
用户6094182
2019/08/23
1.8K0
iOS小技能: 限制按钮的点击频率(Target-Action设计模式的运用)
在项目开发中,会对数据库数据进行更新操作的接口请求,不仅服务器侧需要控制请求频率以及保证数据的唯一性和一致性,app侧也需要进行限制来避免产生垃圾数据
公众号iOS逆向
2022/08/22
9290
iOS小技能: 限制按钮的点击频率(Target-Action设计模式的运用)
推荐阅读
相关推荐
Method-Swizzling 方法交换
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验