티스토리 뷰

🍏/iOS

[iOS] object_setClass 의 위험성

eungding 2022. 11. 4. 21:55
728x90
반응형

[1] ISA Swizzling

Method Swizzling이 런타임에 특정 메서드를 다른 메서드로 바꿔서 실행될 수 있게 하는 것이라면,

ISA Swizzling은 런타임에 특정 객체를 다른 객체로 바꿔서 실행될 수 있게 하는 것을 말합니다. 

(isa 는 포인터를 의미합니다) 

 

object_setClass  를 이용해서 isa swizzling을 할 수 있습니다. 

 

예를들어 나의 커스텀 탭바를 사용하고 싶은 상황을 봅시다. 

TabBarController의 tabBar 프로퍼티는 get only 이고 이를 세팅할 수 없는 이니셜라이저나 메소드가 없습니다.

 

이 때 isa swizzling을 통해 기본 탭바를 서브클래싱한 커스텀 탭바를 사용할 수 있습니다. 

private class CustomTabBar: UITabBar {

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var sizeThatFits = super.sizeThatFits(size)
        sizeThatFits.height += 5
        return sizeThatFits
    }
}

class TabBarController: UITabBarController {

    init() {
        super.init(nibName: nil, bundle: nil)
        object_setClass(self.tabBar, CustomTabBar.self)
    }
 }

 

[2] object_setClass 의 위험성 

 

원래 객체보다 커스텀 객체가 더 큰 경우, 문제가 됩니다. 

예를 들어 위의 예제에서 CustomTabBar 에 단지 오버라이딩 코드가 아니라 추가적인 프로퍼티, 메소드들 더 작성하는 경우 입니다. 

 

이때 간헐적으로 크래쉬가 나는 현상이 발생하게 됩니다. 

Xcode > Product > Scheme > Edit Scheme > Diagnostics 를 키고 돌려보면 

CustomTabBar 에서 'Heap Buffer Overflow' 가 잡히는 것을 확인할 수 있고

콘솔창에서도 로그를 볼 수 있습니다. 

 

그 이유는 무엇일까요? 

object_setClass 는 객체를 reallocate 하지 않고 오직 isa pointer 만 바꾸기 때문입니다. 

 

즉 아래와 같은 상황이 발생합니다.

 

원래 객체 (original UITabBar) 가 초기화될때 그 객체 사이즈에 맞게 메모리가 할당되었음

->  object_setClass를 호출하여 더 큰 메모리를 필요로하는 객체 (custom tabBar) 로 포인터를 바꿈 // 포인터만 바꿈. 재할당  X. 

-> 이 때 새로운 객체는 메모리 공간이 부족하므로 간헐적으로 크래쉬 발생 가능 (특히 custom tabBar에 새로 추가한 코드가 실행될 때) 

 

 

여기 설명이 더 정확해요! 

 

All we are doing when we call object_setClass is changing the isa pointer to the one of our new class. Our original object has already been initialised, and the correct amount of memory has been allocated. When we change the pointer we now have a new class which may need more memory, if it has more methods, ivars etc. This is where the problem lies. New methods may not have enough memory to execute so we could start to see random crashes appearing.

 

 

https://stackoverflow.com/questions/3346427/object-setclass-to-bigger-class 도 참고해주세요! 

 

 

[3] 더 좋은 대안 

setValue 를 사용하는게 더 안전한 방법입니다.

(+ 원래보다 더 큰 서브클래스를 쓸 일이 없다.. 하더라도 개발하다보면 코드를 추가하게 될 일이 생길 수 도 있으니까 처음부터 setValue 를 쓰는 게 좋을 것 같아요)

 

private class CustomTabBar: UITabBar {

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var sizeThatFits = super.sizeThatFits(size)
        sizeThatFits.height += 5
        return sizeThatFits
    }
}

class TabBarController: UITabBarController {

    override func viewDidLoad()
        super.viewDidLoad()
        let tabBar = CustomTabBar()
        self.setValue(tabBar, forKey: "tabBar")
    }
 }

 

+ 애플이 저런 기본 UI는 key 값을 하드코딩으로 안쓸 수 있게 제공해주면 좋을텐데ㅠㅠ.. 

 

 

 

✨ Special Thanks to liam

 

 

반응형
댓글