首页 > 解决方案 > 无法从内存堆栈中释放手势识别器

问题描述

我正在尝试在我的 mapView 上实现自定义手势识别器,以防止用户放大或缩小超过 MKCoordinateSpan 设置的某个阈值。

mapView 的 ViewController 是标签栏控制器的一部分,所以每次视图消失时我都会删除 mapView 并重新添加它以用于内存目的。

由于我添加了自定义手势识别器,因此当视图消失时内存不会被释放。除了从 mapView 中删除手势识别器之外,我还缺少什么?

地图视图控制器:

class MapViewController: UIViewController, CLLocationManagerDelegate {

    @IBOutlet var mapView: MKMapView!

    override func viewDidLoad() {
        super.viewDidLoad()
        loadMapView()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        if mapView == nil {
            loadMapView()
        }
    }

    override func viewDidDisappear(_ animated:Bool) {
        super.viewDidDisappear(animated)
        self.applyMapViewMemoryFix()
    }

    func loadMapView() {
        self.edgesForExtendedLayout = []
        setMapView()
    }

    func setMapView() {
        if self.mapView == nil {
            addMapView()
        }

        mapView.delegate = self
        mapView.mapType = .mutedStandard
        mapView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
    }

    func addMapView() {
        mapView = MKMapView()
        mapView.frame = self.navigationController!.view.bounds
        mapView.mapType = MKMapType.standard
        mapView.isZoomEnabled = true
        mapView.isScrollEnabled = true
        self.view.addSubview(mapView)
    }

    func applyMapViewMemoryFix() {
        for recognizer in (self.mapView?.gestureRecognizers)! {
            if recognizer is WildCardGestureRecognizer {
                self.mapView.removeGestureRecognizer(recognizer)
            }
        }

        self.mapView.showsUserLocation = false
        self.mapView.delegate = nil
        self.mapView.removeFromSuperview()
        self.mapView = nil
    }

}

我为手势识别器设置边界的扩展:

extension MapViewController: MKMapViewDelegate {

    // View Region Changing
    func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
        let northernBorder = 32.741152
        let southernBorder = 32.731461
        let easternBorder = -117.143622
        let westernBorder = -117.157399

        var latitude  = mapView.region.center.latitude
        var longitude = mapView.region.center.longitude

        if (mapView.region.center.latitude > northernBorder) {
            latitude = northernBorder
        }

        if (mapView.region.center.latitude <  southernBorder) {
            latitude = southernBorder
        }

        if (mapView.region.center.longitude > easternBorder) {
            longitude = easternBorder
        }

        if (mapView.region.center.longitude < westernBorder) {
            longitude = westernBorder
        }

        let tapInterceptor = WildCardGestureRecognizer(target: nil, action: nil)

        tapInterceptor.touchesBeganCallback = {_, _ in
            mapView.isZoomEnabled = true
        }

        tapInterceptor.touchesMovedCallback = {_, _ in
            if tapInterceptor.scale < 1 {
                if (latitude != mapView.region.center.latitude || longitude != mapView.region.center.longitude)
                    || ((mapView.region.span.latitudeDelta > (northernBorder - southernBorder) )
                        || (mapView.region.span.longitudeDelta > (easternBorder - westernBorder))) {
                    let span = MKCoordinateSpan.init(latitudeDelta: 0.007, longitudeDelta: 0.007)
                    if mapView.region.span.latitudeDelta > span.latitudeDelta || mapView.region.span.longitudeDelta > span.longitudeDelta {
                        mapView.isZoomEnabled = false
                    } else {
                        mapView.isZoomEnabled = true
                    }
                }
            } else if tapInterceptor.scale > 1 {
                let minimumSpan = MKCoordinateSpan.init(latitudeDelta: 0.002, longitudeDelta: 0.002)
                if mapView.region.span.latitudeDelta < minimumSpan.latitudeDelta || mapView.region.span.longitudeDelta < minimumSpan.longitudeDelta {
                    mapView.isZoomEnabled = false
                } else {
                    mapView.isZoomEnabled = true
                }
            }
        }

        tapInterceptor.touchesEndedCallback = {_, _ in
            mapView.isZoomEnabled = true
        }

        mapView.addGestureRecognizer(tapInterceptor)
    }
}

自定义手势识别器:

class WildCardGestureRecognizer: UIPinchGestureRecognizer {

    var touchesBeganCallback: ((Set<UITouch>, UIEvent) -> Void)?
    var touchesMovedCallback: ((Set<UITouch>, UIEvent) -> Void)?
    var touchesEndedCallback: ((Set<UITouch>, UIEvent) -> Void)?

    override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        self.cancelsTouchesInView = false
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        touchesBeganCallback?(touches, event)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)
        touchesMovedCallback?(touches, event)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesEnded(touches, with: event)
        touchesEndedCallback?(touches, event)
    }

    override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
        return false
    }

    override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
        return false
    }
}

标签: iosswiftuikitmapkituipinchgesturerecognizer

解决方案


在这里声明手势时内存泄漏

 let tapInterceptor = WildCardGestureRecognizer(target: nil, action: nil)
 .
 .
 .
 mapView.addGestureRecognizer(tapInterceptor)

在里面regionDidChangeAnimated,因为它被多次调用,随着区域的变化,你会在地图视图中添加很多手势,所以最好创建一个实例变量,比如

var tapInterceptor:WildCardGestureRecognizer!

并在函数中添加手势初始化和回调,然后将其称为表单viewDidLoad

同时删除@IBOutle

@IBOutlet var mapView: MKMapView!

如果您不在情节提要中制作,我也不认为删除/添加方式会有所作为,因为 IOS 中对象的释放始终不会释放整个参与部分,因此最好将地图视图保留为 1手势而不是在您选择/取消选择该点击时从丢失的那些中累积大量泄漏


推荐阅读